diff --git a/mods/ENTITIES/mcl_minecarts/init.lua b/mods/ENTITIES/mcl_minecarts/init.lua
index bf51ba593..31a648c95 100644
--- a/mods/ENTITIES/mcl_minecarts/init.lua
+++ b/mods/ENTITIES/mcl_minecarts/init.lua
@@ -61,8 +61,9 @@ local function handle_cart_enter_exit(self, pos, next_dir, event)
 		local node_def = minetest.registered_nodes[node.name]
 
 		-- node-specific hook
-		local hook = node_def["_mcl_minecarts_"..event..check[4]]
-		if hook then hook(check_pos, self, next_dir) end
+		local hook_name = "_mcl_minecarts_"..event..check[4]
+		local hook = node_def[hook_name]
+		if hook then hook(check_pos, self, next_dir, pos) end
 
 		-- global minecart hook
 		hook = mcl_minecarts[event..check[4]]
@@ -89,7 +90,7 @@ local function handle_cart_node_watches(self, dtime)
 		local node_def = minetest.registered_nodes[node.name]
 		local hook = node_def._mcl_minecarts_node_on_step
 		if hook and hook(node_pos, self, dtime) then
-			new_watches[#new_watches] = node_pos
+			new_watches[#new_watches+1] = node_pos
 		end
 	end
 
@@ -141,7 +142,7 @@ end
 local function direction_away_from_players(self, staticdata)
 	local objs = minetest.get_objects_inside_radius(self.object:get_pos(), 1.1)
 	for n=1,#objs do
-		obj = objs[n]
+		local obj = objs[n]
 		local player_name = obj:get_player_name()
 		if player_name and player_name ~= "" and not ( self._driver and self._driver == player_name ) then
 			local diff = obj:get_pos() - self.object:get_pos()
@@ -314,7 +315,7 @@ local function do_movement_step(self, dtime)
 		handle_cart_enter(self, pos, next_dir)
 
 		-- Handle end of track
-		if next_dir == staticdata.dir * -1 and ( hopper_pulled or next_dir.y == 0 ) then
+		if next_dir == staticdata.dir * -1 and next_dir.y == 0 then
 			if DEBUG then print("Stopping cart at end of track at "..tostring(pos)) end
 			staticdata.velocity = 0
 		end
@@ -593,6 +594,13 @@ end
 
 local function register_entity(entity_id, def)
 	assert( def.drop, "drop is required parameter" )
+
+	-- Entity groups
+	local groups = { minecart = 1 }
+	for k,v in pairs(def.groups or {}) do
+		groups[k] = v
+	end
+
 	local cart = {
 		initial_properties = {
 			physical = true,
@@ -603,8 +611,11 @@ local function register_entity(entity_id, def)
 			textures = def.textures,
 		},
 
+		groups = groups,
+
 		on_rightclick = def.on_rightclick,
 		on_activate_by_rail = def.on_activate_by_rail,
+
 		_mcl_minecarts_on_enter = def._mcl_minecarts_on_enter,
 		_mcl_minecarts_on_place = def._mcl_minecarts_on_place,
 		_mcl_minecarts_on_step = def._mcl_minecarts_on_step,
@@ -665,7 +676,7 @@ local function register_entity(entity_id, def)
 				staticdata.connected_at = rounded_pos
 				pos = rounded_pos
 			else
-				print("TODO: handle detached cart behavior")
+				mineclone.log("warning","TODO: handle detached cart behavior")
 			end
 		end
 
@@ -757,14 +768,19 @@ local function register_entity(entity_id, def)
 
 	function cart:add_node_watch(pos)
 		local staticdata = self._staticdata
-		local watches = staticdata.watches
+		local watches = staticdata.node_watches or {}
+
 		for _,watch in ipairs(watches) do
 			if watch == pos then return end
 		end
 
 		watches[#watches+1] = pos
+		staticdata.node_watches = watches
 	end
 	function cart:remove_node_watch(pos)
+		local staticdata = self._staticdata
+		local watches = staticdata.node_watches or {}
+
 		local new_watches = {}
 		for _,node_pos in ipairs(watches) do
 			if node_pos ~= post then
@@ -792,8 +808,6 @@ local function register_entity(entity_id, def)
 		end
 
 		local pos, rou_pos, node = self.object:get_pos()
-		--local update = {}
-		--local acceleration = 0
 
 		-- Controls
 		local ctrl, player = nil, nil
@@ -820,7 +834,7 @@ local function register_entity(entity_id, def)
 
 		do_movement(self, dtime)
 
-		-- TODO: move this into do_movement_step
+		-- TODO: move this into mcl_core:cactus _mcl_minecarts_on_enter_side
 		-- Drop minecart if it collides with a cactus node
 		local r = 0.6
 		for _, node_pos in pairs({{r, 0}, {0, r}, {-r, 0}, {0, -r}}) do
@@ -1156,6 +1170,7 @@ register_minecart({
 	},
 	icon = "mcl_minecarts_minecart_chest.png",
 	drop = {"mcl_minecarts:minecart", "mcl_chests:chest"},
+	groups = { container = 1 },
 	on_rightclick = nil,
 	on_activate_by_rail = nil,
 	creative = true
@@ -1269,6 +1284,7 @@ register_minecart({
 	},
 	icon = "mcl_minecarts_minecart_hopper.png",
 	drop = {"mcl_minecarts:minecart", "mcl_hoppers:hopper"},
+	groups = { container = 1 },
 	on_rightclick = nil,
 	on_activate_by_rail = nil,
 	_mcl_minecarts_on_enter = function(self,pos)
diff --git a/mods/ITEMS/mcl_hoppers/init.lua b/mods/ITEMS/mcl_hoppers/init.lua
index 07971c42f..97f99fd37 100644
--- a/mods/ITEMS/mcl_hoppers/init.lua
+++ b/mods/ITEMS/mcl_hoppers/init.lua
@@ -96,6 +96,48 @@ local function bent_hopper_act(pos, node, active_object_count, active_object_cou
 	mcl_util.hopper_pull(pos, src_pos)
 end
 
+local function hopper_push_to_mc(mc_ent, dest_pos, inv_size)
+	local dest_inv = mcl_entity_invs.load_inv(mc_ent, inv_size)
+	if not dest_inv then
+		mcl_log("No inv")
+		return false
+	end
+
+	local meta = minetest.get_meta(dest_pos)
+	local inv = meta:get_inventory()
+	if not inv then
+		mcl_log("No dest inv")
+		return
+	end
+
+	mcl_log("inv. size: " .. mc_ent._inv_size)
+	for i = 1, mc_ent._inv_size, 1 do
+		local stack = inv:get_stack("main", i)
+
+		mcl_log("i: " .. tostring(i))
+		mcl_log("Name: [" .. tostring(stack:get_name()) .. "]")
+		mcl_log("Count: " .. tostring(stack:get_count()))
+		mcl_log("stack max: " .. tostring(stack:get_stack_max()))
+
+		if not stack:get_name() or stack:get_name() ~= "" then
+			if dest_inv:room_for_item("main", stack:peek_item()) then
+				mcl_log("Room so unload")
+				dest_inv:add_item("main", stack:take_item())
+				inv:set_stack("main", i, stack)
+
+				-- Take one item and stop until next time
+				return
+			else
+				mcl_log("no Room")
+			end
+
+		else
+			mcl_log("nothing there")
+		end
+	end
+end
+
+
 -- Downwards hopper (base definition)
 
 ---@type node_definition
@@ -201,6 +243,29 @@ local def_hopper = {
 		minetest.log("action", player:get_player_name() ..
 			" takes stuff from mcl_hoppers at " .. minetest.pos_to_string(pos))
 	end,
+	_mcl_minecarts_on_enter_above = function(pos, cart, next_dir)
+		-- Only push to containers
+		if cart.groups and (cart.groups.container or 0) ~= 0 then
+			cart:add_node_watch(pos)
+		end
+	end,
+	_mcl_minecarts_on_leave_above = function(pos, cart, next_dir)
+		cart:remove_node_watch(pos)
+	end,
+	_mcl_minecarts_node_on_step = function(pos, cart, dtime)
+		local meta = minetest.get_meta(pos)
+
+		local timer = meta:get_int("minecart_hopper_timer")
+		if timer < dtime then
+			hopper_push_to_mc(cart, pos, 5)
+			timer = timer + 1
+		else
+			timer = timer - dtime
+		end
+		meta:set_int("minecart_hopper_timer", timer)
+
+		return true
+	end,
 	sounds = mcl_sounds.node_sound_metal_defaults(),
 
 	_mcl_blast_resistance = 4.8,
@@ -406,6 +471,44 @@ local def_hopper_side = {
 	on_rotate = on_rotate,
 	sounds = mcl_sounds.node_sound_metal_defaults(),
 
+	_mcl_minecarts_on_enter_side = function(pos, cart, next_dir, rail_pos)
+		-- Only try to push to minecarts when the spout position is pointed at the rail
+		local face = minetest.get_node(pos).param2
+		local dst_pos = {}
+		if face == 0 then
+			dst_pos = vector.offset(pos, -1, 0, 0)
+		elseif face == 1 then
+			dst_pos = vector.offset(pos, 0, 0, 1)
+		elseif face == 2 then
+			dst_pos = vector.offset(pos, 1, 0, 0)
+		elseif face == 3 then
+			dst_pos = vector.offset(pos, 0, 0, -1)
+		end
+		if dst_pos ~= rail_pos then return end
+
+		-- Only push to containers
+		if cart.groups and (cart.groups.container or 0) ~= 0 then
+			cart:add_node_watch(pos)
+		end
+	end,
+	_mcl_minecarts_on_leave_side = function(pos, cart, next_dir)
+		cart:remove_node_watch(pos)
+	end,
+	_mcl_minecarts_node_on_step = function(pos, cart, dtime)
+		local meta = minetest.get_meta(pos)
+
+		local timer = meta:get_int("minecart_hopper_timer")
+		if timer < dtime then
+			hopper_push_to_mc(cart, pos, 5)
+			timer = timer + 1
+		else
+			timer = timer - dtime
+		end
+		meta:set_int("minecart_hopper_timer", timer)
+
+		return true
+	end,
+
 	_mcl_blast_resistance = 4.8,
 	_mcl_hardness = 3,
 }
@@ -480,47 +583,6 @@ local function hopper_pull_from_mc(mc_ent, dest_pos, inv_size)
 end
 mcl_hoppers.pull_from_minecart = hopper_pull_from_mc
 
-local function hopper_push_to_mc(mc_ent, dest_pos, inv_size)
-	local dest_inv = mcl_entity_invs.load_inv(mc_ent, inv_size)
-	if not dest_inv then
-		mcl_log("No inv")
-		return false
-	end
-
-	local meta = minetest.get_meta(dest_pos)
-	local inv = meta:get_inventory()
-	if not inv then
-		mcl_log("No dest inv")
-		return
-	end
-
-	mcl_log("inv. size: " .. mc_ent._inv_size)
-	for i = 1, mc_ent._inv_size, 1 do
-		local stack = inv:get_stack("main", i)
-
-		mcl_log("i: " .. tostring(i))
-		mcl_log("Name: [" .. tostring(stack:get_name()) .. "]")
-		mcl_log("Count: " .. tostring(stack:get_count()))
-		mcl_log("stack max: " .. tostring(stack:get_stack_max()))
-
-		if not stack:get_name() or stack:get_name() ~= "" then
-			if dest_inv:room_for_item("main", stack:peek_item()) then
-				mcl_log("Room so unload")
-				dest_inv:add_item("main", stack:take_item())
-				inv:set_stack("main", i, stack)
-
-				-- Take one item and stop until next time
-				return
-			else
-				mcl_log("no Room")
-			end
-
-		else
-			mcl_log("nothing there")
-		end
-	end
-end
-
 --[[ BEGIN OF ABM DEFINITONS ]]
 
 minetest.register_abm({