From 748b121703fa28b10933f4432c09391e66179118 Mon Sep 17 00:00:00 2001
From: Tiger Wang <ziwei.tiger@outlook.com>
Date: Sun, 28 Mar 2021 14:40:57 +0100
Subject: [PATCH] Unify DoWithBlockEntity (#5168)

+ DoWith calls now broadcast the block entity and mark the chunk dirty
+ Add block entity change queue to synchronise BE updates with block updates
* Fixed a few incorrect assertions about BE type
- Remove manual overloads
---
 src/Bindings/ManualBindings.h                 | 222 ----------
 src/Bindings/ManualBindings_BlockArea.cpp     |  60 +++
 src/Bindings/ManualBindings_World.cpp         | 291 +++++++++++--
 src/BlockEntities/BedEntity.cpp               |   8 -
 src/BlockEntities/ChestEntity.cpp             |  21 +-
 src/BlockEntities/CommandBlockEntity.cpp      |  11 -
 src/BlockEntities/MobHeadEntity.cpp           |   5 -
 src/BlockEntities/MobSpawnerEntity.cpp        |   3 +-
 src/BlockType.h                               |  33 --
 src/Blocks/BlockBed.cpp                       |  12 +-
 src/Blocks/BlockEnchantingTable.h             |   4 +-
 src/Blocks/BlockNoteBlock.h                   |   9 +-
 src/Blocks/WorldInterface.h                   |   6 +-
 src/Chunk.cpp                                 | 402 +++---------------
 src/Chunk.h                                   | 108 +----
 src/ChunkDef.h                                |  27 --
 src/ChunkMap.cpp                              | 376 +---------------
 src/ChunkMap.h                                | 118 +----
 src/ChunkSender.cpp                           |   2 +-
 src/ChunkSender.h                             |   2 +-
 src/Items/ItemBanner.h                        |   6 +-
 src/Items/ItemEnchantingTable.h               |  12 +-
 src/Items/ItemMobHead.h                       |  62 ++-
 src/Mobs/Path.cpp                             |   1 +
 src/Mobs/PathFinder.cpp                       |   1 +
 src/Protocol/Protocol_1_11.cpp                |   5 -
 src/Protocol/Protocol_1_9.cpp                 |   6 -
 .../CommandBlockHandler.h                     |   9 +-
 .../DropSpenserHandler.h                      |   9 +-
 .../HopperHandler.h                           |   9 +-
 .../IncrementalRedstoneSimulator.h            |   1 +
 .../NoteBlockHandler.h                        |   9 +-
 .../RedstoneHandler.cpp                       |   1 +
 .../TrappedChestHandler.h                     |  16 +-
 src/Simulator/VaporizeFluidSimulator.cpp      |   1 +
 src/World.cpp                                 | 217 ++--------
 src/World.h                                   |  61 +--
 37 files changed, 550 insertions(+), 1596 deletions(-)

diff --git a/src/Bindings/ManualBindings.h b/src/Bindings/ManualBindings.h
index a23c5d2e4..780c6ce41 100644
--- a/src/Bindings/ManualBindings.h
+++ b/src/Bindings/ManualBindings.h
@@ -211,228 +211,6 @@ public:
 
 
 
-	/** Template for the bindings for the DoWithXYZAt(X, Y, Z) functions that don't need to check their coords. */
-	template <
-		class SELF,
-		class ITEM,
-		bool (SELF::*DoWithFn)(int, int, int, cFunctionRef<bool(ITEM &)>)
-	>
-	static int DoWithXYZ(lua_State * tolua_S)
-	{
-		// Check params:
-		cLuaState L(tolua_S);
-		if (
-			!L.CheckParamNumber(2, 4) ||
-			!L.CheckParamFunction(5) ||
-			!L.CheckParamEnd(6)
-		)
-		{
-			return 0;
-		}
-
-		// Get parameters:
-		SELF * Self = nullptr;
-		int BlockX = 0;
-		int BlockY = 0;
-		int BlockZ = 0;
-		cLuaState::cRef FnRef;
-		L.GetStackValues(1, Self, BlockX, BlockY, BlockZ, FnRef);
-		if (Self == nullptr)
-		{
-			return lua_do_error(tolua_S, "Error in function call '#funcname#': Invalid 'self'");
-		}
-		if (!FnRef.IsValid())
-		{
-			return lua_do_error(tolua_S, "Error in function call '#funcname#': Expected a valid callback function for parameter #5");
-		}
-
-		// Call the DoWith function:
-		bool res = (Self->*DoWithFn)(BlockX, BlockY, BlockZ, [&](ITEM & a_Item)
-			{
-				bool ret = false;
-				L.Call(FnRef, &a_Item, cLuaState::Return, ret);
-				return ret;
-			}
-		);
-
-		// Push the result as the return value:
-		L.Push(res);
-		return 1;
-	}
-
-
-
-
-
-	/** Template for the bindings for the DoWithXYZAt(X, Y, Z) functions that need to check their coords. */
-	template <
-		class SELF,
-		class ITEM,
-		bool (SELF::*DoWithFn)(int, int, int, cFunctionRef<bool(ITEM &)>),
-		bool (SELF::*CoordCheckFn)(int, int, int) const
-	>
-	static int DoWithXYZ(lua_State * tolua_S)
-	{
-		// Check params:
-		cLuaState L(tolua_S);
-		if (
-			!L.CheckParamNumber(2, 4) ||
-			!L.CheckParamFunction(5) ||
-			!L.CheckParamEnd(6)
-		)
-		{
-			return 0;
-		}
-
-		// Get parameters:
-		SELF * Self = nullptr;
-		int BlockX = 0;
-		int BlockY = 0;
-		int BlockZ = 0;
-		cLuaState::cRef FnRef;
-		L.GetStackValues(1, Self, BlockX, BlockY, BlockZ, FnRef);
-		if (Self == nullptr)
-		{
-			return L.ApiParamError("Invalid 'self'");
-		}
-		if (!FnRef.IsValid())
-		{
-			return L.ApiParamError("Expected a valid callback function for parameter #5");
-		}
-		if (!(Self->*CoordCheckFn)(BlockX, BlockY, BlockZ))
-		{
-			return L.FApiParamError("The provided coordinates ({0}) are not valid",
-				Vector3i{BlockX, BlockY, BlockZ}
-			);
-		}
-
-		// Call the DoWith function:
-		bool res = (Self->*DoWithFn)(BlockX, BlockY, BlockZ, [&](ITEM & a_Item)
-			{
-				bool ret = false;
-				L.Call(FnRef, &a_Item, cLuaState::Return, ret);
-				return ret;
-			}
-		);
-
-		// Push the result as the return value:
-		L.Push(res);
-		return 1;
-	}
-
-
-
-
-
-	template <
-		class Ty1,
-		class Ty2,
-		bool (Ty1::*ForEachFn)(int, int, cFunctionRef<bool(Ty2 &)>)
-	>
-	static int ForEachInChunk(lua_State * tolua_S)
-	{
-		// Check params:
-		cLuaState L(tolua_S);
-		if (
-			!L.CheckParamNumber(2, 3) ||
-			!L.CheckParamFunction(4) ||
-			!L.CheckParamEnd(5)
-		)
-		{
-			return 0;
-		}
-
-		// Get parameters:
-		Ty1 * Self = nullptr;
-		int ChunkX = 0;
-		int ChunkZ = 0;
-		cLuaState::cRef FnRef;
-		L.GetStackValues(1, Self, ChunkX, ChunkZ, FnRef);
-		if (Self == nullptr)
-		{
-			return lua_do_error(tolua_S, "Error in function call '#funcname#': Invalid 'self'");
-		}
-		if (!FnRef.IsValid())
-		{
-			return lua_do_error(tolua_S, "Error in function call '#funcname#': Expected a valid callback function for parameter #4");
-		}
-
-		// Call the DoWith function:
-		bool res = (Self->*ForEachFn)(ChunkX, ChunkZ, [&](Ty2 & a_Item)
-			{
-				bool ret = false;
-				L.Call(FnRef, &a_Item, cLuaState::Return, ret);
-				return ret;
-			}
-		);
-
-		// Push the result as the return value:
-		L.Push(res);
-		return 1;
-	}
-
-
-
-
-
-	template <
-		class Ty1,
-		class Ty2,
-		bool (Ty1::*ForEachFn)(const cBoundingBox &, cFunctionRef<bool(Ty2 &)>)
-	>
-	static int ForEachInBox(lua_State * tolua_S)
-	{
-		// Check params:
-		cLuaState L(tolua_S);
-		if (
-			!L.CheckParamUserType(1, "cWorld") ||
-			!L.CheckParamUserType(2, "cBoundingBox") ||
-			!L.CheckParamFunction(3) ||
-			!L.CheckParamEnd(4)
-		)
-		{
-			return 0;
-		}
-
-		// Get the params:
-		Ty1 * Self = nullptr;
-		cBoundingBox * Box = nullptr;
-		cLuaState::cRef FnRef;
-		L.GetStackValues(1, Self, Box, FnRef);
-		if ((Self == nullptr) || (Box == nullptr))
-		{
-			LOGWARNING("Invalid world (%p) or boundingbox (%p)", static_cast<void *>(Self), static_cast<void *>(Box));
-			L.LogStackTrace();
-			return 0;
-		}
-		if (!FnRef.IsValid())
-		{
-			return lua_do_error(tolua_S, "Error in function call '#funcname#': Expected a valid callback function for parameter #2");
-		}
-
-		bool res = (Self->*ForEachFn)(*Box, [&](Ty2 & a_Item)
-			{
-				bool ret = false;
-				if (!L.Call(FnRef, &a_Item, cLuaState::Return, ret))
-				{
-					LOGWARNING("Failed to call Lua callback");
-					L.LogStackTrace();
-					return true;  // Abort enumeration
-				}
-
-				return ret;
-			}
-		);
-
-		// Push the result as the return value:
-		L.Push(res);
-		return 1;
-	}
-
-
-
-
-
 	template <
 		class Ty1,
 		class Ty2,
diff --git a/src/Bindings/ManualBindings_BlockArea.cpp b/src/Bindings/ManualBindings_BlockArea.cpp
index 5f281fadc..a53a7bebf 100644
--- a/src/Bindings/ManualBindings_BlockArea.cpp
+++ b/src/Bindings/ManualBindings_BlockArea.cpp
@@ -15,6 +15,66 @@
 
 
 
+/** Template for the bindings for the DoWithXYZAt(X, Y, Z) functions that need to check their coords. */
+template <
+	class SELF,
+	class ITEM,
+	bool (SELF::*DoWithFn)(int, int, int, cFunctionRef<bool(ITEM &)>),
+	bool (SELF::*CoordCheckFn)(int, int, int) const
+>
+static int DoWithXYZ(lua_State * tolua_S)
+{
+	// Check params:
+	cLuaState L(tolua_S);
+	if (
+		!L.CheckParamNumber(2, 4) ||
+		!L.CheckParamFunction(5) ||
+		!L.CheckParamEnd(6)
+	)
+	{
+		return 0;
+	}
+
+	// Get parameters:
+	SELF * Self = nullptr;
+	int BlockX = 0;
+	int BlockY = 0;
+	int BlockZ = 0;
+	cLuaState::cRef FnRef;
+	L.GetStackValues(1, Self, BlockX, BlockY, BlockZ, FnRef);
+	if (Self == nullptr)
+	{
+		return L.ApiParamError("Invalid 'self'");
+	}
+	if (!FnRef.IsValid())
+	{
+		return L.ApiParamError("Expected a valid callback function for parameter #5");
+	}
+	if (!(Self->*CoordCheckFn)(BlockX, BlockY, BlockZ))
+	{
+		return L.FApiParamError("The provided coordinates ({0}) are not valid",
+			Vector3i{BlockX, BlockY, BlockZ}
+		);
+	}
+
+	// Call the DoWith function:
+	bool res = (Self->*DoWithFn)(BlockX, BlockY, BlockZ, [&](ITEM & a_Item)
+		{
+			bool ret = false;
+			L.Call(FnRef, &a_Item, cLuaState::Return, ret);
+			return ret;
+		}
+	);
+
+	// Push the result as the return value:
+	L.Push(res);
+	return 1;
+}
+
+
+
+
+
 /** Reads params that together form a Cuboid.
 These can be:
 	- 6 numbers (MinX, MaxX, MinY, MaxY, MinZ, MaxZ)
diff --git a/src/Bindings/ManualBindings_World.cpp b/src/Bindings/ManualBindings_World.cpp
index 0a748c55d..db797483d 100644
--- a/src/Bindings/ManualBindings_World.cpp
+++ b/src/Bindings/ManualBindings_World.cpp
@@ -12,6 +12,23 @@
 #include "PluginLua.h"
 #include "LuaChunkStay.h"
 
+#include "BlockEntities/BeaconEntity.h"
+#include "BlockEntities/BedEntity.h"
+#include "BlockEntities/BrewingstandEntity.h"
+#include "BlockEntities/ChestEntity.h"
+#include "BlockEntities/CommandBlockEntity.h"
+#include "BlockEntities/DispenserEntity.h"
+#include "BlockEntities/DropSpenserEntity.h"
+#include "BlockEntities/DropperEntity.h"
+#include "BlockEntities/FlowerPotEntity.h"
+#include "BlockEntities/FurnaceEntity.h"
+#include "BlockEntities/HopperEntity.h"
+#include "BlockEntities/MobHeadEntity.h"
+#include "BlockEntities/NoteEntity.h"
+
+
+
+
 
 /** Check that a Lua parameter is either a vector or 3 numbers in sequence
 \param L The Lua state
@@ -50,6 +67,183 @@ static bool GetStackVectorOr3Numbers(cLuaState & L, int a_Index, Vector3<T> & a_
 
 
 
+/** Template for the bindings for the DoWithXYZAt(X, Y, Z) functions that don't need to check their coords. */
+template <class BlockEntityType, BLOCKTYPE... BlockTypes>
+static int DoWithBlockEntityAt(lua_State * tolua_S)
+{
+	cLuaState L(tolua_S);
+	int OffsetIndex;
+
+	// Check params:
+	if (
+		!L.CheckParamSelf("cWorld") ||
+		!CheckParamVectorOr3Numbers(L, "Vector3<int>", 2, OffsetIndex) ||
+		!L.CheckParamFunction(OffsetIndex) ||
+		!L.CheckParamEnd(OffsetIndex + 1)
+	)
+	{
+		return 0;
+	}
+
+	cWorld * Self = nullptr;
+	Vector3i Position;
+	cLuaState::cRef FnRef;
+
+	// Get parameters:
+	if (
+		!L.GetStackValues(1, Self) ||
+		!GetStackVectorOr3Numbers(L, 2, Position) ||
+		!L.GetStackValues(OffsetIndex, FnRef)
+	)
+	{
+		return 0;
+	}
+
+	if (Self == nullptr)
+	{
+		return L.ApiParamError("Invalid 'self'");
+	}
+	if (!FnRef.IsValid())
+	{
+		return L.ApiParamError("Expected a valid callback function for parameter %i", OffsetIndex);
+	}
+
+	// Call the DoWith function:
+	bool res = Self->DoWithBlockEntityAt(Position, [&L, &FnRef](cBlockEntity & a_BlockEntity)
+	{
+		if constexpr (sizeof...(BlockTypes) != 0)
+		{
+			if (((a_BlockEntity.GetBlockType() != BlockTypes) && ...))
+			{
+				return false;
+			}
+		}
+
+		bool ret = false;
+		L.Call(FnRef, static_cast<BlockEntityType *>(&a_BlockEntity), cLuaState::Return, ret);
+		return ret;
+	});
+
+	// Push the result as the return value:
+	L.Push(res);
+	return 1;
+}
+
+
+
+
+
+template <
+	class Ty1,
+	class Ty2,
+	bool (Ty1::*ForEachFn)(const cBoundingBox &, cFunctionRef<bool(Ty2 &)>)
+>
+static int ForEachInBox(lua_State * tolua_S)
+{
+	// Check params:
+	cLuaState L(tolua_S);
+	if (
+		!L.CheckParamUserType(1, "cWorld") ||
+		!L.CheckParamUserType(2, "cBoundingBox") ||
+		!L.CheckParamFunction(3) ||
+		!L.CheckParamEnd(4)
+	)
+	{
+		return 0;
+	}
+
+	// Get the params:
+	Ty1 * Self = nullptr;
+	cBoundingBox * Box = nullptr;
+	cLuaState::cRef FnRef;
+	L.GetStackValues(1, Self, Box, FnRef);
+	if ((Self == nullptr) || (Box == nullptr))
+	{
+		return L.ApiParamError("Invalid world (%p) or boundingbox (%p)", static_cast<void *>(Self), static_cast<void *>(Box));
+	}
+	if (!FnRef.IsValid())
+	{
+		return L.ApiParamError("Expected a valid callback function for parameter #2");
+	}
+
+	bool res = (Self->*ForEachFn)(*Box, [&](Ty2 & a_Item)
+		{
+			bool ret = false;
+			if (!L.Call(FnRef, &a_Item, cLuaState::Return, ret))
+			{
+				LOGWARNING("Failed to call Lua callback");
+				L.LogStackTrace();
+				return true;  // Abort enumeration
+			}
+
+			return ret;
+		}
+	);
+
+	// Push the result as the return value:
+	L.Push(res);
+	return 1;
+}
+
+
+
+
+
+template <class BlockEntityType, BLOCKTYPE... BlockTypes>
+static int ForEachBlockEntityInChunk(lua_State * tolua_S)
+{
+	// Check params:
+	cLuaState L(tolua_S);
+	if (
+		!L.CheckParamSelf("cWorld") ||
+		!L.CheckParamNumber(2, 3) ||
+		!L.CheckParamFunction(4) ||
+		!L.CheckParamEnd(5)
+	)
+	{
+		return 0;
+	}
+
+	// Get parameters:
+	cWorld * Self = nullptr;
+	int ChunkX = 0;
+	int ChunkZ = 0;
+	cLuaState::cRef FnRef;
+	L.GetStackValues(1, Self, ChunkX, ChunkZ, FnRef);
+	if (Self == nullptr)
+	{
+		return L.ApiParamError("Error in function call '#funcname#': Invalid 'self'");
+	}
+	if (!FnRef.IsValid())
+	{
+		return L.ApiParamError("Expected a valid callback function for parameter #4");
+	}
+
+	// Call the ForEach function:
+	bool res = Self->ForEachBlockEntityInChunk(ChunkX, ChunkZ, [&L, &FnRef](cBlockEntity & a_BlockEntity)
+	{
+		if constexpr (sizeof...(BlockTypes) != 0)
+		{
+			if (((a_BlockEntity.GetBlockType() != BlockTypes) && ...))
+			{
+				return false;
+			}
+		}
+
+		bool ret = false;
+		L.Call(FnRef, static_cast<BlockEntityType *>(&a_BlockEntity), cLuaState::Return, ret);
+		return ret;
+	});
+
+	// Push the result as the return value:
+	L.Push(res);
+	return 1;
+}
+
+
+
+
+
 static int tolua_cWorld_BroadcastBlockAction(lua_State * tolua_S)
 {
 	/* Function signature:
@@ -566,6 +760,53 @@ static int tolua_cWorld_FastSetBlock(lua_State * tolua_S)
 
 
 
+static int tolua_cWorld_ForEachEntityInChunk(lua_State * tolua_S)
+{
+	// Check params:
+	cLuaState L(tolua_S);
+	if (
+		!L.CheckParamUserType(1, "cWorld") ||
+		!L.CheckParamNumber(2, 3) ||
+		!L.CheckParamFunction(4) ||
+		!L.CheckParamEnd(5)
+	)
+	{
+		return 0;
+	}
+
+	// Get parameters:
+	cWorld * Self = nullptr;
+	int ChunkX = 0;
+	int ChunkZ = 0;
+	cLuaState::cRef FnRef;
+	L.GetStackValues(1, Self, ChunkX, ChunkZ, FnRef);
+	if (Self == nullptr)
+	{
+		return L.ApiParamError("Invalid 'self'");
+	}
+	if (!FnRef.IsValid())
+	{
+		return L.ApiParamError("Expected a valid callback function for parameter #4");
+	}
+
+	// Call the DoWith function:
+	bool res = Self->ForEachEntityInChunk(ChunkX, ChunkZ, [&](cEntity & a_Item)
+		{
+			bool ret = false;
+			L.Call(FnRef, &a_Item, cLuaState::Return, ret);
+			return ret;
+		}
+	);
+
+	// Push the result as the return value:
+	L.Push(res);
+	return 1;
+}
+
+
+
+
+
 static int tolua_cWorld_ForEachLoadedChunk(lua_State * tolua_S)
 {
 	// Exported manually, because tolua doesn't support converting functions to functor types.
@@ -1347,35 +1588,35 @@ void cManualBindings::BindWorld(lua_State * tolua_S)
 			tolua_function(tolua_S, "BroadcastParticleEffect",      tolua_cWorld_BroadcastParticleEffect);
 			tolua_function(tolua_S, "ChunkStay",                    tolua_cWorld_ChunkStay);
 			tolua_function(tolua_S, "DoExplosionAt",                tolua_cWorld_DoExplosionAt);
-			tolua_function(tolua_S, "DoWithBeaconAt",               DoWithXYZ<cWorld, cBeaconEntity,       &cWorld::DoWithBeaconAt>);
-			tolua_function(tolua_S, "DoWithBedAt",                  DoWithXYZ<cWorld, cBedEntity,          &cWorld::DoWithBedAt>);
-			tolua_function(tolua_S, "DoWithBlockEntityAt",          DoWithXYZ<cWorld, cBlockEntity,        &cWorld::DoWithBlockEntityAt>);
-			tolua_function(tolua_S, "DoWithBrewingstandAt",         DoWithXYZ<cWorld, cBrewingstandEntity, &cWorld::DoWithBrewingstandAt>);
-			tolua_function(tolua_S, "DoWithChestAt",                DoWithXYZ<cWorld, cChestEntity,        &cWorld::DoWithChestAt>);
-			tolua_function(tolua_S, "DoWithCommandBlockAt",         DoWithXYZ<cWorld, cCommandBlockEntity, &cWorld::DoWithCommandBlockAt>);
-			tolua_function(tolua_S, "DoWithDispenserAt",            DoWithXYZ<cWorld, cDispenserEntity,    &cWorld::DoWithDispenserAt>);
-			tolua_function(tolua_S, "DoWithDropSpenserAt",          DoWithXYZ<cWorld, cDropSpenserEntity,  &cWorld::DoWithDropSpenserAt>);
-			tolua_function(tolua_S, "DoWithDropperAt",              DoWithXYZ<cWorld, cDropperEntity,      &cWorld::DoWithDropperAt>);
-			tolua_function(tolua_S, "DoWithEntityByID",             DoWithID< cWorld, cEntity,             &cWorld::DoWithEntityByID>);
-			tolua_function(tolua_S, "DoWithFlowerPotAt",            DoWithXYZ<cWorld, cFlowerPotEntity,    &cWorld::DoWithFlowerPotAt>);
-			tolua_function(tolua_S, "DoWithFurnaceAt",              DoWithXYZ<cWorld, cFurnaceEntity,      &cWorld::DoWithFurnaceAt>);
-			tolua_function(tolua_S, "DoWithHopperAt",               DoWithXYZ<cWorld, cHopperEntity,       &cWorld::DoWithHopperAt>);
-			tolua_function(tolua_S, "DoWithMobHeadAt",              DoWithXYZ<cWorld, cMobHeadEntity,      &cWorld::DoWithMobHeadAt>);
+			tolua_function(tolua_S, "DoWithBeaconAt",               DoWithBlockEntityAt<cBeaconEntity, E_BLOCK_BEACON>);
+			tolua_function(tolua_S, "DoWithBedAt",                  DoWithBlockEntityAt<cBedEntity, E_BLOCK_BED>);
+			tolua_function(tolua_S, "DoWithBlockEntityAt",          DoWithBlockEntityAt<cBlockEntity>);
+			tolua_function(tolua_S, "DoWithBrewingstandAt",         DoWithBlockEntityAt<cBrewingstandEntity, E_BLOCK_BREWING_STAND>);
+			tolua_function(tolua_S, "DoWithChestAt",                DoWithBlockEntityAt<cChestEntity, E_BLOCK_CHEST, E_BLOCK_TRAPPED_CHEST>);
+			tolua_function(tolua_S, "DoWithCommandBlockAt",         DoWithBlockEntityAt<cCommandBlockEntity, E_BLOCK_COMMAND_BLOCK>);
+			tolua_function(tolua_S, "DoWithDispenserAt",            DoWithBlockEntityAt<cDispenserEntity, E_BLOCK_DISPENSER>);
+			tolua_function(tolua_S, "DoWithDropSpenserAt",          DoWithBlockEntityAt<cDropSpenserEntity, E_BLOCK_DISPENSER, E_BLOCK_DROPPER>);
+			tolua_function(tolua_S, "DoWithDropperAt",              DoWithBlockEntityAt<cDropperEntity, E_BLOCK_DROPPER>);
+			tolua_function(tolua_S, "DoWithEntityByID",             DoWithID<cWorld, cEntity, &cWorld::DoWithEntityByID>);
+			tolua_function(tolua_S, "DoWithFlowerPotAt",            DoWithBlockEntityAt<cFlowerPotEntity, E_BLOCK_FLOWER_POT>);
+			tolua_function(tolua_S, "DoWithFurnaceAt",              DoWithBlockEntityAt<cFurnaceEntity, E_BLOCK_FURNACE, E_BLOCK_LIT_FURNACE>);
+			tolua_function(tolua_S, "DoWithHopperAt",               DoWithBlockEntityAt<cHopperEntity, E_BLOCK_HOPPER>);
+			tolua_function(tolua_S, "DoWithMobHeadAt",              DoWithBlockEntityAt<cMobHeadEntity, E_BLOCK_HEAD>);
 			tolua_function(tolua_S, "DoWithNearestPlayer",          tolua_cWorld_DoWithNearestPlayer);
-			tolua_function(tolua_S, "DoWithNoteBlockAt",            DoWithXYZ<cWorld, cNoteEntity,         &cWorld::DoWithNoteBlockAt>);
-			tolua_function(tolua_S, "DoWithPlayer",                 DoWith<   cWorld, cPlayer,             &cWorld::DoWithPlayer>);
+			tolua_function(tolua_S, "DoWithNoteBlockAt",            DoWithBlockEntityAt<cNoteEntity, E_BLOCK_NOTE_BLOCK>);
+			tolua_function(tolua_S, "DoWithPlayer",                 DoWith<cWorld, cPlayer, &cWorld::DoWithPlayer>);
 			tolua_function(tolua_S, "DoWithPlayerByUUID",           tolua_cWorld_DoWithPlayerByUUID);
 			tolua_function(tolua_S, "FastSetBlock",                 tolua_cWorld_FastSetBlock);
-			tolua_function(tolua_S, "FindAndDoWithPlayer",          DoWith<   cWorld, cPlayer,             &cWorld::FindAndDoWithPlayer>);
-			tolua_function(tolua_S, "ForEachBlockEntityInChunk",    ForEachInChunk<cWorld, cBlockEntity,   &cWorld::ForEachBlockEntityInChunk>);
-			tolua_function(tolua_S, "ForEachBrewingstandInChunk",   ForEachInChunk<cWorld, cBrewingstandEntity, &cWorld::ForEachBrewingstandInChunk>);
-			tolua_function(tolua_S, "ForEachChestInChunk",          ForEachInChunk<cWorld, cChestEntity,   &cWorld::ForEachChestInChunk>);
-			tolua_function(tolua_S, "ForEachEntity",                ForEach<       cWorld, cEntity,        &cWorld::ForEachEntity>);
-			tolua_function(tolua_S, "ForEachEntityInBox",           ForEachInBox<  cWorld, cEntity,        &cWorld::ForEachEntityInBox>);
-			tolua_function(tolua_S, "ForEachEntityInChunk",         ForEachInChunk<cWorld, cEntity,        &cWorld::ForEachEntityInChunk>);
-			tolua_function(tolua_S, "ForEachFurnaceInChunk",        ForEachInChunk<cWorld, cFurnaceEntity, &cWorld::ForEachFurnaceInChunk>);
+			tolua_function(tolua_S, "FindAndDoWithPlayer",          DoWith<cWorld, cPlayer, &cWorld::FindAndDoWithPlayer>);
+			tolua_function(tolua_S, "ForEachBlockEntityInChunk",    ForEachBlockEntityInChunk<cBlockEntity>);
+			tolua_function(tolua_S, "ForEachBrewingstandInChunk",   ForEachBlockEntityInChunk<cBrewingstandEntity, E_BLOCK_BREWING_STAND>);
+			tolua_function(tolua_S, "ForEachChestInChunk",          ForEachBlockEntityInChunk<cChestEntity, E_BLOCK_CHEST, E_BLOCK_TRAPPED_CHEST>);
+			tolua_function(tolua_S, "ForEachEntity",                ForEach<cWorld, cEntity, &cWorld::ForEachEntity>);
+			tolua_function(tolua_S, "ForEachEntityInBox",           ForEachInBox<cWorld, cEntity, &cWorld::ForEachEntityInBox>);
+			tolua_function(tolua_S, "ForEachEntityInChunk",         tolua_cWorld_ForEachEntityInChunk);
+			tolua_function(tolua_S, "ForEachFurnaceInChunk",        ForEachBlockEntityInChunk<cFurnaceEntity, E_BLOCK_FURNACE, E_BLOCK_LIT_FURNACE>);
 			tolua_function(tolua_S, "ForEachLoadedChunk",           tolua_cWorld_ForEachLoadedChunk);
-			tolua_function(tolua_S, "ForEachPlayer",                ForEach<       cWorld, cPlayer,        &cWorld::ForEachPlayer>);
+			tolua_function(tolua_S, "ForEachPlayer",                ForEach<cWorld, cPlayer, &cWorld::ForEachPlayer>);
 			tolua_function(tolua_S, "GetBlock",                     tolua_cWorld_GetBlock);
 			tolua_function(tolua_S, "GetBlockBlockLight",           tolua_cWorld_GetBlockBlockLight);
 			tolua_function(tolua_S, "GetBlockInfo",                 tolua_cWorld_GetBlockInfo);
diff --git a/src/BlockEntities/BedEntity.cpp b/src/BlockEntities/BedEntity.cpp
index 3d7005b12..7f5f90639 100644
--- a/src/BlockEntities/BedEntity.cpp
+++ b/src/BlockEntities/BedEntity.cpp
@@ -56,12 +56,4 @@ void cBedEntity::SendTo(cClientHandle & a_Client)
 void cBedEntity::SetColor(short a_Color)
 {
 	m_Color = a_Color;
-	auto Pos = GetPos();
-
-	// If the bed entity is send immediately, the client (maybe) still has not the bed.
-	// Fix that by delaying the broadcast of the bed entity by a tick:
-	m_World->ScheduleTask(1, [Pos](cWorld & a_World)
-	{
-		a_World.BroadcastBlockEntity(Pos);
-	});
 }
diff --git a/src/BlockEntities/ChestEntity.cpp b/src/BlockEntities/ChestEntity.cpp
index 039d62287..9ede18759 100644
--- a/src/BlockEntities/ChestEntity.cpp
+++ b/src/BlockEntities/ChestEntity.cpp
@@ -146,24 +146,25 @@ bool cChestEntity::UsedBy(cPlayer * a_Player)
 
 void cChestEntity::ScanNeighbours()
 {
-	// Callback for finding neighbouring chest:
-	auto FindNeighbour = [this](cChestEntity & a_Chest)
+	// Callback for finding neighbouring chest.
+	auto FindNeighbour = [this](cBlockEntity & a_BlockEntity)
 	{
-		if (a_Chest.GetBlockType() != m_BlockType)
+		if (a_BlockEntity.GetBlockType() != m_BlockType)
 		{
 			// Neighboring block is not the same type of chest
-			return true;
+			return false;
 		}
-		m_Neighbour = &a_Chest;
-		return false;
+
+		m_Neighbour = static_cast<cChestEntity *>(&a_BlockEntity);
+		return true;
 	};
 
 	// Scan horizontally adjacent blocks for any neighbouring chest of the same type:
 	if (
-		m_World->DoWithChestAt(m_Pos.x - 1, m_Pos.y, m_Pos.z,     FindNeighbour) ||
-		m_World->DoWithChestAt(m_Pos.x + 1, m_Pos.y, m_Pos.z,     FindNeighbour) ||
-		m_World->DoWithChestAt(m_Pos.x,     m_Pos.y, m_Pos.z - 1, FindNeighbour) ||
-		m_World->DoWithChestAt(m_Pos.x,     m_Pos.y, m_Pos.z + 1, FindNeighbour)
+		m_World->DoWithBlockEntityAt(m_Pos.addedX(-1), FindNeighbour) ||
+		m_World->DoWithBlockEntityAt(m_Pos.addedX(+1), FindNeighbour) ||
+		m_World->DoWithBlockEntityAt(m_Pos.addedZ(-1), FindNeighbour) ||
+		m_World->DoWithBlockEntityAt(m_Pos.addedX(+1), FindNeighbour)
 	)
 	{
 		m_Neighbour->m_Neighbour = this;
diff --git a/src/BlockEntities/CommandBlockEntity.cpp b/src/BlockEntities/CommandBlockEntity.cpp
index c1e2cd430..34b0fd5f5 100644
--- a/src/BlockEntities/CommandBlockEntity.cpp
+++ b/src/BlockEntities/CommandBlockEntity.cpp
@@ -43,15 +43,6 @@ bool cCommandBlockEntity::UsedBy(cPlayer * a_Player)
 void cCommandBlockEntity::SetCommand(const AString & a_Cmd)
 {
 	m_Command = a_Cmd;
-
-	/*
-	Vanilla requires that the server send a Block Entity Update after a command has been set
-	Therefore, command blocks don't support on-the-fly (when window is open) updating of a command and therefore...
-	...the following code can't be put in UsedBy just before the window opens
-
-	Just documenting my experience in getting this to work :P
-	*/
-	m_World->BroadcastBlockEntity(GetPos());
 }
 
 
@@ -60,7 +51,6 @@ void cCommandBlockEntity::SetCommand(const AString & a_Cmd)
 
 void cCommandBlockEntity::SetLastOutput(const AString & a_LastOut)
 {
-	m_World->BroadcastBlockEntity(GetPos());
 	m_LastOutput = a_LastOut;
 }
 
@@ -180,7 +170,6 @@ void cCommandBlockEntity::Execute()
 		{
 			// Overwrite field
 			m_CmdBlock->SetLastOutput(cClientHandle::FormatChatPrefix(m_CmdBlock->GetWorld()->ShouldUseChatPrefixes(), "SUCCESS", cChatColor::Green, cChatColor::White) + a_Text);
-			m_CmdBlock->GetWorld()->BroadcastBlockEntity(m_CmdBlock->GetPos());
 		}
 	};
 
diff --git a/src/BlockEntities/MobHeadEntity.cpp b/src/BlockEntities/MobHeadEntity.cpp
index 14773a0f1..72d039e7e 100644
--- a/src/BlockEntities/MobHeadEntity.cpp
+++ b/src/BlockEntities/MobHeadEntity.cpp
@@ -33,7 +33,6 @@ void cMobHeadEntity::SetType(const eMobHeadType & a_Type)
 		m_OwnerUUID = cUUID{};
 	}
 	m_Type = a_Type;
-	m_World->BroadcastBlockEntity(GetPos());
 }
 
 
@@ -43,7 +42,6 @@ void cMobHeadEntity::SetType(const eMobHeadType & a_Type)
 void cMobHeadEntity::SetRotation(eMobHeadRotation a_Rotation)
 {
 	m_Rotation = a_Rotation;
-	m_World->BroadcastBlockEntity(GetPos());
 }
 
 
@@ -70,8 +68,6 @@ void cMobHeadEntity::SetOwner(const cPlayer & a_Owner)
 			break;
 		}
 	}
-
-	m_World->BroadcastBlockEntity(GetPos());
 }
 
 
@@ -89,7 +85,6 @@ void cMobHeadEntity::SetOwner(const cUUID & a_OwnerUUID, const AString & a_Owner
 	m_OwnerName = a_OwnerName;
 	m_OwnerTexture = a_OwnerTexture;
 	m_OwnerTextureSignature = a_OwnerTextureSignature;
-	m_World->BroadcastBlockEntity(GetPos());
 }
 
 
diff --git a/src/BlockEntities/MobSpawnerEntity.cpp b/src/BlockEntities/MobSpawnerEntity.cpp
index 78312edb1..8e5585b89 100644
--- a/src/BlockEntities/MobSpawnerEntity.cpp
+++ b/src/BlockEntities/MobSpawnerEntity.cpp
@@ -70,6 +70,7 @@ bool cMobSpawnerEntity::UsedBy(cPlayer * a_Player)
 		{
 			a_Player->GetInventory().RemoveOneEquippedItem();
 		}
+		m_World->BroadcastBlockEntity(GetPos());
 		FLOGD("Changed monster spawner at {0} to type {1}.", GetPos(), cMonster::MobTypeToString(MonsterType));
 		return true;
 	}
@@ -105,6 +106,7 @@ bool cMobSpawnerEntity::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
 	if (m_SpawnDelay <= 0)
 	{
 		SpawnEntity();
+		m_World->BroadcastBlockEntity(GetPos());
 		return true;
 	}
 	else
@@ -121,7 +123,6 @@ bool cMobSpawnerEntity::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
 void cMobSpawnerEntity::ResetTimer(void)
 {
 	m_SpawnDelay = GetRandomProvider().RandInt<short>(m_MinSpawnDelay, m_MaxSpawnDelay);
-	m_World->BroadcastBlockEntity(GetPos());
 }
 
 
diff --git a/src/BlockType.h b/src/BlockType.h
index f98d9db82..4814c5a68 100644
--- a/src/BlockType.h
+++ b/src/BlockType.h
@@ -1181,36 +1181,3 @@ extern AString ItemToFullString(const cItem & a_Item);
 extern cItem GetIniItemSet(cIniFile & a_IniFile, const char * a_Section, const char * a_Key, const char * a_Default);
 
 // tolua_end
-
-
-
-
-
-/** Base case for IsOneOf to handle empty template aguments. */
-template <class = void>
-bool IsOneOf(BLOCKTYPE a_BlockType)
-{
-	return false;
-}
-
-
-/** Returns true if a_BlockType is equal to any of the variadic template arguments.
-Some example usage:
-\code
-	IsOneOf<>(E_BLOCK_AIR)                           == false
-	IsOneOf<E_BLOCK_AIR>(E_BLOCK_DIRT)               == false
-	IsOneOf<E_BLOCK_AIR, E_BLOCK_DIRT>(E_BLOCK_DIRT) == true
-\endcode
-The implementation is ugly but it is equivalent to this C++17 fold expression:
-\code
-	((a_BlockType == Types) || ...)
-\endcode
-Just written to be valid without fold expressions or SFINAE. */
-template <BLOCKTYPE Head, BLOCKTYPE ... Tail>
-bool IsOneOf(BLOCKTYPE a_BlockType)
-{
-	return ((a_BlockType == Head) || (IsOneOf<Tail...>(a_BlockType)));
-}
-
-
-
diff --git a/src/Blocks/BlockBed.cpp b/src/Blocks/BlockBed.cpp
index 58ff720c8..4fd3eba54 100644
--- a/src/Blocks/BlockBed.cpp
+++ b/src/Blocks/BlockBed.cpp
@@ -158,12 +158,12 @@ bool cBlockBedHandler::OnUse(
 
 void cBlockBedHandler::OnPlacedByPlayer(cChunkInterface & a_ChunkInterface, cWorldInterface & a_WorldInterface, cPlayer & a_Player, const sSetBlock & a_BlockChange) const
 {
-	a_Player.GetWorld()->DoWithBedAt(a_BlockChange.GetX(), a_BlockChange.GetY(), a_BlockChange.GetZ(), [&](cBedEntity & a_Bed)
-		{
-			a_Bed.SetColor(a_Player.GetEquippedItem().m_ItemDamage);
-			return true;
-		}
-	);
+	a_Player.GetWorld()->DoWithBlockEntityAt(a_BlockChange.GetAbsolutePos(), [&a_Player](cBlockEntity & a_BlockEntity)
+	{
+		ASSERT(a_BlockEntity.GetBlockType() == E_BLOCK_BED);
+		static_cast<cBedEntity &>(a_BlockEntity).SetColor(a_Player.GetEquippedItem().m_ItemDamage);
+		return false;
+	});
 }
 
 
diff --git a/src/Blocks/BlockEnchantingTable.h b/src/Blocks/BlockEnchantingTable.h
index c921d8f27..33d7092e3 100644
--- a/src/Blocks/BlockEnchantingTable.h
+++ b/src/Blocks/BlockEnchantingTable.h
@@ -32,7 +32,7 @@ private:
 	) const override
 	{
 		AString WindowName = "Enchant";
-		a_WorldInterface.DoWithBlockEntityAt(a_BlockPos.x, a_BlockPos.y, a_BlockPos.z, [&WindowName](cBlockEntity & a_Entity)
+		a_WorldInterface.DoWithBlockEntityAt(a_BlockPos, [&WindowName](cBlockEntity & a_Entity)
 		{
 			if (a_Entity.GetBlockType() != E_BLOCK_ENCHANTMENT_TABLE)
 			{
@@ -46,7 +46,7 @@ private:
 				WindowName = CustomName;
 			}
 
-			return true;
+			return false;
 		});
 
 		cWindow * Window = new cEnchantingWindow(a_BlockPos, std::move(WindowName));
diff --git a/src/Blocks/BlockNoteBlock.h b/src/Blocks/BlockNoteBlock.h
index ac1ab7238..566b9bef5 100644
--- a/src/Blocks/BlockNoteBlock.h
+++ b/src/Blocks/BlockNoteBlock.h
@@ -26,18 +26,15 @@ private:
 		const Vector3i a_BlockPos
 	) const override
 	{
-		a_WorldInterface.DoWithBlockEntityAt(a_BlockPos.x, a_BlockPos.y, a_BlockPos.z, [](cBlockEntity & a_BlockEntity)
+		a_WorldInterface.DoWithBlockEntityAt(a_BlockPos, [](cBlockEntity & a_BlockEntity)
 		{
 			if (a_BlockEntity.GetBlockType() != E_BLOCK_NOTE_BLOCK)
 			{
 				return false;
 			}
 
-			auto & NoteEntity = static_cast<cNoteEntity &>(a_BlockEntity);
-
-			NoteEntity.MakeSound();
-
-			return true;
+			static_cast<cNoteEntity &>(a_BlockEntity).MakeSound();
+			return false;
 		});
 	}
 };
diff --git a/src/Blocks/WorldInterface.h b/src/Blocks/WorldInterface.h
index 2a39ffbc2..4ae3f33c9 100644
--- a/src/Blocks/WorldInterface.h
+++ b/src/Blocks/WorldInterface.h
@@ -4,13 +4,11 @@
 #include "../FunctionRef.h"
 #include "../Mobs/MonsterTypes.h"
 
-class cBedEntity;
 class cBlockEntity;
 class cBroadcastInterface;
 class cItems;
 class cPlayer;
 
-using cBedCallback         = cFunctionRef<bool(cBedEntity   &)>;
 using cBlockEntityCallback = cFunctionRef<bool(cBlockEntity &)>;
 using cPlayerListCallback  = cFunctionRef<bool(cPlayer      &)>;
 using cEntityCallback      = cFunctionRef<bool(cEntity      &)>;
@@ -32,10 +30,8 @@ public:
 
 	virtual void DoExplosionAt(double a_ExplosionSize, double a_BlockX, double a_BlockY, double a_BlockZ, bool a_CanCauseFire, eExplosionSource a_Source, void * a_SourceData) = 0;
 
-	virtual bool DoWithBedAt(int a_BlockX, int a_BlockY, int a_BlockZ, cBedCallback a_Callback) = 0;
-
 	/** Calls the callback for the block entity at the specified coords; returns false if there's no block entity at those coords, true if found */
-	virtual bool DoWithBlockEntityAt(int a_BlockX, int a_BlockY, int a_BlockZ, cBlockEntityCallback a_Callback) = 0;
+	virtual bool DoWithBlockEntityAt(Vector3i a_Position, cBlockEntityCallback a_Callback) = 0;
 
 	/** Spawns item pickups for each item in the list. May compress pickups if too many entities: */
 	virtual void SpawnItemPickups(const cItems & a_Pickups, double a_BlockX, double a_BlockY, double a_BlockZ, double a_FlyAwaySpeed = 1.0, bool IsPlayerCreated = false) = 0;
diff --git a/src/Chunk.cpp b/src/Chunk.cpp
index 161a42512..09b0f0f99 100644
--- a/src/Chunk.cpp
+++ b/src/Chunk.cpp
@@ -12,21 +12,6 @@
 #include "ClientHandle.h"
 #include "Server.h"
 #include "Defines.h"
-#include "BlockEntities/BeaconEntity.h"
-#include "BlockEntities/BedEntity.h"
-#include "BlockEntities/BrewingstandEntity.h"
-#include "BlockEntities/ChestEntity.h"
-#include "BlockEntities/CommandBlockEntity.h"
-#include "BlockEntities/DispenserEntity.h"
-#include "BlockEntities/DropperEntity.h"
-#include "BlockEntities/FlowerPotEntity.h"
-#include "BlockEntities/FurnaceEntity.h"
-#include "BlockEntities/HopperEntity.h"
-#include "BlockEntities/JukeboxEntity.h"
-#include "BlockEntities/MobHeadEntity.h"
-#include "BlockEntities/MobSpawnerEntity.h"
-#include "BlockEntities/NoteEntity.h"
-#include "BlockEntities/SignEntity.h"
 #include "Entities/Pickup.h"
 #include "Item.h"
 #include "Noise/Noise.h"
@@ -468,7 +453,6 @@ void cChunk::WriteBlockArea(cBlockArea & a_Area, int a_MinBlockX, int a_MinBlock
 			auto clone = be->Clone({posX, posY, posZ});
 			clone->SetWorld(m_World);
 			AddBlockEntity(std::move(clone));
-			m_World->BroadcastBlockEntity({posX, posY, posZ});
 		}
 	}
 }
@@ -789,28 +773,34 @@ void cChunk::MoveEntityToNewChunk(OwnedEntity a_Entity)
 
 void cChunk::BroadcastPendingBlockChanges(void)
 {
-	if (m_PendingSendBlocks.empty())
+	if (const auto PendingBlocksCount = m_PendingSendBlocks.size(); PendingBlocksCount >= 10240)
 	{
-		return;
-	}
-
-	if (m_PendingSendBlocks.size() >= 10240)
-	{
-		// Resend the full chunk
-		for (auto ClientHandle : m_LoadedByClient)
+		// Resend the full chunk:
+		for (const auto ClientHandle : m_LoadedByClient)
 		{
 			m_World->ForceSendChunkTo(m_PosX, m_PosZ, cChunkSender::Priority::Medium, ClientHandle);
 		}
 	}
-	else
+	else if (PendingBlocksCount != 0)
 	{
-		// Only send block changes
-		for (auto ClientHandle : m_LoadedByClient)
+		// Send block changes:
+		for (const auto ClientHandle : m_LoadedByClient)
 		{
 			ClientHandle->SendBlockChanges(m_PosX, m_PosZ, m_PendingSendBlocks);
 		}
 	}
+
+	// Send block entity changes:
+	for (const auto Entity : m_PendingSendBlockEntities)
+	{
+		for (const auto ClientHandle : m_LoadedByClient)
+		{
+			Entity->SendTo(*ClientHandle);
+		}
+	}
+
 	m_PendingSendBlocks.clear();
+	m_PendingSendBlockEntities.clear();
 }
 
 
@@ -823,12 +813,12 @@ void cChunk::CheckBlocks()
 	cBlockInServerPluginInterface PluginInterface(*m_World);
 
 	// Process a limited number of blocks since cBlockHandler::Check may queue more to tick
-	auto Count = m_ToTickBlocks.size();
+	auto Count = m_BlocksToCheck.size();
 
 	while (Count != 0)
 	{
-		const auto Pos = m_ToTickBlocks.front();
-		m_ToTickBlocks.pop();
+		const auto Pos = m_BlocksToCheck.front();
+		m_BlocksToCheck.pop();
 		Count--;
 
 		cBlockHandler::For(GetBlock(Pos)).Check(ChunkInterface, PluginInterface, Pos, *this);
@@ -1265,8 +1255,8 @@ void cChunk::SetBlock(Vector3i a_RelPos, BLOCKTYPE a_BlockType, NIBBLETYPE a_Blo
 {
 	FastSetBlock(a_RelPos, a_BlockType, a_BlockMeta);
 
-	// Tick this block's neighbors via cBlockHandler::Check:
-	m_ToTickBlocks.push(a_RelPos);
+	// Queue a check of this block's neighbors:
+	m_BlocksToCheck.push(a_RelPos);
 
 	// Wake up the simulators for this block:
 	GetWorld()->GetSimulatorManager()->WakeUp(*this, a_RelPos);
@@ -1370,22 +1360,26 @@ void cChunk::FastSetBlock(int a_RelX, int a_RelY, int a_RelZ, BLOCKTYPE a_BlockT
 
 void cChunk::SendBlockTo(int a_RelX, int a_RelY, int a_RelZ, cClientHandle * a_Client)
 {
+	const auto BlockEntity = GetBlockEntityRel({ a_RelX, a_RelY, a_RelZ });
 
 	if (a_Client == nullptr)
 	{
-		// Queue the block for all clients in the chunk (will be sent in Tick())
+		// Queue the block (entity) for all clients in the chunk (will be sent in BroadcastPendingBlockChanges()):
 		m_PendingSendBlocks.emplace_back(m_PosX, m_PosZ, a_RelX, a_RelY, a_RelZ, GetBlock(a_RelX, a_RelY, a_RelZ), GetMeta(a_RelX, a_RelY, a_RelZ));
+		if (BlockEntity != nullptr)
+		{
+			m_PendingSendBlockEntities.push_back(BlockEntity);
+		}
 		return;
 	}
 
-	Vector3i wp = PositionToWorldPosition(a_RelX, a_RelY, a_RelZ);
-	a_Client->SendBlockChange(wp.x, wp.y, wp.z, GetBlock(a_RelX, a_RelY, a_RelZ), GetMeta(a_RelX, a_RelY, a_RelZ));
+	const auto Position = PositionToWorldPosition(a_RelX, a_RelY, a_RelZ);
+	a_Client->SendBlockChange(Position.x, Position.y, Position.z, GetBlock(a_RelX, a_RelY, a_RelZ), GetMeta(a_RelX, a_RelY, a_RelZ));
 
 	// FS #268 - if a BlockEntity digging is cancelled by a plugin, the entire block entity must be re-sent to the client:
-	cBlockEntity * Block = GetBlockEntity(wp.x, wp.y, wp.z);
-	if (Block != nullptr)
+	if (BlockEntity != nullptr)
 	{
-		Block->SendTo(*a_Client);
+		BlockEntity->SendTo(*a_Client);
 	}
 }
 
@@ -1508,39 +1502,12 @@ void cChunk::SetAreaBiome(int a_MinRelX, int a_MaxRelX, int a_MinRelZ, int a_Max
 
 
 
-bool cChunk::SetSignLines(int a_PosX, int a_PosY, int a_PosZ, const AString & a_Line1, const AString & a_Line2, const AString & a_Line3, const AString & a_Line4)
-{
-	// Also sends update packets to all clients in the chunk
-	auto Entity = GetBlockEntity(a_PosX, a_PosY, a_PosZ);
-	if (Entity == nullptr)
-	{
-		return false;  // Not a block entity
-	}
-	if (
-		(Entity->GetBlockType() != E_BLOCK_WALLSIGN) &&
-		(Entity->GetBlockType() != E_BLOCK_SIGN_POST)
-	)
-	{
-		return false;  // Not a sign
-	}
-
-	MarkDirty();
-	auto Sign = static_cast<cSignEntity *>(Entity);
-	Sign->SetLines(a_Line1, a_Line2, a_Line3, a_Line4);
-	m_World->BroadcastBlockEntity({a_PosX, a_PosY, a_PosZ});
-	return true;
-}
-
-
-
-
-
 void cChunk::RemoveBlockEntity(cBlockEntity * a_BlockEntity)
 {
 	MarkDirty();
 	ASSERT(a_BlockEntity != nullptr);
-	auto idx = static_cast<size_t>(cChunkDef::MakeIndex(a_BlockEntity->GetRelX(), a_BlockEntity->GetPosY(), a_BlockEntity->GetRelZ()));
-	m_BlockEntities.erase(idx);
+	m_BlockEntities.erase(static_cast<size_t>(cChunkDef::MakeIndex(a_BlockEntity->GetRelX(), a_BlockEntity->GetPosY(), a_BlockEntity->GetRelZ())));
+	m_PendingSendBlockEntities.erase(std::remove(m_PendingSendBlockEntities.begin(), m_PendingSendBlockEntities.end(), a_BlockEntity), m_PendingSendBlockEntities.end());
 }
 
 
@@ -1745,125 +1712,19 @@ bool cChunk::DoWithEntityByID(UInt32 a_EntityID, cEntityCallback a_Callback, boo
 
 
 
-template <class tyEntity, BLOCKTYPE... tBlocktype>
-bool cChunk::GenericForEachBlockEntity(cFunctionRef<bool(tyEntity &)> a_Callback)
-{
-	// The blockentity list is locked by the parent chunkmap's CS
-	for (auto & KeyPair : m_BlockEntities)
-	{
-		cBlockEntity * Block = KeyPair.second.get();
-		if (
-			(sizeof...(tBlocktype) == 0) ||  // Let empty list mean all block entities
-			(IsOneOf<tBlocktype...>(Block->GetBlockType()))
-		)
-		{
-			if (a_Callback(*static_cast<tyEntity *>(Block)))
-			{
-				return false;
-			}
-		}
-	}  // for KeyPair - m_BlockEntitites[]
-	return true;
-}
-
-
-
-
-
 bool cChunk::ForEachBlockEntity(cBlockEntityCallback a_Callback)
-{
-	return GenericForEachBlockEntity<cBlockEntity>(a_Callback);
-}
-
-
-
-
-
-bool cChunk::ForEachBrewingstand(cBrewingstandCallback a_Callback)
-{
-	return GenericForEachBlockEntity<cBrewingstandEntity,
-		E_BLOCK_BREWING_STAND
-	>(a_Callback);
-}
-
-
-
-
-
-bool cChunk::ForEachChest(cChestCallback a_Callback)
-{
-	return GenericForEachBlockEntity<cChestEntity,
-		E_BLOCK_CHEST
-	>(a_Callback);
-}
-
-
-
-
-
-bool cChunk::ForEachDispenser(cDispenserCallback a_Callback)
-{
-	return GenericForEachBlockEntity<cDispenserEntity,
-		E_BLOCK_DISPENSER
-	>(a_Callback);
-}
-
-
-
-
-
-bool cChunk::ForEachDropper(cDropperCallback a_Callback)
-{
-	return GenericForEachBlockEntity<cDropperEntity,
-		E_BLOCK_DROPPER
-	>(a_Callback);
-}
-
-
-
-
-
-bool cChunk::ForEachDropSpenser(cDropSpenserCallback a_Callback)
-{
-	return GenericForEachBlockEntity<cDropSpenserEntity,
-		E_BLOCK_DISPENSER,
-		E_BLOCK_DROPPER
-	>(a_Callback);
-}
-
-
-
-
-
-bool cChunk::ForEachFurnace(cFurnaceCallback a_Callback)
-{
-	return GenericForEachBlockEntity<cFurnaceEntity,
-		E_BLOCK_FURNACE,
-		E_BLOCK_LIT_FURNACE
-	>(a_Callback);
-}
-
-
-
-
-
-template <class tyEntity, BLOCKTYPE... tBlocktype>
-bool cChunk::GenericDoWithBlockEntityAt(Vector3i a_Position, cFunctionRef<bool(tyEntity &)> a_Callback)
 {
 	// The blockentity list is locked by the parent chunkmap's CS
-	cBlockEntity * Block = GetBlockEntityRel(a_Position);
-	if (Block == nullptr)
+
+	for (auto & KeyPair : m_BlockEntities)
 	{
-		return false;  // No block entity here
+		if (a_Callback(*KeyPair.second))
+		{
+			return false;
+		}
 	}
-	if (
-		(sizeof...(tBlocktype) != 0) &&  // Let empty list mean all block entities
-		(!IsOneOf<tBlocktype...>(Block->GetBlockType()))
-	)
-	{
-		return false;  // Not any of the given tBlocktypes
-	}
-	return !a_Callback(*static_cast<tyEntity *>(Block));
+
+	return true;
 }
 
 
@@ -1871,182 +1732,19 @@ bool cChunk::GenericDoWithBlockEntityAt(Vector3i a_Position, cFunctionRef<bool(t
 
 
 bool cChunk::DoWithBlockEntityAt(Vector3i a_Position, cBlockEntityCallback a_Callback)
-{
-	return GenericDoWithBlockEntityAt<cBlockEntity>(a_Position, a_Callback);
-}
-
-
-
-
-
-bool cChunk::DoWithBeaconAt(Vector3i a_Position, cBeaconCallback a_Callback)
-{
-	return GenericDoWithBlockEntityAt<cBeaconEntity,
-		E_BLOCK_BEACON
-	>(a_Position, a_Callback);
-}
-
-
-
-
-
-bool cChunk::DoWithBedAt(Vector3i a_Position, cBedCallback a_Callback)
-{
-	return GenericDoWithBlockEntityAt<cBedEntity,
-		E_BLOCK_BED
-	>(a_Position, a_Callback);
-}
-
-
-
-
-
-bool cChunk::DoWithBrewingstandAt(Vector3i a_Position, cBrewingstandCallback a_Callback)
-{
-	return GenericDoWithBlockEntityAt<cBrewingstandEntity,
-		E_BLOCK_BREWING_STAND
-	>(a_Position, a_Callback);
-}
-
-
-
-
-
-bool cChunk::DoWithChestAt(Vector3i a_Position, cChestCallback a_Callback)
-{
-	return GenericDoWithBlockEntityAt<cChestEntity,
-		E_BLOCK_CHEST,
-		E_BLOCK_TRAPPED_CHEST
-	>(a_Position, a_Callback);
-}
-
-
-
-
-
-bool cChunk::DoWithDispenserAt(Vector3i a_Position, cDispenserCallback a_Callback)
-{
-	return GenericDoWithBlockEntityAt<cDispenserEntity,
-		E_BLOCK_DISPENSER
-	>(a_Position, a_Callback);
-}
-
-
-
-
-
-bool cChunk::DoWithDropperAt(Vector3i a_Position, cDropperCallback a_Callback)
-{
-	return GenericDoWithBlockEntityAt<cDropperEntity,
-		E_BLOCK_DROPPER
-	>(a_Position, a_Callback);
-}
-
-
-
-
-
-bool cChunk::DoWithDropSpenserAt(Vector3i a_Position, cDropSpenserCallback a_Callback)
-{
-	return GenericDoWithBlockEntityAt<cDropSpenserEntity,
-		E_BLOCK_DISPENSER,
-		E_BLOCK_DROPPER
-	>(a_Position, a_Callback);
-}
-
-
-
-
-
-bool cChunk::DoWithFurnaceAt(Vector3i a_Position, cFurnaceCallback a_Callback)
-{
-	return GenericDoWithBlockEntityAt<cFurnaceEntity,
-		E_BLOCK_FURNACE,
-		E_BLOCK_LIT_FURNACE
-	>(a_Position, a_Callback);
-}
-
-
-
-
-
-bool cChunk::DoWithHopperAt(Vector3i a_Position, cHopperCallback a_Callback)
-{
-	return GenericDoWithBlockEntityAt<cHopperEntity,
-		E_BLOCK_HOPPER
-	>(a_Position, a_Callback);
-}
-
-
-
-
-
-bool cChunk::DoWithNoteBlockAt(Vector3i a_Position, cNoteBlockCallback a_Callback)
-{
-	return GenericDoWithBlockEntityAt<cNoteEntity,
-		E_BLOCK_NOTE_BLOCK
-	>(a_Position, a_Callback);
-}
-
-
-
-
-
-bool cChunk::DoWithCommandBlockAt(Vector3i a_Position, cCommandBlockCallback a_Callback)
-{
-	return GenericDoWithBlockEntityAt<cCommandBlockEntity,
-		E_BLOCK_COMMAND_BLOCK
-	>(a_Position, a_Callback);
-}
-
-
-
-
-
-bool cChunk::DoWithMobHeadAt(Vector3i a_Position, cMobHeadCallback a_Callback)
-{
-	return GenericDoWithBlockEntityAt<cMobHeadEntity,
-		E_BLOCK_HEAD
-	>(a_Position, a_Callback);
-}
-
-
-
-
-
-bool cChunk::DoWithFlowerPotAt(Vector3i a_Position, cFlowerPotCallback a_Callback)
-{
-	return GenericDoWithBlockEntityAt<cFlowerPotEntity,
-		E_BLOCK_FLOWER_POT
-	>(a_Position, a_Callback);
-}
-
-
-
-
-
-bool cChunk::GetSignLines(Vector3i a_Position, AString & a_Line1, AString & a_Line2, AString & a_Line3, AString & a_Line4)
 {
 	// The blockentity list is locked by the parent chunkmap's CS
-	auto Entity = GetBlockEntity(a_Position);
-	if (Entity == nullptr)
+
+	const auto BlockEntity = GetBlockEntityRel(a_Position);
+	if (BlockEntity == nullptr)
 	{
-		return false;  // Not a block entity
-	}
-	if (
-		(Entity->GetBlockType() != E_BLOCK_WALLSIGN) &&
-		(Entity->GetBlockType() != E_BLOCK_SIGN_POST)
-	)
-	{
-		return false;  // Not a sign
+		return false;  // No block entity here
 	}
 
-	auto Sign = static_cast<cSignEntity *>(Entity);
-	a_Line1 = Sign->GetLine(0);
-	a_Line2 = Sign->GetLine(1);
-	a_Line3 = Sign->GetLine(2);
-	a_Line4 = Sign->GetLine(3);
-	return true;
+	const bool Result = a_Callback(*BlockEntity);
+	m_PendingSendBlockEntities.push_back(BlockEntity);
+	MarkDirty();
+	return Result;
 }
 
 
diff --git a/src/Chunk.h b/src/Chunk.h
index ce735df8e..c51794d24 100644
--- a/src/Chunk.h
+++ b/src/Chunk.h
@@ -2,7 +2,6 @@
 #pragma once
 
 #include "BlockEntities/BlockEntity.h"
-#include "Entities/Entity.h"
 #include "ChunkData.h"
 
 #include "Simulator/FireSimulator.h"
@@ -19,19 +18,8 @@ class cWorld;
 class cClientHandle;
 class cPlayer;
 class cChunkMap;
-class cBeaconEntity;
-class cBedEntity;
-class cBrewingstandEntity;
 class cBoundingBox;
-class cChestEntity;
 class cChunkDataCallback;
-class cCommandBlockEntity;
-class cDispenserEntity;
-class cFurnaceEntity;
-class cHopperEntity;
-class cNoteEntity;
-class cMobHeadEntity;
-class cFlowerPotEntity;
 class cBlockArea;
 class cBlockArea;
 class cFluidSimulatorData;
@@ -212,9 +200,6 @@ public:
 	Sends the chunk to all relevant clients. */
 	void SetAreaBiome(int a_MinRelX, int a_MaxRelX, int a_MinRelZ, int a_MaxRelZ, EMCSBiome a_Biome);
 
-	/** Sets the sign text. Returns true if successful. Also sends update packets to all clients in the chunk */
-	bool SetSignLines(int a_RelX, int a_RelY, int a_RelZ, const AString & a_Line1, const AString & a_Line2, const AString & a_Line3, const AString & a_Line4);
-
 	int GetHeight( int a_X, int a_Z) const;
 
 	/** Returns true if it is sunny at the specified location. This takes into account biomes. */
@@ -259,84 +244,12 @@ public:
 	/** Calls the callback if the entity with the specified ID is found, with the entity object as the callback param. Returns true if entity found. */
 	bool DoWithEntityByID(UInt32 a_EntityID, cEntityCallback a_Callback, bool & a_CallbackResult) const;  // Lua-accessible
 
-	/** Calls the callback for each tyEntity; returns true if all block entities processed, false if the callback aborted by returning true
-	tBlocktypes are all blocktypes convertible to tyEntity which are to be called. If no block type is given the callback is called for every block entity
-	Accessible only from within Chunk.cpp */
-	template <class tyEntity, BLOCKTYPE... tBlocktype>
-	bool GenericForEachBlockEntity(cFunctionRef<bool(tyEntity &)> a_Callback);
-
 	/** Calls the callback for each block entity; returns true if all block entities processed, false if the callback aborted by returning true */
 	bool ForEachBlockEntity(cBlockEntityCallback a_Callback);  // Lua-accessible
 
-	/** Calls the callback for each brewingstand; returns true if all brewingstands processed, false if the callback aborted by returning true */
-	bool ForEachBrewingstand(cBrewingstandCallback a_Callback);  // Lua-accessible
-
-	/** Calls the callback for each chest; returns true if all chests processed, false if the callback aborted by returning true */
-	bool ForEachChest(cChestCallback a_Callback);  // Lua-accessible
-
-	/** Calls the callback for each dispenser; returns true if all dispensers processed, false if the callback aborted by returning true */
-	bool ForEachDispenser(cDispenserCallback a_Callback);
-
-	/** Calls the callback for each dropper; returns true if all droppers processed, false if the callback aborted by returning true */
-	bool ForEachDropper(cDropperCallback a_Callback);
-
-	/** Calls the callback for each dropspenser; returns true if all dropspensers processed, false if the callback aborted by returning true */
-	bool ForEachDropSpenser(cDropSpenserCallback a_Callback);
-
-	/** Calls the callback for each furnace; returns true if all furnaces processed, false if the callback aborted by returning true */
-	bool ForEachFurnace(cFurnaceCallback a_Callback);  // Lua-accessible
-
-	/** Calls the callback for the tyEntity at the specified coords; returns false if there's no such block entity at those coords, true if found
-	tBlocktype is a list of the blocktypes to be called. If no BLOCKTYPE template arguments are given the callback is called for any block entity
-	Accessible only from within Chunk.cpp */
-	template <class tyEntity, BLOCKTYPE... tBlocktype>
-	bool GenericDoWithBlockEntityAt(Vector3i a_Position, cFunctionRef<bool(tyEntity &)> a_Callback);
-
-	/** Calls the callback for the block entity at the specified coords; returns false if there's no block entity at those coords, true if found */
+	/** Calls the callback for the block entity at the specified coords; returns false if there's no block entity at those coords, and whatever the callback returns if found. */
 	bool DoWithBlockEntityAt(Vector3i a_Position, cBlockEntityCallback a_Callback);  // Lua-acessible
 
-	/** Calls the callback for the beacon at the specified coords; returns false if there's no beacon at those coords, true if found */
-	bool DoWithBeaconAt(Vector3i a_Position, cBeaconCallback a_Callback);  // Lua-acessible
-
-	/** Calls the callback for the brewingstand at the specified coords; returns false if there's no brewingstand at those coords, true if found */
-	bool DoWithBrewingstandAt(Vector3i a_Position, cBrewingstandCallback a_Callback);  // Lua-acessible
-
-	/** Calls the callback for the bed at the specified coords; returns false if there's no bed at those coords, true if found */
-	bool DoWithBedAt(Vector3i a_Position, cBedCallback a_Callback);  // Lua-acessible
-
-	/** Calls the callback for the chest at the specified coords; returns false if there's no chest at those coords, true if found */
-	bool DoWithChestAt(Vector3i a_Position, cChestCallback a_Callback);  // Lua-acessible
-
-	/** Calls the callback for the dispenser at the specified coords; returns false if there's no dispenser at those coords or callback returns true, returns true if found */
-	bool DoWithDispenserAt(Vector3i a_Position, cDispenserCallback a_Callback);
-
-	/** Calls the callback for the dispenser at the specified coords; returns false if there's no dropper at those coords or callback returns true, returns true if found */
-	bool DoWithDropperAt(Vector3i a_Position, cDropperCallback a_Callback);
-
-	/** Calls the callback for the dispenser at the specified coords; returns false if there's no dropspenser at those coords or callback returns true, returns true if found */
-	bool DoWithDropSpenserAt(Vector3i a_Position, cDropSpenserCallback a_Callback);
-
-	/** Calls the callback for the furnace at the specified coords; returns false if there's no furnace at those coords or callback returns true, returns true if found */
-	bool DoWithFurnaceAt(Vector3i a_Position, cFurnaceCallback a_Callback);  // Lua-accessible
-
-	/** Calls the callback for the hopper at the specified coords; returns false if there's no hopper at those coords or callback returns true, returns true if found */
-	bool DoWithHopperAt(Vector3i a_Position, cHopperCallback a_Callback);  // Lua-accessible
-
-	/** Calls the callback for the noteblock at the specified coords; returns false if there's no noteblock at those coords or callback returns true, returns true if found */
-	bool DoWithNoteBlockAt(Vector3i a_Position, cNoteBlockCallback a_Callback);
-
-	/** Calls the callback for the command block at the specified coords; returns false if there's no command block at those coords or callback returns true, returns true if found */
-	bool DoWithCommandBlockAt(Vector3i a_Position, cCommandBlockCallback a_Callback);
-
-	/** Calls the callback for the mob head block at the specified coords; returns false if there's no mob head block at those coords or callback returns true, returns true if found */
-	bool DoWithMobHeadAt(Vector3i a_Position, cMobHeadCallback a_Callback);
-
-	/** Calls the callback for the flower pot at the specified coords; returns false if there's no flower pot at those coords or callback returns true, returns true if found */
-	bool DoWithFlowerPotAt(Vector3i a_Position, cFlowerPotCallback a_Callback);
-
-	/** Retrieves the test on the sign at the specified coords; returns false if there's no sign at those coords, true if found */
-	bool GetSignLines (Vector3i a_Position, AString & a_Line1, AString & a_Line2, AString & a_Line3, AString & a_Line4);  // Lua-accessible
-
 	/** Use block entity on coordinate.
 	returns true if the use was successful, return false to use the block as a "normal" block */
 	bool UseBlockEntity(cPlayer * a_Player, int a_X, int a_Y, int a_Z);  // [x, y, z] in world block coords
@@ -529,9 +442,9 @@ public:
 	as at least one requests is active the chunk will be ticked). */
 	void SetAlwaysTicked(bool a_AlwaysTicked);
 
-	cChunkClientHandles GetAllClients(void) const
+	const auto & GetAllClients(void) const
 	{
-		return cChunkClientHandles(m_LoadedByClient);
+		return m_LoadedByClient;
 	}
 
 	/** Converts the coord relative to this chunk into an absolute coord.
@@ -570,8 +483,19 @@ private:
 	bool m_IsDirty;        // True if the chunk has changed since it was last saved
 	bool m_IsSaving;       // True if the chunk is being saved
 
-	std::queue<Vector3i>  m_ToTickBlocks;
-	sSetBlockVector       m_PendingSendBlocks;  ///< Blocks that have changed and need to be sent to all clients
+	/** Blocks that have changed and need to be sent to all clients.
+	The protocol has a provision for coalescing block changes, and this is the buffer.
+	It will collect the block changes that occur in a tick, before being flushed in BroadcastPendingSendBlocks. */
+	sSetBlockVector m_PendingSendBlocks;
+
+	/** Block entities that have been touched and need to be sent to all clients.
+	Because block changes are buffered and we need to happen after them, this buffer exists too.
+	Pointers to block entities that were destroyed are guaranteed to be removed from this array by RemoveBlockEntity. */
+	std::vector<cBlockEntity *> m_PendingSendBlockEntities;
+
+	/** A queue of relative positions to call cBlockHandler::Check on.
+	Processed at the end of each tick by CheckBlocks. */
+	std::queue<Vector3i> m_BlocksToCheck;
 
 	// A critical section is not needed, because all chunk access is protected by its parent ChunkMap's csLayers
 	std::vector<cClientHandle *> m_LoadedByClient;
diff --git a/src/ChunkDef.h b/src/ChunkDef.h
index 9e97d53fc..673ae347a 100644
--- a/src/ChunkDef.h
+++ b/src/ChunkDef.h
@@ -98,33 +98,6 @@ public:
 
 
 
-/** Non-owning view of a chunk's client handles. */
-class cChunkClientHandles
-{
-public:
-	using const_iterator = std::vector<cClientHandle *>::const_iterator;
-	using iterator = const_iterator;
-
-	explicit cChunkClientHandles(const std::vector<cClientHandle *> & a_Container):
-		m_Begin(a_Container.cbegin()),
-		m_End(a_Container.cend())
-	{
-	}
-
-	const_iterator begin()  const { return m_Begin; }
-	const_iterator cbegin() const { return m_Begin; }
-
-	const_iterator end()  const { return m_End; }
-	const_iterator cend() const { return m_End; }
-
-private:
-	const_iterator m_Begin, m_End;
-};
-
-
-
-
-
 /** Constants used throughout the code, useful typedefs and utility functions */
 class cChunkDef
 {
diff --git a/src/ChunkMap.cpp b/src/ChunkMap.cpp
index dbd6f8cf3..7bdd1c649 100644
--- a/src/ChunkMap.cpp
+++ b/src/ChunkMap.cpp
@@ -11,7 +11,6 @@
 #include "Generating/Trees.h"  // used in cChunkMap::ReplaceTreeBlocks() for tree block discrimination
 #include "BlockArea.h"
 #include "Bindings/PluginManager.h"
-#include "Entities/TNTEntity.h"
 #include "Blocks/BlockHandler.h"
 #include "MobCensus.h"
 #include "MobSpawner.h"
@@ -20,7 +19,6 @@
 #include "Blocks/ChunkInterface.h"
 #include "Entities/Pickup.h"
 #include "DeadlockDetect.h"
-#include "BlockEntities/BlockEntity.h"
 
 
 
@@ -840,8 +838,8 @@ void cChunkMap::CompareChunkClients(int a_ChunkX1, int a_ChunkZ1, int a_ChunkX2,
 
 void cChunkMap::CompareChunkClients(cChunk * a_Chunk1, cChunk * a_Chunk2, cClientDiffCallback & a_Callback)
 {
-	auto Clients1 = a_Chunk1->GetAllClients();
-	auto Clients2 = a_Chunk2->GetAllClients();
+	const auto & Clients1 = a_Chunk1->GetAllClients();
+	const auto & Clients2 = a_Chunk2->GetAllClients();
 
 	// Find "removed" clients:
 	for (auto * Client : Clients1)
@@ -1074,360 +1072,17 @@ bool cChunkMap::ForEachBlockEntityInChunk(int a_ChunkX, int a_ChunkZ, cBlockEnti
 
 
 
-bool cChunkMap::ForEachBrewingstandInChunk(int a_ChunkX, int a_ChunkZ, cBrewingstandCallback a_Callback)
+bool cChunkMap::DoWithBlockEntityAt(const Vector3i a_Position, cBlockEntityCallback a_Callback)
 {
+	const auto ChunkPosition = cChunkDef::BlockToChunk(a_Position);
+	const auto Relative = cChunkDef::AbsoluteToRelative(a_Position, ChunkPosition);
 	cCSLock Lock(m_CSChunks);
-	const auto Chunk = FindChunk(a_ChunkX, a_ChunkZ);
+	const auto Chunk = FindChunk(ChunkPosition.m_ChunkX, ChunkPosition.m_ChunkZ);
 	if ((Chunk == nullptr) || !Chunk->IsValid())
 	{
 		return false;
 	}
-	return Chunk->ForEachBrewingstand(a_Callback);
-}
-
-
-
-
-
-bool cChunkMap::ForEachChestInChunk(int a_ChunkX, int a_ChunkZ, cChestCallback a_Callback)
-{
-	cCSLock Lock(m_CSChunks);
-	const auto Chunk = FindChunk(a_ChunkX, a_ChunkZ);
-	if ((Chunk == nullptr) || !Chunk->IsValid())
-	{
-		return false;
-	}
-	return Chunk->ForEachChest(a_Callback);
-}
-
-
-
-
-
-bool cChunkMap::ForEachDispenserInChunk(int a_ChunkX, int a_ChunkZ, cDispenserCallback a_Callback)
-{
-	cCSLock Lock(m_CSChunks);
-	const auto Chunk = FindChunk(a_ChunkX, a_ChunkZ);
-	if ((Chunk == nullptr) || !Chunk->IsValid())
-	{
-		return false;
-	}
-	return Chunk->ForEachDispenser(a_Callback);
-}
-
-
-
-
-
-bool cChunkMap::ForEachDropperInChunk(int a_ChunkX, int a_ChunkZ, cDropperCallback a_Callback)
-{
-	cCSLock Lock(m_CSChunks);
-	const auto Chunk = FindChunk(a_ChunkX, a_ChunkZ);
-	if ((Chunk == nullptr) || !Chunk->IsValid())
-	{
-		return false;
-	}
-	return Chunk->ForEachDropper(a_Callback);
-}
-
-
-
-
-
-bool cChunkMap::ForEachDropSpenserInChunk(int a_ChunkX, int a_ChunkZ, cDropSpenserCallback a_Callback)
-{
-	cCSLock Lock(m_CSChunks);
-	const auto Chunk = FindChunk(a_ChunkX, a_ChunkZ);
-	if ((Chunk == nullptr) || !Chunk->IsValid())
-	{
-		return false;
-	}
-	return Chunk->ForEachDropSpenser(a_Callback);
-}
-
-
-
-
-
-bool cChunkMap::ForEachFurnaceInChunk(int a_ChunkX, int a_ChunkZ, cFurnaceCallback a_Callback)
-{
-	cCSLock Lock(m_CSChunks);
-	const auto Chunk = FindChunk(a_ChunkX, a_ChunkZ);
-	if ((Chunk == nullptr) || !Chunk->IsValid())
-	{
-		return false;
-	}
-	return Chunk->ForEachFurnace(a_Callback);
-}
-
-
-
-
-
-bool cChunkMap::DoWithBlockEntityAt(int a_BlockX, int a_BlockY, int a_BlockZ, cBlockEntityCallback a_Callback)
-{
-	int ChunkX, ChunkZ;
-	int BlockX = a_BlockX, BlockY = a_BlockY, BlockZ = a_BlockZ;
-	cChunkDef::AbsoluteToRelative(BlockX, BlockY, BlockZ, ChunkX, ChunkZ);
-	cCSLock Lock(m_CSChunks);
-	const auto Chunk = FindChunk(ChunkX, ChunkZ);
-	if ((Chunk == nullptr) || !Chunk->IsValid())
-	{
-		return false;
-	}
-	return Chunk->DoWithBlockEntityAt({ BlockX, BlockY, BlockZ }, a_Callback);
-}
-
-
-
-
-
-bool cChunkMap::DoWithBeaconAt(int a_BlockX, int a_BlockY, int a_BlockZ, cBeaconCallback a_Callback)
-{
-	int ChunkX, ChunkZ;
-	int BlockX = a_BlockX, BlockY = a_BlockY, BlockZ = a_BlockZ;
-	cChunkDef::AbsoluteToRelative(BlockX, BlockY, BlockZ, ChunkX, ChunkZ);
-	cCSLock Lock(m_CSChunks);
-	const auto Chunk = FindChunk(ChunkX, ChunkZ);
-	if ((Chunk == nullptr) || !Chunk->IsValid())
-	{
-		return false;
-	}
-	return Chunk->DoWithBeaconAt({ BlockX, BlockY, BlockZ }, a_Callback);
-}
-
-
-
-
-
-bool cChunkMap::DoWithBedAt(int a_BlockX, int a_BlockY, int a_BlockZ, cBedCallback a_Callback)
-{
-	int ChunkX, ChunkZ;
-	int BlockX = a_BlockX, BlockY = a_BlockY, BlockZ = a_BlockZ;
-	cChunkDef::AbsoluteToRelative(BlockX, BlockY, BlockZ, ChunkX, ChunkZ);
-	cCSLock Lock(m_CSChunks);
-	const auto Chunk = FindChunk(ChunkX, ChunkZ);
-	if ((Chunk == nullptr) || !Chunk->IsValid())
-	{
-		return false;
-	}
-	return Chunk->DoWithBedAt({ BlockX, BlockY, BlockZ }, a_Callback);
-}
-
-
-
-
-
-bool cChunkMap::DoWithBrewingstandAt(int a_BlockX, int a_BlockY, int a_BlockZ, cBrewingstandCallback a_Callback)
-{
-	int ChunkX, ChunkZ;
-	int BlockX = a_BlockX, BlockY = a_BlockY, BlockZ = a_BlockZ;
-	cChunkDef::AbsoluteToRelative(BlockX, BlockY, BlockZ, ChunkX, ChunkZ);
-	cCSLock Lock(m_CSChunks);
-	const auto Chunk = FindChunk(ChunkX, ChunkZ);
-	if ((Chunk == nullptr) || !Chunk->IsValid())
-	{
-		return false;
-	}
-	return Chunk->DoWithBrewingstandAt({ BlockX, BlockY, BlockZ }, a_Callback);
-}
-
-
-
-
-
-bool cChunkMap::DoWithChestAt(int a_BlockX, int a_BlockY, int a_BlockZ, cChestCallback a_Callback)
-{
-	int ChunkX, ChunkZ;
-	int BlockX = a_BlockX, BlockY = a_BlockY, BlockZ = a_BlockZ;
-	cChunkDef::AbsoluteToRelative(BlockX, BlockY, BlockZ, ChunkX, ChunkZ);
-	cCSLock Lock(m_CSChunks);
-	const auto Chunk = FindChunk(ChunkX, ChunkZ);
-	if ((Chunk == nullptr) || !Chunk->IsValid())
-	{
-		return false;
-	}
-	return Chunk->DoWithChestAt({ BlockX, BlockY, BlockZ }, a_Callback);
-}
-
-
-
-
-
-bool cChunkMap::DoWithDispenserAt(int a_BlockX, int a_BlockY, int a_BlockZ, cDispenserCallback a_Callback)
-{
-	int ChunkX, ChunkZ;
-	int BlockX = a_BlockX, BlockY = a_BlockY, BlockZ = a_BlockZ;
-	cChunkDef::AbsoluteToRelative(BlockX, BlockY, BlockZ, ChunkX, ChunkZ);
-	cCSLock Lock(m_CSChunks);
-	const auto Chunk = FindChunk(ChunkX, ChunkZ);
-	if ((Chunk == nullptr) || !Chunk->IsValid())
-	{
-		return false;
-	}
-	return Chunk->DoWithDispenserAt({ BlockX, BlockY, BlockZ }, a_Callback);
-}
-
-
-
-
-
-bool cChunkMap::DoWithDropperAt(int a_BlockX, int a_BlockY, int a_BlockZ, cDropperCallback a_Callback)
-{
-	int ChunkX, ChunkZ;
-	int BlockX = a_BlockX, BlockY = a_BlockY, BlockZ = a_BlockZ;
-	cChunkDef::AbsoluteToRelative(BlockX, BlockY, BlockZ, ChunkX, ChunkZ);
-	cCSLock Lock(m_CSChunks);
-	const auto Chunk = FindChunk(ChunkX, ChunkZ);
-	if ((Chunk == nullptr) || !Chunk->IsValid())
-	{
-		return false;
-	}
-	return Chunk->DoWithDropperAt({ BlockX, BlockY, BlockZ }, a_Callback);
-}
-
-
-
-
-
-bool cChunkMap::DoWithDropSpenserAt(int a_BlockX, int a_BlockY, int a_BlockZ, cDropSpenserCallback a_Callback)
-{
-	int ChunkX, ChunkZ;
-	int BlockX = a_BlockX, BlockY = a_BlockY, BlockZ = a_BlockZ;
-	cChunkDef::AbsoluteToRelative(BlockX, BlockY, BlockZ, ChunkX, ChunkZ);
-	cCSLock Lock(m_CSChunks);
-	const auto Chunk = FindChunk(ChunkX, ChunkZ);
-	if ((Chunk == nullptr) || !Chunk->IsValid())
-	{
-		return false;
-	}
-	return Chunk->DoWithDropSpenserAt({ BlockX, BlockY, BlockZ }, a_Callback);
-}
-
-
-
-
-
-bool cChunkMap::DoWithFurnaceAt(int a_BlockX, int a_BlockY, int a_BlockZ, cFurnaceCallback a_Callback)
-{
-	int ChunkX, ChunkZ;
-	int BlockX = a_BlockX, BlockY = a_BlockY, BlockZ = a_BlockZ;
-	cChunkDef::AbsoluteToRelative(BlockX, BlockY, BlockZ, ChunkX, ChunkZ);
-	cCSLock Lock(m_CSChunks);
-	const auto Chunk = FindChunk(ChunkX, ChunkZ);
-	if ((Chunk == nullptr) || !Chunk->IsValid())
-	{
-		return false;
-	}
-	return Chunk->DoWithFurnaceAt({ BlockX, BlockY, BlockZ }, a_Callback);
-}
-
-
-
-
-
-bool cChunkMap::DoWithHopperAt(int a_BlockX, int a_BlockY, int a_BlockZ, cHopperCallback a_Callback)
-{
-	int ChunkX, ChunkZ;
-	int BlockX = a_BlockX, BlockY = a_BlockY, BlockZ = a_BlockZ;
-	cChunkDef::AbsoluteToRelative(BlockX, BlockY, BlockZ, ChunkX, ChunkZ);
-	cCSLock Lock(m_CSChunks);
-	const auto Chunk = FindChunk(ChunkX, ChunkZ);
-	if ((Chunk == nullptr) || !Chunk->IsValid())
-	{
-		return false;
-	}
-	return Chunk->DoWithHopperAt({ BlockX, BlockY, BlockZ }, a_Callback);
-}
-
-
-
-
-
-bool cChunkMap::DoWithNoteBlockAt(int a_BlockX, int a_BlockY, int a_BlockZ, cNoteBlockCallback a_Callback)
-{
-	int ChunkX, ChunkZ;
-	int BlockX = a_BlockX, BlockY = a_BlockY, BlockZ = a_BlockZ;
-	cChunkDef::AbsoluteToRelative(BlockX, BlockY, BlockZ, ChunkX, ChunkZ);
-	cCSLock Lock(m_CSChunks);
-	const auto Chunk = FindChunk(ChunkX, ChunkZ);
-	if ((Chunk == nullptr) || !Chunk->IsValid())
-	{
-		return false;
-	}
-	return Chunk->DoWithNoteBlockAt({ BlockX, BlockY, BlockZ }, a_Callback);
-}
-
-
-
-
-
-bool cChunkMap::DoWithCommandBlockAt(int a_BlockX, int a_BlockY, int a_BlockZ, cCommandBlockCallback a_Callback)
-{
-	int ChunkX, ChunkZ;
-	int BlockX = a_BlockX, BlockY = a_BlockY, BlockZ = a_BlockZ;
-	cChunkDef::AbsoluteToRelative(BlockX, BlockY, BlockZ, ChunkX, ChunkZ);
-	cCSLock Lock(m_CSChunks);
-	const auto Chunk = FindChunk(ChunkX, ChunkZ);
-	if ((Chunk == nullptr) || !Chunk->IsValid())
-	{
-		return false;
-	}
-	return Chunk->DoWithCommandBlockAt({ BlockX, BlockY, BlockZ }, a_Callback);
-}
-
-
-
-
-
-bool cChunkMap::DoWithMobHeadAt(int a_BlockX, int a_BlockY, int a_BlockZ, cMobHeadCallback a_Callback)
-{
-	int ChunkX, ChunkZ;
-	int BlockX = a_BlockX, BlockY = a_BlockY, BlockZ = a_BlockZ;
-	cChunkDef::AbsoluteToRelative(BlockX, BlockY, BlockZ, ChunkX, ChunkZ);
-	cCSLock Lock(m_CSChunks);
-	const auto Chunk = FindChunk(ChunkX, ChunkZ);
-	if ((Chunk == nullptr) || !Chunk->IsValid())
-	{
-		return false;
-	}
-	return Chunk->DoWithMobHeadAt({ BlockX, BlockY, BlockZ }, a_Callback);
-}
-
-
-
-
-
-bool cChunkMap::DoWithFlowerPotAt(int a_BlockX, int a_BlockY, int a_BlockZ, cFlowerPotCallback a_Callback)
-{
-	int ChunkX, ChunkZ;
-	int BlockX = a_BlockX, BlockY = a_BlockY, BlockZ = a_BlockZ;
-	cChunkDef::AbsoluteToRelative(BlockX, BlockY, BlockZ, ChunkX, ChunkZ);
-	cCSLock Lock(m_CSChunks);
-	const auto Chunk = FindChunk(ChunkX, ChunkZ);
-	if ((Chunk == nullptr) || !Chunk->IsValid())
-	{
-		return false;
-	}
-	return Chunk->DoWithFlowerPotAt({ BlockX, BlockY, BlockZ }, a_Callback);
-}
-
-
-
-
-
-bool cChunkMap::GetSignLines(int a_BlockX, int a_BlockY, int a_BlockZ, AString & a_Line1, AString & a_Line2, AString & a_Line3, AString & a_Line4)
-{
-	int ChunkX, ChunkZ;
-	int BlockX = a_BlockX, BlockY = a_BlockY, BlockZ = a_BlockZ;
-	cChunkDef::AbsoluteToRelative(BlockX, BlockY, BlockZ, ChunkX, ChunkZ);
-	cCSLock Lock(m_CSChunks);
-	const auto Chunk = FindChunk(ChunkX, ChunkZ);
-	if ((Chunk == nullptr) || !Chunk->IsValid())
-	{
-		return false;
-	}
-	return Chunk->GetSignLines({ BlockX, BlockY, BlockZ }, a_Line1, a_Line2, a_Line3, a_Line4);
+	return Chunk->DoWithBlockEntityAt(Relative, a_Callback);
 }
 
 
@@ -1479,23 +1134,6 @@ void cChunkMap::ChunkLoadFailed(int a_ChunkX, int a_ChunkZ)
 
 
 
-bool cChunkMap::SetSignLines(int a_BlockX, int a_BlockY, int a_BlockZ, const AString & a_Line1, const AString & a_Line2, const AString & a_Line3, const AString & a_Line4)
-{
-	cCSLock Lock(m_CSChunks);
-	int ChunkX, ChunkZ;
-	cChunkDef::BlockToChunk(a_BlockX, a_BlockZ, ChunkX, ChunkZ);
-	const auto Chunk = FindChunk(ChunkX, ChunkZ);
-	if ((Chunk == nullptr) || !Chunk->IsValid())
-	{
-		return false;
-	}
-	return Chunk->SetSignLines(a_BlockX, a_BlockY, a_BlockZ, a_Line1, a_Line2, a_Line3, a_Line4);
-}
-
-
-
-
-
 void cChunkMap::MarkChunkRegenerating(int a_ChunkX, int a_ChunkZ)
 {
 	cCSLock Lock(m_CSChunks);
diff --git a/src/ChunkMap.h b/src/ChunkMap.h
index 99a1d764d..d0aeb867c 100644
--- a/src/ChunkMap.h
+++ b/src/ChunkMap.h
@@ -18,19 +18,6 @@ class cItems;
 class cChunkStay;
 class cChunk;
 class cPlayer;
-class cBeaconEntity;
-class cBedEntity;
-class cBrewingstandEntity;
-class cChestEntity;
-class cDispenserEntity;
-class cDropperEntity;
-class cDropSpenserEntity;
-class cFurnaceEntity;
-class cHopperEntity;
-class cNoteEntity;
-class cCommandBlockEntity;
-class cMobHeadEntity;
-class cFlowerPotEntity;
 class cBlockArea;
 class cMobCensus;
 class cMobSpawner;
@@ -40,22 +27,9 @@ class cDeadlockDetect;
 struct SetChunkData;
 
 typedef std::list<cClientHandle *> cClientHandleList;
-using cEntityCallback       = cFunctionRef<bool(cEntity             &)>;
-using cBeaconCallback       = cFunctionRef<bool(cBeaconEntity       &)>;
-using cBedCallback          = cFunctionRef<bool(cBedEntity          &)>;
-using cBlockEntityCallback  = cFunctionRef<bool(cBlockEntity        &)>;
-using cBrewingstandCallback = cFunctionRef<bool(cBrewingstandEntity &)>;
-using cChestCallback        = cFunctionRef<bool(cChestEntity        &)>;
-using cChunkCallback        = cFunctionRef<bool(cChunk              &)>;
-using cDispenserCallback    = cFunctionRef<bool(cDispenserEntity    &)>;
-using cDropperCallback      = cFunctionRef<bool(cDropperEntity      &)>;
-using cDropSpenserCallback  = cFunctionRef<bool(cDropSpenserEntity  &)>;
-using cFurnaceCallback      = cFunctionRef<bool(cFurnaceEntity      &)>;
-using cHopperCallback       = cFunctionRef<bool(cHopperEntity       &)>;
-using cNoteBlockCallback    = cFunctionRef<bool(cNoteEntity         &)>;
-using cCommandBlockCallback = cFunctionRef<bool(cCommandBlockEntity &)>;
-using cMobHeadCallback      = cFunctionRef<bool(cMobHeadEntity      &)>;
-using cFlowerPotCallback    = cFunctionRef<bool(cFlowerPotEntity    &)>;
+using cChunkCallback        = cFunctionRef<bool(cChunk       &)>;
+using cEntityCallback       = cFunctionRef<bool(cEntity      &)>;
+using cBlockEntityCallback  = cFunctionRef<bool(cBlockEntity &)>;
 
 
 
@@ -228,88 +202,9 @@ public:
 	Returns true if all block entities processed, false if the callback aborted by returning true. */
 	bool ForEachBlockEntityInChunk(int a_ChunkX, int a_ChunkZ, cBlockEntityCallback a_Callback);  // Lua-accessible
 
-	/** Calls the callback for brewingstand in the specified chunk.
-	Returns true if all brewingstands processed, false if the callback aborted by returning true. */
-	bool ForEachBrewingstandInChunk(int a_ChunkX, int a_ChunkZ, cBrewingstandCallback a_Callback);  // Lua-accessible
-
-	/** Calls the callback for each chest in the specified chunk.
-	Returns true if all chests processed, false if the callback aborted by returning true. */
-	bool ForEachChestInChunk(int a_ChunkX, int a_ChunkZ, cChestCallback a_Callback);  // Lua-accessible
-
-	/** Calls the callback for each dispenser in the specified chunk.
-	Returns true if all dispensers processed, false if the callback aborted by returning true. */
-	bool ForEachDispenserInChunk(int a_ChunkX, int a_ChunkZ, cDispenserCallback a_Callback);
-
-	/** Calls the callback for each dropper in the specified chunk.
-	Returns true if all droppers processed, false if the callback aborted by returning true. */
-	bool ForEachDropperInChunk(int a_ChunkX, int a_ChunkZ, cDropperCallback a_Callback);
-
-	/** Calls the callback for each dropspenser in the specified chunk.
-	Returns true if all dropspensers processed, false if the callback aborted by returning true. */
-	bool ForEachDropSpenserInChunk(int a_ChunkX, int a_ChunkZ, cDropSpenserCallback a_Callback);
-
-	/** Calls the callback for each furnace in the specified chunk.
-	Returns true if all furnaces processed, false if the callback aborted by returning true. */
-	bool ForEachFurnaceInChunk(int a_ChunkX, int a_ChunkZ, cFurnaceCallback a_Callback);  // Lua-accessible
-
 	/** Calls the callback for the block entity at the specified coords.
-	Returns false if there's no block entity at those coords, true if found. */
-	bool DoWithBlockEntityAt(int a_BlockX, int a_BlockY, int a_BlockZ, cBlockEntityCallback a_Callback);  // Lua-acessible
-
-	/** Calls the callback for the beacon at the specified coords.
-	Returns false if there's no beacon at those coords, true if found. */
-	bool DoWithBeaconAt(int a_BlockX, int a_BlockY, int a_BlockZ, cBeaconCallback a_Callback);  // Lua-acessible
-
-	/** Calls the callback for the bed at the specified coords.
-	Returns false if there's no bed at those coords, true if found. */
-	bool DoWithBedAt(int a_BlockX, int a_BlockY, int a_BlockZ, cBedCallback a_Callback);  // Lua-acessible
-
-	/** Calls the callback for the brewingstand at the specified coords; returns false if there's no brewingstand at those coords, true if found */
-	bool DoWithBrewingstandAt(int a_BlockX, int a_BlockY, int a_BlockZ, cBrewingstandCallback a_Callback);  // Lua-acessible
-
-	/** Calls the callback for the chest at the specified coords.
-	Returns false if there's no chest at those coords, true if found. */
-	bool DoWithChestAt(int a_BlockX, int a_BlockY, int a_BlockZ, cChestCallback a_Callback);  // Lua-acessible
-
-	/** Calls the callback for the dispenser at the specified coords.
-	Returns false if there's no dispenser at those coords or callback returns true, returns true if found. */
-	bool DoWithDispenserAt(int a_BlockX, int a_BlockY, int a_BlockZ, cDispenserCallback a_Callback);  // Lua-accessible
-
-	/** Calls the callback for the dropper at the specified coords.
-	Returns false if there's no dropper at those coords or callback returns true, returns true if found. */
-	bool DoWithDropperAt(int a_BlockX, int a_BlockY, int a_BlockZ, cDropperCallback a_Callback);  // Lua-accessible
-
-	/** Calls the callback for the dropspenser at the specified coords.
-	Returns false if there's no dropspenser at those coords or callback returns true, returns true if found. */
-	bool DoWithDropSpenserAt(int a_BlockX, int a_BlockY, int a_BlockZ, cDropSpenserCallback a_Callback);  // Lua-accessible
-
-	/** Calls the callback for the furnace at the specified coords.
-	Returns false if there's no furnace at those coords or callback returns true, returns true if found. */
-	bool DoWithFurnaceAt(int a_BlockX, int a_BlockY, int a_BlockZ, cFurnaceCallback a_Callback);  // Lua-accessible
-
-	/** Calls the callback for the hopper at the specified coords.
-	Returns false if there's no hopper at those coords or callback returns true, returns true if found. */
-	bool DoWithHopperAt(int a_BlockX, int a_BlockY, int a_BlockZ, cHopperCallback a_Callback);  // Lua-accessible
-
-	/** Calls the callback for the noteblock at the specified coords.
-	Returns false if there's no noteblock at those coords or callback returns true, returns true if found. */
-	bool DoWithNoteBlockAt(int a_BlockX, int a_BlockY, int a_BlockZ, cNoteBlockCallback a_Callback);  // Lua-accessible
-
-	/** Calls the callback for the command block at the specified coords.
-	Returns false if there's no command block at those coords or callback returns true, returns true if found. */
-	bool DoWithCommandBlockAt(int a_BlockX, int a_BlockY, int a_BlockZ, cCommandBlockCallback a_Callback);  // Lua-accessible
-
-	/** Calls the callback for the mob head block at the specified coords.
-	Returns false if there's no mob head block at those coords or callback returns true, returns true if found. */
-	bool DoWithMobHeadAt(int a_BlockX, int a_BlockY, int a_BlockZ, cMobHeadCallback a_Callback);  // Lua-accessible
-
-	/** Calls the callback for the flower pot at the specified coords.
-	Returns false if there's no flower pot at those coords or callback returns true, returns true if found. */
-	bool DoWithFlowerPotAt(int a_BlockX, int a_BlockY, int a_BlockZ, cFlowerPotCallback a_Callback);  // Lua-accessible
-
-	/** Retrieves the test on the sign at the specified coords.
-	Returns false if there's no sign at those coords, true if found. */
-	bool GetSignLines (int a_BlockX, int a_BlockY, int a_BlockZ, AString & a_Line1, AString & a_Line2, AString & a_Line3, AString & a_Line4);  // Lua-accessible
+	Returns false if there's no block entity at those coords, and whatever the callback returns if found. */
+	bool DoWithBlockEntityAt(Vector3i a_Position, cBlockEntityCallback a_Callback);  // Lua-acessible
 
 	/** Queues the chunk for preparing - making sure that it's generated and lit.
 	The specified chunk is queued to be loaded or generated, and lit if needed.
@@ -324,9 +219,6 @@ public:
 	/** Marks the chunk as failed-to-load */
 	void ChunkLoadFailed(int a_ChunkX, int a_ChunkZ);
 
-	/** Sets the sign text. Returns true if sign text changed. */
-	bool SetSignLines(int a_BlockX, int a_BlockY, int a_BlockZ, const AString & a_Line1, const AString & a_Line2, const AString & a_Line3, const AString & a_Line4);
-
 	/** Marks the chunk as being regenerated - all its clients want that chunk again (used by cWorld::RegenerateChunk()) */
 	void MarkChunkRegenerating(int a_ChunkX, int a_ChunkZ);
 
diff --git a/src/ChunkSender.cpp b/src/ChunkSender.cpp
index 2ab9a5a4d..1fa74977f 100644
--- a/src/ChunkSender.cpp
+++ b/src/ChunkSender.cpp
@@ -121,7 +121,7 @@ void cChunkSender::QueueSendChunkTo(int a_ChunkX, int a_ChunkZ, Priority a_Prior
 
 
 
-void cChunkSender::QueueSendChunkTo(int a_ChunkX, int a_ChunkZ, Priority a_Priority, cChunkClientHandles a_Clients)
+void cChunkSender::QueueSendChunkTo(int a_ChunkX, int a_ChunkZ, Priority a_Priority, const std::vector<cClientHandle *> & a_Clients)
 {
 	{
 		cChunkCoords Chunk{a_ChunkX, a_ChunkZ};
diff --git a/src/ChunkSender.h b/src/ChunkSender.h
index 7fe47c360..75d261af5 100644
--- a/src/ChunkSender.h
+++ b/src/ChunkSender.h
@@ -72,7 +72,7 @@ public:
 
 	/** Queues a chunk to be sent to a specific client */
 	void QueueSendChunkTo(int a_ChunkX, int a_ChunkZ, Priority a_Priority, cClientHandle * a_Client);
-	void QueueSendChunkTo(int a_ChunkX, int a_ChunkZ, Priority a_Priority, cChunkClientHandles a_Client);
+	void QueueSendChunkTo(int a_ChunkX, int a_ChunkZ, Priority a_Priority, const std::vector<cClientHandle *> & a_Clients);
 
 protected:
 
diff --git a/src/Items/ItemBanner.h b/src/Items/ItemBanner.h
index 7b9e6d05b..3f082c5a5 100644
--- a/src/Items/ItemBanner.h
+++ b/src/Items/ItemBanner.h
@@ -203,13 +203,15 @@ public:
 		}
 
 		const auto BannerPos = AddFaceDirection(a_ClickedBlockPos, a_ClickedBlockFace);
-		return a_World.DoWithBlockEntityAt(BannerPos.x, BannerPos.y, BannerPos.z, [Color](cBlockEntity & a_BlockEntity)
+		a_World.DoWithBlockEntityAt(BannerPos, [Color](cBlockEntity & a_BlockEntity)
 		{
 			ASSERT((a_BlockEntity.GetBlockType() == E_BLOCK_STANDING_BANNER) || (a_BlockEntity.GetBlockType() == E_BLOCK_WALL_BANNER));
 
 			auto & Banner = static_cast<cBannerEntity &>(a_BlockEntity);
 			Banner.SetBaseColor(Color);
-			return true;
+			return false;
 		});
+
+		return true;
 	}
 };
diff --git a/src/Items/ItemEnchantingTable.h b/src/Items/ItemEnchantingTable.h
index c8eb42cac..12835cb4a 100644
--- a/src/Items/ItemEnchantingTable.h
+++ b/src/Items/ItemEnchantingTable.h
@@ -46,16 +46,12 @@ private:
 		}
 
 		const auto PlacePos = AddFaceDirection(a_ClickedBlockPos, a_ClickedBlockFace);
-		a_World.DoWithBlockEntityAt(PlacePos.x, PlacePos.y, PlacePos.z, [&a_EquippedItem](cBlockEntity & a_Entity)
+		a_World.DoWithBlockEntityAt(PlacePos, [&a_EquippedItem](cBlockEntity & a_Entity)
 		{
-			if (a_Entity.GetBlockType() != E_BLOCK_ENCHANTMENT_TABLE)
-			{
-				return true;
-			}
+			ASSERT(a_Entity.GetBlockType() == E_BLOCK_ENCHANTMENT_TABLE);
 
-			auto & EnchantingTable = static_cast<cEnchantingTableEntity &>(a_Entity);
-			EnchantingTable.SetCustomName(a_EquippedItem.m_CustomName);
-			return true;
+			static_cast<cEnchantingTableEntity &>(a_Entity).SetCustomName(a_EquippedItem.m_CustomName);
+			return false;
 		});
 
 		return true;
diff --git a/src/Items/ItemMobHead.h b/src/Items/ItemMobHead.h
index ef16f6c96..f1e963e1f 100644
--- a/src/Items/ItemMobHead.h
+++ b/src/Items/ItemMobHead.h
@@ -75,26 +75,22 @@ public:
 		auto BlockMeta = static_cast<NIBBLETYPE>(a_ClickedBlockFace);
 
 		// Use a callback to set the properties of the mob head block entity:
-		a_World.DoWithBlockEntityAt(a_PlacePos.x, a_PlacePos.y, a_PlacePos.z, [&](cBlockEntity & a_BlockEntity)
+		a_World.DoWithBlockEntityAt(a_PlacePos, [&](cBlockEntity & a_BlockEntity)
+		{
+			ASSERT(a_BlockEntity.GetBlockType() == E_BLOCK_HEAD);
+
+			auto & MobHeadEntity = static_cast<cMobHeadEntity &>(a_BlockEntity);
+
+			int Rotation = 0;
+			if (BlockMeta == 1)
 			{
-				if (a_BlockEntity.GetBlockType() != E_BLOCK_HEAD)
-				{
-					return false;
-				}
-				auto & MobHeadEntity = static_cast<cMobHeadEntity &>(a_BlockEntity);
-
-				int Rotation = 0;
-				if (BlockMeta == 1)
-				{
-					Rotation = FloorC(a_Player.GetYaw() * 16.0f / 360.0f + 0.5f) & 0x0f;
-				}
-
-				MobHeadEntity.SetType(HeadType);
-				MobHeadEntity.SetRotation(static_cast<eMobHeadRotation>(Rotation));
-				MobHeadEntity.GetWorld()->BroadcastBlockEntity(MobHeadEntity.GetPos());
-				return false;
+				Rotation = FloorC(a_Player.GetYaw() * 16.0f / 360.0f + 0.5f) & 0x0f;
 			}
-		);
+
+			MobHeadEntity.SetType(HeadType);
+			MobHeadEntity.SetRotation(static_cast<eMobHeadRotation>(Rotation));
+			return false;
+		});
 	}
 
 
@@ -243,23 +239,23 @@ public:
 				return false;
 			}
 
-			// If it is a mob head, check the correct head type using the block entity:
-			if (BlockType == E_BLOCK_HEAD)
-			{
-				bool IsWitherHead = false;
-				a_World.DoWithBlockEntityAt(BlockX, BlockY, BlockZ, [&](cBlockEntity & a_Entity)
-					{
-						ASSERT(a_Entity.GetBlockType() == E_BLOCK_HEAD);
-						auto & MobHead = static_cast<cMobHeadEntity &>(a_Entity);
-						IsWitherHead = (MobHead.GetType() == SKULL_TYPE_WITHER);
-						return true;
-					}
-				);
-				if (!IsWitherHead)
+			// If it is a mob head, check it's a wither skull using the block entity:
+			if (
+				(BlockType == E_BLOCK_HEAD) &&
+				!a_World.DoWithBlockEntityAt({ BlockX, BlockY, BlockZ }, [&](cBlockEntity & a_BlockEntity)
 				{
-					return false;
-				}
+					if (a_BlockEntity.GetBlockType() != E_BLOCK_HEAD)
+					{
+						return false;
+					}
+
+					return static_cast<cMobHeadEntity &>(a_BlockEntity).GetType() == SKULL_TYPE_WITHER;
+				})
+			)
+			{
+				return false;
 			}
+
 			// Matched, continue checking
 			AirBlocks.emplace_back(BlockX, BlockY, BlockZ, E_BLOCK_AIR, 0);
 		}  // for i - a_Image
diff --git a/src/Mobs/Path.cpp b/src/Mobs/Path.cpp
index 378051163..e9c4e6ca7 100644
--- a/src/Mobs/Path.cpp
+++ b/src/Mobs/Path.cpp
@@ -2,6 +2,7 @@
 #include "Globals.h"
 
 #include "Path.h"
+#include "BlockType.h"
 #include "../BlockInfo.h"
 #include "../Chunk.h"
 
diff --git a/src/Mobs/PathFinder.cpp b/src/Mobs/PathFinder.cpp
index 423c4a0c6..9e63b7362 100644
--- a/src/Mobs/PathFinder.cpp
+++ b/src/Mobs/PathFinder.cpp
@@ -1,5 +1,6 @@
 #include "Globals.h"
 #include "PathFinder.h"
+#include "BlockType.h"
 #include "../BlockInfo.h"
 #include "../Chunk.h"
 
diff --git a/src/Protocol/Protocol_1_11.cpp b/src/Protocol/Protocol_1_11.cpp
index 15197ad07..db4ed5502 100644
--- a/src/Protocol/Protocol_1_11.cpp
+++ b/src/Protocol/Protocol_1_11.cpp
@@ -30,13 +30,8 @@ Implements the 1.11 protocol classes:
 
 #include "../Mobs/IncludeAllMonsters.h"
 
-#include "../BlockEntities/BannerEntity.h"
-#include "../BlockEntities/BeaconEntity.h"
 #include "../BlockEntities/BedEntity.h"
-#include "../BlockEntities/CommandBlockEntity.h"
-#include "../BlockEntities/MobHeadEntity.h"
 #include "../BlockEntities/MobSpawnerEntity.h"
-#include "../BlockEntities/FlowerPotEntity.h"
 
 #include "../Root.h"
 #include "../Server.h"
diff --git a/src/Protocol/Protocol_1_9.cpp b/src/Protocol/Protocol_1_9.cpp
index 4ebf826bf..0669e6a1a 100644
--- a/src/Protocol/Protocol_1_9.cpp
+++ b/src/Protocol/Protocol_1_9.cpp
@@ -45,13 +45,7 @@ Implements the 1.9 protocol classes:
 #include "../Mobs/IncludeAllMonsters.h"
 #include "../UI/HorseWindow.h"
 
-#include "../BlockEntities/BannerEntity.h"
-#include "../BlockEntities/BeaconEntity.h"
-#include "../BlockEntities/CommandBlockEntity.h"
-#include "../BlockEntities/MobHeadEntity.h"
 #include "../BlockEntities/MobSpawnerEntity.h"
-#include "../BlockEntities/FlowerPotEntity.h"
-#include "../Bindings/PluginManager.h"
 
 
 
diff --git a/src/Simulator/IncrementalRedstoneSimulator/CommandBlockHandler.h b/src/Simulator/IncrementalRedstoneSimulator/CommandBlockHandler.h
index c216d12bc..0a5ffe136 100644
--- a/src/Simulator/IncrementalRedstoneSimulator/CommandBlockHandler.h
+++ b/src/Simulator/IncrementalRedstoneSimulator/CommandBlockHandler.h
@@ -31,9 +31,14 @@ namespace CommandBlockHandler
 			return;
 		}
 
-		a_Chunk.DoWithCommandBlockAt(a_Position, [](cCommandBlockEntity & a_CommandBlock)
+		a_Chunk.DoWithBlockEntityAt(a_Position, [](cBlockEntity & a_BlockEntity)
 		{
-			a_CommandBlock.Activate();
+			if (a_BlockEntity.GetBlockType() != E_BLOCK_COMMAND_BLOCK)
+			{
+				return false;
+			}
+
+			static_cast<cCommandBlockEntity &>(a_BlockEntity).Activate();
 			return false;
 		});
 	}
diff --git a/src/Simulator/IncrementalRedstoneSimulator/DropSpenserHandler.h b/src/Simulator/IncrementalRedstoneSimulator/DropSpenserHandler.h
index 309fa757a..4dd87e972 100644
--- a/src/Simulator/IncrementalRedstoneSimulator/DropSpenserHandler.h
+++ b/src/Simulator/IncrementalRedstoneSimulator/DropSpenserHandler.h
@@ -46,9 +46,14 @@ namespace DropSpenserHandler
 
 		if (IsPoweredNow && !WasPoweredPreviously)
 		{
-			a_Chunk.DoWithDropSpenserAt(a_Position, [](cDropSpenserEntity & a_DropSpenser)
+			a_Chunk.DoWithBlockEntityAt(a_Position, [](cBlockEntity & a_BlockEntity)
 			{
-				a_DropSpenser.Activate();
+				if ((a_BlockEntity.GetBlockType() != E_BLOCK_DISPENSER) && (a_BlockEntity.GetBlockType() != E_BLOCK_DROPPER))
+				{
+					return false;
+				}
+
+				static_cast<cDropSpenserEntity &>(a_BlockEntity).Activate();
 				return false;
 			});
 		}
diff --git a/src/Simulator/IncrementalRedstoneSimulator/HopperHandler.h b/src/Simulator/IncrementalRedstoneSimulator/HopperHandler.h
index 998a98677..379f7c7d8 100644
--- a/src/Simulator/IncrementalRedstoneSimulator/HopperHandler.h
+++ b/src/Simulator/IncrementalRedstoneSimulator/HopperHandler.h
@@ -30,9 +30,14 @@ namespace HopperHandler
 			return;
 		}
 
-		a_Chunk.DoWithHopperAt(a_Position, [Power](cHopperEntity & a_Hopper)
+		a_Chunk.DoWithBlockEntityAt(a_Position, [Power](cBlockEntity & a_BlockEntity)
 		{
-			a_Hopper.SetLocked(Power != 0);
+			if (a_BlockEntity.GetBlockType() != E_BLOCK_HOPPER)
+			{
+				return false;
+			}
+
+			static_cast<cHopperEntity &>(a_BlockEntity).SetLocked(Power != 0);
 			return false;
 		});
 	}
diff --git a/src/Simulator/IncrementalRedstoneSimulator/IncrementalRedstoneSimulator.h b/src/Simulator/IncrementalRedstoneSimulator/IncrementalRedstoneSimulator.h
index e8cd4bf5d..b13d1f6f5 100644
--- a/src/Simulator/IncrementalRedstoneSimulator/IncrementalRedstoneSimulator.h
+++ b/src/Simulator/IncrementalRedstoneSimulator/IncrementalRedstoneSimulator.h
@@ -1,6 +1,7 @@
 
 #pragma once
 
+#include "BlockType.h"
 #include "../RedstoneSimulator.h"
 #include "RedstoneSimulatorChunkData.h"
 
diff --git a/src/Simulator/IncrementalRedstoneSimulator/NoteBlockHandler.h b/src/Simulator/IncrementalRedstoneSimulator/NoteBlockHandler.h
index 6a2f66737..8bd639357 100644
--- a/src/Simulator/IncrementalRedstoneSimulator/NoteBlockHandler.h
+++ b/src/Simulator/IncrementalRedstoneSimulator/NoteBlockHandler.h
@@ -31,9 +31,14 @@ namespace NoteBlockHandler
 			return;
 		}
 
-		a_Chunk.DoWithNoteBlockAt(a_Position, [](cNoteEntity & a_NoteBlock)
+		a_Chunk.DoWithBlockEntityAt(a_Position, [](cBlockEntity & a_BlockEntity)
 		{
-			a_NoteBlock.MakeSound();
+			if (a_BlockEntity.GetBlockType() != E_BLOCK_NOTE_BLOCK)
+			{
+				return false;
+			}
+
+			static_cast<cNoteEntity &>(a_BlockEntity).MakeSound();
 			return false;
 		});
 	}
diff --git a/src/Simulator/IncrementalRedstoneSimulator/RedstoneHandler.cpp b/src/Simulator/IncrementalRedstoneSimulator/RedstoneHandler.cpp
index 63da4c0dc..ab6b35c0b 100644
--- a/src/Simulator/IncrementalRedstoneSimulator/RedstoneHandler.cpp
+++ b/src/Simulator/IncrementalRedstoneSimulator/RedstoneHandler.cpp
@@ -5,6 +5,7 @@
 #include "RedstoneDataHelper.h"
 #include "ForEachSourceCallback.h"
 
+#include "BlockType.h"
 #include "CommandBlockHandler.h"
 #include "DaylightSensorHandler.h"
 #include "DoorHandler.h"
diff --git a/src/Simulator/IncrementalRedstoneSimulator/TrappedChestHandler.h b/src/Simulator/IncrementalRedstoneSimulator/TrappedChestHandler.h
index 619401d80..145c5dc83 100644
--- a/src/Simulator/IncrementalRedstoneSimulator/TrappedChestHandler.h
+++ b/src/Simulator/IncrementalRedstoneSimulator/TrappedChestHandler.h
@@ -22,14 +22,16 @@ namespace TrappedChestHandler
 	static unsigned char GetPowerLevel(cChunk & a_Chunk, Vector3i a_Position)
 	{
 		int NumberOfPlayers = 0;
-		VERIFY(
-			!a_Chunk.DoWithChestAt(a_Position, [&](cChestEntity & a_Chest)
+		a_Chunk.DoWithBlockEntityAt(a_Position, [&NumberOfPlayers](cBlockEntity & a_BlockEntity)
+		{
+			if (a_BlockEntity.GetBlockType() != E_BLOCK_TRAPPED_CHEST)
 			{
-				ASSERT(a_Chest.GetBlockType() == E_BLOCK_TRAPPED_CHEST);
-				NumberOfPlayers = a_Chest.GetNumberOfPlayers();
-				return true;
-			})
-		);
+				return false;
+			}
+
+			NumberOfPlayers = static_cast<cChestEntity &>(a_BlockEntity).GetNumberOfPlayers();
+			return false;
+		});
 
 		return static_cast<unsigned char>(std::min(NumberOfPlayers, 15));
 	}
diff --git a/src/Simulator/VaporizeFluidSimulator.cpp b/src/Simulator/VaporizeFluidSimulator.cpp
index 82f71c03f..6256c8e78 100644
--- a/src/Simulator/VaporizeFluidSimulator.cpp
+++ b/src/Simulator/VaporizeFluidSimulator.cpp
@@ -5,6 +5,7 @@
 
 #include "Globals.h"
 #include "VaporizeFluidSimulator.h"
+#include "BlockType.h"
 #include "../OpaqueWorld.h"
 #include "../Chunk.h"
 #include "../Blocks/BroadcastInterface.h"
diff --git a/src/World.cpp b/src/World.cpp
index 84f8bb9c6..0a03110e2 100644
--- a/src/World.cpp
+++ b/src/World.cpp
@@ -29,7 +29,7 @@
 #include "Entities/TNTEntity.h"
 
 #include "BlockEntities/CommandBlockEntity.h"
-#include "BlockEntities/BeaconEntity.h"
+#include "BlockEntities/SignEntity.h"
 
 // Simulators:
 #include "Simulator/FloodyFluidSimulator.h"
@@ -1342,60 +1342,6 @@ bool cWorld::ForEachBlockEntityInChunk(int a_ChunkX, int a_ChunkZ, cBlockEntityC
 
 
 
-bool cWorld::ForEachBrewingstandInChunk(int a_ChunkX, int a_ChunkZ, cBrewingstandCallback a_Callback)
-{
-	return m_ChunkMap.ForEachBrewingstandInChunk(a_ChunkX, a_ChunkZ, a_Callback);
-}
-
-
-
-
-
-bool cWorld::ForEachChestInChunk(int a_ChunkX, int a_ChunkZ, cChestCallback a_Callback)
-{
-	return m_ChunkMap.ForEachChestInChunk(a_ChunkX, a_ChunkZ, a_Callback);
-}
-
-
-
-
-
-bool cWorld::ForEachDispenserInChunk(int a_ChunkX, int a_ChunkZ, cDispenserCallback a_Callback)
-{
-	return m_ChunkMap.ForEachDispenserInChunk(a_ChunkX, a_ChunkZ, a_Callback);
-}
-
-
-
-
-
-bool cWorld::ForEachDropperInChunk(int a_ChunkX, int a_ChunkZ, cDropperCallback a_Callback)
-{
-	return m_ChunkMap.ForEachDropperInChunk(a_ChunkX, a_ChunkZ, a_Callback);
-}
-
-
-
-
-
-bool cWorld::ForEachDropSpenserInChunk(int a_ChunkX, int a_ChunkZ, cDropSpenserCallback a_Callback)
-{
-	return m_ChunkMap.ForEachDropSpenserInChunk(a_ChunkX, a_ChunkZ, a_Callback);
-}
-
-
-
-
-
-bool cWorld::ForEachFurnaceInChunk(int a_ChunkX, int a_ChunkZ, cFurnaceCallback a_Callback)
-{
-	return m_ChunkMap.ForEachFurnaceInChunk(a_ChunkX, a_ChunkZ, a_Callback);
-}
-
-
-
-
-
 void cWorld::DoExplosionAt(double a_ExplosionSize, double a_BlockX, double a_BlockY, double a_BlockZ, bool a_CanCauseFire, eExplosionSource a_Source, void * a_SourceData)
 {
 	cLock Lock(*this);
@@ -1431,126 +1377,9 @@ void cWorld::DoExplosionAt(double a_ExplosionSize, double a_BlockX, double a_Blo
 
 
 
-bool cWorld::DoWithBlockEntityAt(int a_BlockX, int a_BlockY, int a_BlockZ, cBlockEntityCallback a_Callback)
+bool cWorld::DoWithBlockEntityAt(const Vector3i a_Position, cBlockEntityCallback a_Callback)
 {
-	return m_ChunkMap.DoWithBlockEntityAt(a_BlockX, a_BlockY, a_BlockZ, a_Callback);
-}
-
-
-
-
-
-bool cWorld::DoWithBeaconAt(int a_BlockX, int a_BlockY, int a_BlockZ, cBeaconCallback a_Callback)
-{
-	return m_ChunkMap.DoWithBeaconAt(a_BlockX, a_BlockY, a_BlockZ, a_Callback);
-}
-
-
-
-
-
-bool cWorld::DoWithBedAt(int a_BlockX, int a_BlockY, int a_BlockZ, cBedCallback a_Callback)
-{
-	return m_ChunkMap.DoWithBedAt(a_BlockX, a_BlockY, a_BlockZ, a_Callback);
-}
-
-
-
-
-
-bool cWorld::DoWithBrewingstandAt(int a_BlockX, int a_BlockY, int a_BlockZ, cBrewingstandCallback a_Callback)
-{
-	return m_ChunkMap.DoWithBrewingstandAt(a_BlockX, a_BlockY, a_BlockZ, a_Callback);
-}
-
-
-
-
-
-bool cWorld::DoWithChestAt(int a_BlockX, int a_BlockY, int a_BlockZ, cChestCallback a_Callback)
-{
-	return m_ChunkMap.DoWithChestAt(a_BlockX, a_BlockY, a_BlockZ, a_Callback);
-}
-
-
-
-
-
-bool cWorld::DoWithDispenserAt(int a_BlockX, int a_BlockY, int a_BlockZ, cDispenserCallback a_Callback)
-{
-	return m_ChunkMap.DoWithDispenserAt(a_BlockX, a_BlockY, a_BlockZ, a_Callback);
-}
-
-
-
-
-
-bool cWorld::DoWithDropperAt(int a_BlockX, int a_BlockY, int a_BlockZ, cDropperCallback a_Callback)
-{
-	return m_ChunkMap.DoWithDropperAt(a_BlockX, a_BlockY, a_BlockZ, a_Callback);
-}
-
-
-
-
-
-bool cWorld::DoWithDropSpenserAt(int a_BlockX, int a_BlockY, int a_BlockZ, cDropSpenserCallback a_Callback)
-{
-	return m_ChunkMap.DoWithDropSpenserAt(a_BlockX, a_BlockY, a_BlockZ, a_Callback);
-}
-
-
-
-
-
-bool cWorld::DoWithFurnaceAt(int a_BlockX, int a_BlockY, int a_BlockZ, cFurnaceCallback a_Callback)
-{
-	return m_ChunkMap.DoWithFurnaceAt(a_BlockX, a_BlockY, a_BlockZ, a_Callback);
-}
-
-
-
-
-
-bool cWorld::DoWithHopperAt(int a_BlockX, int a_BlockY, int a_BlockZ, cHopperCallback a_Callback)
-{
-	return m_ChunkMap.DoWithHopperAt(a_BlockX, a_BlockY, a_BlockZ, a_Callback);
-}
-
-
-
-
-
-bool cWorld::DoWithNoteBlockAt(int a_BlockX, int a_BlockY, int a_BlockZ, cNoteBlockCallback a_Callback)
-{
-	return m_ChunkMap.DoWithNoteBlockAt(a_BlockX, a_BlockY, a_BlockZ, a_Callback);
-}
-
-
-
-
-
-bool cWorld::DoWithCommandBlockAt(int a_BlockX, int a_BlockY, int a_BlockZ, cCommandBlockCallback a_Callback)
-{
-	return m_ChunkMap.DoWithCommandBlockAt(a_BlockX, a_BlockY, a_BlockZ, a_Callback);
-}
-
-
-
-
-
-bool cWorld::DoWithMobHeadAt(int a_BlockX, int a_BlockY, int a_BlockZ, cMobHeadCallback a_Callback)
-{
-	return m_ChunkMap.DoWithMobHeadAt(a_BlockX, a_BlockY, a_BlockZ, a_Callback);
-}
-
-
-
-
-
-bool cWorld::DoWithFlowerPotAt(int a_BlockX, int a_BlockY, int a_BlockZ, cFlowerPotCallback a_Callback)
-{
-	return m_ChunkMap.DoWithFlowerPotAt(a_BlockX, a_BlockY, a_BlockZ, a_Callback);
+	return m_ChunkMap.DoWithBlockEntityAt(a_Position, a_Callback);
 }
 
 
@@ -1559,7 +1388,20 @@ bool cWorld::DoWithFlowerPotAt(int a_BlockX, int a_BlockY, int a_BlockZ, cFlower
 
 bool cWorld::GetSignLines(int a_BlockX, int a_BlockY, int a_BlockZ, AString & a_Line1, AString & a_Line2, AString & a_Line3, AString & a_Line4)
 {
-	return m_ChunkMap.GetSignLines(a_BlockX, a_BlockY, a_BlockZ, a_Line1, a_Line2, a_Line3, a_Line4);
+	return DoWithBlockEntityAt({ a_BlockX, a_BlockY, a_BlockZ }, [&a_Line1, &a_Line2, &a_Line3, &a_Line4](cBlockEntity & a_BlockEntity)
+	{
+		if ((a_BlockEntity.GetBlockType() != E_BLOCK_WALLSIGN) && (a_BlockEntity.GetBlockType() != E_BLOCK_SIGN_POST))
+		{
+			return false;  // Not a sign
+		}
+
+		const auto & Sign = static_cast<cSignEntity &>(a_BlockEntity);
+		a_Line1 = Sign.GetLine(0);
+		a_Line2 = Sign.GetLine(1);
+		a_Line3 = Sign.GetLine(2);
+		a_Line4 = Sign.GetLine(3);
+		return true;
+	});
 }
 
 
@@ -2672,6 +2514,8 @@ void cWorld::ChunkLoadFailed(int a_ChunkX, int a_ChunkZ)
 
 bool cWorld::SetSignLines(int a_BlockX, int a_BlockY, int a_BlockZ, const AString & a_Line1, const AString & a_Line2, const AString & a_Line3, const AString & a_Line4, cPlayer * a_Player)
 {
+	// TODO: rvalue these strings
+
 	AString Line1(a_Line1);
 	AString Line2(a_Line2);
 	AString Line3(a_Line3);
@@ -2682,7 +2526,18 @@ bool cWorld::SetSignLines(int a_BlockX, int a_BlockY, int a_BlockZ, const AStrin
 		return false;
 	}
 
-	if (m_ChunkMap.SetSignLines(a_BlockX, a_BlockY, a_BlockZ, Line1, Line2, Line3, Line4))
+	if (
+		DoWithBlockEntityAt({ a_BlockX, a_BlockY, a_BlockZ }, [&Line1, &Line2, &Line3, &Line4](cBlockEntity & a_BlockEntity)
+		{
+			if ((a_BlockEntity.GetBlockType() != E_BLOCK_WALLSIGN) && (a_BlockEntity.GetBlockType() != E_BLOCK_SIGN_POST))
+			{
+				return false;  // Not a sign
+			}
+
+			static_cast<cSignEntity &>(a_BlockEntity).SetLines(Line1, Line2, Line3, Line4);
+			return true;
+		})
+	)
 	{
 		cRoot::Get()->GetPluginManager()->CallHookUpdatedSign(*this, a_BlockX, a_BlockY, a_BlockZ, Line1, Line2, Line3, Line4, a_Player);
 		return true;
@@ -2697,12 +2552,16 @@ bool cWorld::SetSignLines(int a_BlockX, int a_BlockY, int a_BlockZ, const AStrin
 
 bool cWorld::SetCommandBlockCommand(int a_BlockX, int a_BlockY, int a_BlockZ, const AString & a_Command)
 {
-	return DoWithCommandBlockAt(a_BlockX, a_BlockY, a_BlockZ, [&](cCommandBlockEntity & a_CommandBlock)
+	return DoWithBlockEntityAt({ a_BlockX, a_BlockY, a_BlockZ }, [&](cBlockEntity & a_BlockEntity)
+	{
+		if (a_BlockEntity.GetBlockType() != E_BLOCK_COMMAND_BLOCK)
 		{
-			a_CommandBlock.SetCommand(a_Command);
 			return false;
 		}
-	);
+
+		static_cast<cCommandBlockEntity &>(a_BlockEntity).SetCommand(a_Command);
+		return true;
+	});
 }
 
 
diff --git a/src/World.h b/src/World.h
index 0f0ade751..9bb1f8b6b 100644
--- a/src/World.h
+++ b/src/World.h
@@ -615,71 +615,14 @@ public:
 	/** Calls the callback for each block entity in the specified chunk; returns true if all block entities processed, false if the callback aborted by returning true */
 	bool ForEachBlockEntityInChunk(int a_ChunkX, int a_ChunkZ, cBlockEntityCallback a_Callback);  // Exported in ManualBindings.cpp
 
-	/** Calls the callback for each brewingstand in the specified chunk; returns true if all brewingstands processed, false if the callback aborted by returning true */
-	bool ForEachBrewingstandInChunk(int a_ChunkX, int a_ChunkZ, cBrewingstandCallback a_Callback);  // Exported in ManualBindings.cpp
-
-	/** Calls the callback for each chest in the specified chunk; returns true if all chests processed, false if the callback aborted by returning true */
-	bool ForEachChestInChunk(int a_ChunkX, int a_ChunkZ, cChestCallback a_Callback);  // Exported in ManualBindings.cpp
-
-	/** Calls the callback for each dispenser in the specified chunk; returns true if all dispensers processed, false if the callback aborted by returning true */
-	bool ForEachDispenserInChunk(int a_ChunkX, int a_ChunkZ, cDispenserCallback a_Callback);
-
-	/** Calls the callback for each dropper in the specified chunk; returns true if all droppers processed, false if the callback aborted by returning true */
-	bool ForEachDropperInChunk(int a_ChunkX, int a_ChunkZ, cDropperCallback a_Callback);
-
-	/** Calls the callback for each dropspenser in the specified chunk; returns true if all dropspensers processed, false if the callback aborted by returning true */
-	bool ForEachDropSpenserInChunk(int a_ChunkX, int a_ChunkZ, cDropSpenserCallback a_Callback);
-
-	/** Calls the callback for each furnace in the specified chunk; returns true if all furnaces processed, false if the callback aborted by returning true */
-	bool ForEachFurnaceInChunk(int a_ChunkX, int a_ChunkZ, cFurnaceCallback a_Callback);  // Exported in ManualBindings.cpp
-
 	/** Does an explosion with the specified strength at the specified coordinates.
 	Executes the HOOK_EXPLODING and HOOK_EXPLODED hooks as part of the processing.
 	a_SourceData exact type depends on the a_Source, see the declaration of the esXXX constants in Defines.h for details.
 	Exported to Lua manually in ManualBindings_World.cpp in order to support the variable a_SourceData param. */
 	virtual void DoExplosionAt(double a_ExplosionSize, double a_BlockX, double a_BlockY, double a_BlockZ, bool a_CanCauseFire, eExplosionSource a_Source, void * a_SourceData) override;
 
-	/** Calls the callback for the block entity at the specified coords; returns false if there's no block entity at those coords, true if found */
-	virtual bool DoWithBlockEntityAt(int a_BlockX, int a_BlockY, int a_BlockZ, cBlockEntityCallback a_Callback) override;  // Exported in ManualBindings.cpp
-
-	/** Calls the callback for the beacon at the specified coords; returns false if there's no beacon at those coords, true if found */
-	bool DoWithBeaconAt(int a_BlockX, int a_BlockY, int a_BlockZ, cBeaconCallback a_Callback);  // Exported in ManualBindings.cpp
-
-	/** Calls the callback for the bed at the specified coords; returns false if there's no bed at those coords, true if found */
-	virtual bool DoWithBedAt(int a_BlockX, int a_BlockY, int a_BlockZ, cBedCallback a_Callback) override;  // Exported in ManualBindings.cpp
-
-	/** Calls the callback for the brewingstand at the specified coords; returns false if there's no brewingstand at those coords, true if found */
-	bool DoWithBrewingstandAt(int a_BlockX, int a_BlockY, int a_BlockZ, cBrewingstandCallback a_Callback);  // Lua-acessible
-
-	/** Calls the callback for the chest at the specified coords; returns false if there's no chest at those coords, true if found */
-	bool DoWithChestAt(int a_BlockX, int a_BlockY, int a_BlockZ, cChestCallback a_Callback);  // Exported in ManualBindings.cpp
-
-	/** Calls the callback for the dispenser at the specified coords; returns false if there's no dispenser at those coords or callback returns true, returns true if found */
-	bool DoWithDispenserAt(int a_BlockX, int a_BlockY, int a_BlockZ, cDispenserCallback a_Callback);  // Exported in ManualBindings.cpp
-
-	/** Calls the callback for the dropper at the specified coords; returns false if there's no dropper at those coords or callback returns true, returns true if found */
-	bool DoWithDropperAt(int a_BlockX, int a_BlockY, int a_BlockZ, cDropperCallback a_Callback);  // Exported in ManualBindings.cpp
-
-	/** Calls the callback for the dropspenser at the specified coords; returns false if there's no dropspenser at those coords or callback returns true, returns true if found */
-	bool DoWithDropSpenserAt(int a_BlockX, int a_BlockY, int a_BlockZ, cDropSpenserCallback a_Callback);  // Exported in ManualBindings.cpp
-
-	/** Calls the callback for the furnace at the specified coords; returns false if there's no furnace at those coords or callback returns true, returns true if found */
-	bool DoWithFurnaceAt(int a_BlockX, int a_BlockY, int a_BlockZ, cFurnaceCallback a_Callback);  // Exported in ManualBindings.cpp
-
-	/** Calls the callback for the hopper at the specified coords; returns false if there's no hopper at those coords or callback returns true, returns true if found */
-	bool DoWithHopperAt(int a_BlockX, int a_BlockY, int a_BlockZ, cHopperCallback a_Callback);  // Exported in ManualBindings.cpp
-
-	/** Calls the callback for the noteblock at the specified coords; returns false if there's no noteblock at those coords or callback returns true, returns true if found */
-	bool DoWithNoteBlockAt(int a_BlockX, int a_BlockY, int a_BlockZ, cNoteBlockCallback a_Callback);  // Exported in ManualBindings.cpp
-
-	/** Calls the callback for the command block at the specified coords; returns false if there's no command block at those coords or callback returns true, returns true if found */
-	bool DoWithCommandBlockAt(int a_BlockX, int a_BlockY, int a_BlockZ, cCommandBlockCallback a_Callback);  // Exported in ManualBindings.cpp
-
-	/** Calls the callback for the mob head block at the specified coords; returns false if there's no mob head block at those coords or callback returns true, returns true if found */
-	bool DoWithMobHeadAt(int a_BlockX, int a_BlockY, int a_BlockZ, cMobHeadCallback a_Callback);  // Exported in ManualBindings.cpp
-
-	/** Calls the callback for the flower pot at the specified coords; returns false if there's no flower pot at those coords or callback returns true, returns true if found */
-	bool DoWithFlowerPotAt(int a_BlockX, int a_BlockY, int a_BlockZ, cFlowerPotCallback a_Callback);  // Exported in ManualBindings.cpp
+	/** Calls the callback for the block entity at the specified coords; returns false if there's no block entity at those coords, and whatever the callback returns if found. */
+	virtual bool DoWithBlockEntityAt(Vector3i a_Position, cBlockEntityCallback a_Callback) override;  // Exported in ManualBindings.cpp
 
 	/** Retrieves the test on the sign at the specified coords; returns false if there's no sign at those coords, true if found */
 	bool GetSignLines (int a_BlockX, int a_BlockY, int a_BlockZ, AString & a_Line1, AString & a_Line2, AString & a_Line3, AString & a_Line4);  // Exported in ManualBindings.cpp