code:
-- internationalization boilerplate
local MP = minetest.get_modpath(minetest.get_current_modname())
local S, NS = dofile(MP.."/intllib.lua")

-- formspec
local function get_hopper_formspec(pos)
	local spos = hopper.get_string_pos(pos)
	local formspec =
		"size[8,9]"
		.. hopper.formspec_bg
		.. "list[nodemeta:" .. spos .. ";main;2,0.3;4,4;]"
		.. hopper.get_eject_button_texts(pos, 7, 2)
		.. "list[current_player;main;0,4.85;8,1;]"
		.. "list[current_player;main;0,6.08;8,3;8]"
		.. "listring[nodemeta:" .. spos .. ";main]"
		.. "listring[current_player;main]"
	return formspec
end

local hopper_on_place = function(itemstack, placer, pointed_thing, node_name)
	local pos  = pointed_thing.under
	local pos2 = pointed_thing.above
	local x = pos.x - pos2.x
	local z = pos.z - pos2.z

	local returned_stack, success
	-- unfortunately param2 overrides are needed for side hoppers even in the non-single-craftable-item case
	-- because they are literally *side* hoppers - their spouts point to the side rather than to the front, so
	-- the default item_place_node orientation code will not orient them pointing toward the selected surface.
	if x == -1 and (hopper.config.single_craftable_item or node_name == "hopper:hopper_side") then
		returned_stack, success = minetest.item_place_node(ItemStack("hopper:hopper_side"), placer, pointed_thing, 0)
	elseif x == 1 and (hopper.config.single_craftable_item or node_name == "hopper:hopper_side") then
		returned_stack, success = minetest.item_place_node(ItemStack("hopper:hopper_side"), placer, pointed_thing, 2)
	elseif z == -1 and (hopper.config.single_craftable_item or node_name == "hopper:hopper_side")  then
		returned_stack, success = minetest.item_place_node(ItemStack("hopper:hopper_side"), placer, pointed_thing, 3)
	elseif z == 1 and (hopper.config.single_craftable_item or node_name == "hopper:hopper_side") then
		returned_stack, success = minetest.item_place_node(ItemStack("hopper:hopper_side"), placer, pointed_thing, 1)
	else
		if hopper.config.single_craftable_item then
			node_name = "hopper:hopper" -- For cases where single_craftable_item was set on an existing world and there are still side hoppers in player inventories
		end
		returned_stack, success = minetest.item_place_node(ItemStack(node_name), placer, pointed_thing)
	end

	if success then
		local meta = minetest.get_meta(pos2)
		meta:set_string("placer", placer:get_player_name())
		if not minetest.settings:get_bool("creative_mode") then
			itemstack:take_item()
		end
	end
	return itemstack
end

-------------------------------------------------------------------------------------------
-- Hoppers

minetest.register_node("hopper:hopper", {
	drop = "hopper:hopper",
	description = S("Hopper"),
	_doc_items_longdesc = hopper.doc.hopper_long_desc,
    _doc_items_usagehelp = hopper.doc.hopper_usage,
	groups = {cracky = 3},
	sounds = hopper.metal_sounds,
	drawtype = "nodebox",
	paramtype = "light",
	paramtype2 = "facedir",
	tiles = {
		"hopper_top_" .. hopper.config.texture_resolution .. ".png",
		"hopper_top_" .. hopper.config.texture_resolution .. ".png",
		"hopper_front_" .. hopper.config.texture_resolution .. ".png"
	},
	node_box = {
		type = "fixed",
		fixed = {
			--funnel walls
			{-0.5, 0.0, 0.4, 0.5, 0.5, 0.5},
			{0.4, 0.0, -0.5, 0.5, 0.5, 0.5},
			{-0.5, 0.0, -0.5, -0.4, 0.5, 0.5},
			{-0.5, 0.0, -0.5, 0.5, 0.5, -0.4},
			--funnel base
			{-0.5, 0.0, -0.5, 0.5, 0.1, 0.5},
			--spout
			{-0.3, -0.3, -0.3, 0.3, 0.0, 0.3},
			{-0.15, -0.3, -0.15, 0.15, -0.7, 0.15},
		},
	},
	selection_box = {
		type = "fixed",
		fixed = {
			--funnel
			{-0.5, 0.0, -0.5, 0.5, 0.5, 0.5},
			--spout
			{-0.3, -0.3, -0.3, 0.3, 0.0, 0.3},
			{-0.15, -0.3, -0.15, 0.15, -0.7, 0.15},
		},
	},

	on_construct = function(pos)
		local inv = minetest.get_meta(pos):get_inventory()
		inv:set_size("main", 4*4)
	end,

	on_place = function(itemstack, placer, pointed_thing)
		return hopper_on_place(itemstack, placer, pointed_thing, "hopper:hopper")
	end,

	can_dig = function(pos, player)
		local inv = minetest.get_meta(pos):get_inventory()
		return inv:is_empty("main")
	end,

	on_rightclick = function(pos, node, clicker, itemstack)
		if minetest.is_protected(pos, clicker:get_player_name()) and not minetest.check_player_privs(clicker, "protection_bypass") then
			return
		end
		minetest.show_formspec(clicker:get_player_name(),
			"hopper_formspec:"..minetest.pos_to_string(pos), get_hopper_formspec(pos))
	end,

	on_metadata_inventory_move = function(pos, from_list, from_index, to_list, to_index, count, player)
		minetest.log("action", S("@1 moves stuff in hopper at @2",
			player:get_player_name(), minetest.pos_to_string(pos)))
	end,

	on_metadata_inventory_put = function(pos, listname, index, stack, player)
		minetest.log("action", S("@1 moves stuff to hopper at @2",
			player:get_player_name(), minetest.pos_to_string(pos)))
	end,

	on_metadata_inventory_take = function(pos, listname, index, stack, player)
		minetest.log("action", S("@1 moves stuff from hopper at @2",
			player:get_player_name(), minetest.pos_to_string(pos)))
	end,
})

local hopper_side_drop
local hopper_groups
if hopper.config.single_craftable_item then
	hopper_side_drop = "hopper:hopper"
	hopper_groups = {cracky=3, not_in_creative_inventory = 1}
else
	hopper_side_drop = "hopper:hopper_side"
	hopper_groups = {cracky=3}
end

minetest.register_node("hopper:hopper_side", {
	description = S("Side Hopper"),
	_doc_items_longdesc = hopper.doc.hopper_long_desc,
    _doc_items_usagehelp = hopper.doc.hopper_usage,
	drop = hopper_side_drop,
	groups = hopper_groups,
	sounds = hopper.metal_sounds,
	drawtype = "nodebox",
	paramtype = "light",
	paramtype2 = "facedir",
	tiles = {
		"hopper_top_" .. hopper.config.texture_resolution .. ".png",
		"hopper_bottom_" .. hopper.config.texture_resolution .. ".png",
		"hopper_back_" .. hopper.config.texture_resolution .. ".png",
		"hopper_side_" .. hopper.config.texture_resolution .. ".png",
		"hopper_back_" .. hopper.config.texture_resolution .. ".png",
		"hopper_back_" .. hopper.config.texture_resolution .. ".png"
	},
	node_box = {
		type = "fixed",
		fixed = {
			--funnel walls
			{-0.5, 0.0, 0.4, 0.5, 0.5, 0.5},
			{0.4, 0.0, -0.5, 0.5, 0.5, 0.5},
			{-0.5, 0.0, -0.5, -0.4, 0.5, 0.5},
			{-0.5, 0.0, -0.5, 0.5, 0.5, -0.4},
			--funnel base
			{-0.5, 0.0, -0.5, 0.5, 0.1, 0.5},
			--spout
			{-0.3, -0.3, -0.3, 0.3, 0.0, 0.3},
			{-0.7, -0.3, -0.15, 0.15, 0.0, 0.15},
		},
	},
	selection_box = {
		type = "fixed",
		fixed = {
			--funnel
			{-0.5, 0.0, -0.5, 0.5, 0.5, 0.5},
			--spout
			{-0.3, -0.3, -0.3, 0.3, 0.0, 0.3},
			{-0.7, -0.3, -0.15, 0.15, 0.0, 0.15},
		},
	},

	on_construct = function(pos)
		local inv = minetest.get_meta(pos):get_inventory()
		inv:set_size("main", 4*4)
	end,

	on_place = function(itemstack, placer, pointed_thing)
		return hopper_on_place(itemstack, placer, pointed_thing, "hopper:hopper_side")
	end,

	can_dig = function(pos,player)
		local inv = minetest.get_meta(pos):get_inventory()
		return inv:is_empty("main")
	end,

	on_rightclick = function(pos, node, clicker, itemstack)
		if minetest.is_protected(pos, clicker:get_player_name()) and not minetest.check_player_privs(clicker, "protection_bypass") then
			return
		end
		minetest.show_formspec(clicker:get_player_name(),
			"hopper_formspec:"..minetest.pos_to_string(pos), get_hopper_formspec(pos))
	end,

	on_metadata_inventory_move = function(pos, from_list, from_index, to_list, to_index, count, player)
		minetest.log("action", S("@1 moves stuff in hopper at @2",
			player:get_player_name(), minetest.pos_to_string(pos)))
	end,

	on_metadata_inventory_put = function(pos, listname, index, stack, player)
		minetest.log("action", S("@1 moves stuff to hopper at @2",
			player:get_player_name(), minetest.pos_to_string(pos)))
	end,

	on_metadata_inventory_take = function(pos, listname, index, stack, player)
		minetest.log("action", S("@1 moves stuff from hopper at @2",
			player:get_player_name(), minetest.pos_to_string(pos)))
	end,
})


code:
-- internationalization boilerplate
local MP = minetest.get_modpath(minetest.get_current_modname())
local S, NS = dofile(MP.."/intllib.lua")

local facedir_to_bottomdir = {
	[0]={x=0, y=-1, z=0},
	{x=0, y=0, z=-1},
	{x=0, y=0, z=1},
	{x=-1, y=0, z=0},
	{x=1, y=0, z=0},
	{x=0, y=1, z=0},
}

local bottomdir = function(facedir)
	return facedir_to_bottomdir[math.floor(facedir/4)]
end

local function get_sorter_formspec(pos)
	local spos = hopper.get_string_pos(pos)

	local filter_all = minetest.get_meta(pos):get_string("filter_all") == "true"
	local y_displace = 0
	local filter_button_text, filter_button_tooltip, filter_body
	if filter_all then
		filter_body = ""
		filter_button_text = S("Selective\nFilter")
		filter_button_tooltip = S("This sorter is currently set to try sending all items\nin the direction of the arrow. Click this button\nto enable an item-type-specific filter.")
	else
		filter_body = "label[3.7,0;"..S("Filter").."]list[nodemeta:" .. spos .. ";filter;0,0.5;8,1;]"
		filter_button_text = S("Filter\nAll")
		filter_button_tooltip = S("This sorter is currently set to only send items listed\nin the filter list in the direction of the arrow.\nClick this button to set it to try sending all\nitems that way first.")
		y_displace = 1.6
	end

	local formspec =
		"size[8," .. 7 + y_displace .. "]"
		.. hopper.formspec_bg
		.. filter_body
		.. "list[nodemeta:" .. spos .. ";main;3,".. tostring(0.3 + y_displace) .. ";2,2;]"
		.. "button_exit[7,".. tostring(0.8 + y_displace) .. ";1,1;filter_all;".. filter_button_text .. "]tooltip[filter_all;" .. filter_button_tooltip.. "]"
		.. hopper.get_eject_button_texts(pos, 6, 0.8 + y_displace)
		.. "list[current_player;main;0,".. tostring(2.85 + y_displace) .. ";8,1;]"
		.. "list[current_player;main;0,".. tostring(4.08 + y_displace) .. ";8,3;8]"
		.. "listring[nodemeta:" .. spos .. ";main]"
		.. "listring[current_player;main]"
	return formspec
end

minetest.register_node("hopper:sorter", {
	description = S("Sorter"),
	_doc_items_longdesc = hopper.doc.sorter_long_desc,
	_doc_items_usagehelp = hopper.doc.sorter_usage,
	groups = {cracky = 3},
	sounds = hopper.metal_sounds,
	drawtype = "nodebox",
	paramtype = "light",
	paramtype2 = "facedir",
	tiles = {
			"hopper_bottom_" .. hopper.config.texture_resolution .. ".png",
			"hopper_top_" .. hopper.config.texture_resolution .. ".png",
			"hopper_bottom_" .. hopper.config.texture_resolution .. ".png^hopper_sorter_arrow_" .. hopper.config.texture_resolution .. ".png^[transformFX^hopper_sorter_sub_arrow_" .. hopper.config.texture_resolution .. ".png^[transformFX",
			"hopper_bottom_" .. hopper.config.texture_resolution .. ".png^hopper_sorter_arrow_" .. hopper.config.texture_resolution .. ".png^hopper_sorter_sub_arrow_" .. hopper.config.texture_resolution .. ".png",
			"hopper_top_" .. hopper.config.texture_resolution .. ".png",
			"hopper_bottom_" .. hopper.config.texture_resolution .. ".png^hopper_sorter_arrow_" .. hopper.config.texture_resolution .. ".png",
		},
	node_box = {
		type = "fixed",
		fixed = {
			{-0.3, -0.3, -0.4, 0.3, 0.4, 0.4},
			{-0.2, -0.2, 0.4, 0.2, 0.2, 0.7},
			{-0.2, -0.3, -0.2, 0.2, -0.7, 0.2},
		},
	},

	on_construct = function(pos)
		local meta = minetest.get_meta(pos)
		local inv = meta:get_inventory()
		inv:set_size("main", 2*2)
		inv:set_size("filter", 8)
	end,

	on_place = function(itemstack, placer, pointed_thing, node_name)
		local pos  = pointed_thing.under
		local pos2 = pointed_thing.above
		local x = pos.x - pos2.x
		local z = pos.z - pos2.z

		local returned_stack, success = minetest.item_place_node(itemstack, placer, pointed_thing)
		if success then
			local meta = minetest.get_meta(pos2)
			meta:set_string("placer", placer:get_player_name())
		end
		return returned_stack
	end,

	can_dig = function(pos,player)
		local meta = minetest.get_meta(pos);
		local inv = meta:get_inventory()
		return inv:is_empty("main")
	end,

	on_rightclick = function(pos, node, clicker, itemstack)
		if minetest.is_protected(pos, clicker:get_player_name()) and not minetest.check_player_privs(clicker, "protection_bypass") then
			return
		end
		minetest.show_formspec(clicker:get_player_name(),
			"hopper_formspec:"..minetest.pos_to_string(pos), get_sorter_formspec(pos))
	end,

	allow_metadata_inventory_put = function(pos, listname, index, stack, player)
		if listname == "filter" then
			local inv = minetest.get_inventory({type="node", pos=pos})
			inv:set_stack(listname, index, stack:take_item(1))
			return 0
		end
		return stack:get_count()
	end,

	allow_metadata_inventory_take = function(pos, listname, index, stack, player)
		if listname == "filter" then
			local inv = minetest.get_inventory({type="node", pos=pos})
			inv:set_stack(listname, index, ItemStack(""))
			return 0
		end
		return stack:get_count()
	end,

	allow_metadata_inventory_move = function(pos, from_list, from_index, to_list, to_index, count, player)
		if to_list == "filter" then
			local inv = minetest.get_inventory({type="node", pos=pos})
			local stack_moved = inv:get_stack(from_list, from_index)
			inv:set_stack(to_list, to_index, stack_moved:take_item(1))
			return 0
		elseif from_list == "filter" then
			local inv = minetest.get_inventory({type="node", pos=pos})
			inv:set_stack(from_list, from_index, ItemStack(""))
			return 0
		end
		return count
	end,

	on_metadata_inventory_put = function(pos, listname, index, stack, player)
		minetest.log("action", S("@1 moves stuff to sorter at @2",
			player:get_player_name(), minetest.pos_to_string(pos)))

		local timer = minetest.get_node_timer(pos)
		if not timer:is_started() then
			timer:start(1)
		end
	end,

	on_timer = function(pos, elapsed)
		local meta = minetest.get_meta(pos);
		local inv = meta:get_inventory()

		-- build a filter list
		local filter_items = nil
		if meta:get_string("filter_all") ~= "true" then
			filter_items = {}
			local filter_inv_size = inv:get_size("filter")
			for i = 1, filter_inv_size do
				local stack = inv:get_stack("filter", i)
				local item = stack:get_name()
				if item ~= "" then
					filter_items[item] = true
				end
			end
		end

		local node = minetest.get_node(pos)
		local dir = minetest.facedir_to_dir(node.param2)
		local default_destination_pos = vector.add(pos, dir)
		local default_output_direction
		if dir.y == 0 then
			default_output_direction = "horizontal"
		end

		dir = bottomdir(node.param2)
		local filter_destination_pos = vector.add(pos, dir)
		local filter_output_direction
		if dir.y == 0 then
			filter_output_direction = "horizontal"
		end

		local success = false

		local filter_destination_node = minetest.get_node(filter_destination_pos)
		local registered_inventories = hopper.get_registered_inventories_for(filter_destination_node.name)
		if registered_inventories ~= nil then
			if filter_output_direction == "horizontal" then
				success = hopper.send_item_to(pos, filter_destination_pos, filter_destination_node, registered_inventories["side"], filter_items)
			else
				success = hopper.send_item_to(pos, filter_destination_pos, filter_destination_node, registered_inventories["bottom"], filter_items)
			end
		else
			success = hopper.send_item_to(pos, filter_destination_pos, filter_destination_node, nil, filter_items)
		end

		if not success then -- weren't able to put something in the filter destination, for whatever reason. Now we can start moving stuff forward to the default.
			local default_destination_node = minetest.get_node(default_destination_pos)
			local registered_inventories = hopper.get_registered_inventories_for(default_destination_node.name)
			if registered_inventories ~= nil then
				if default_output_direction == "horizontal" then
					hopper.send_item_to(pos, default_destination_pos, default_destination_node, registered_inventories["side"])
				else
					hopper.send_item_to(pos, default_destination_pos, default_destination_node, registered_inventories["bottom"])
				end
			else
				hopper.send_item_to(pos, default_destination_pos, default_destination_node)
			end
		end

		if not inv:is_empty("main") then
			minetest.get_node_timer(pos):start(1)
		end
	end,
})

repo_name: korkut/MeseCraft

license: mit


code:
#Dimensions of the textures used, in pixels.
hopper_texture_size (Hopper texture size) enum 16 16,32

#When true, the hopper mod uses a single craftable item that auto-selects
#a top hopper or side hopper depending on the context the player places it in.
#When false, top hoppers and side hoppers are explicitly craftable as separate
#items.
hopper_single_craftable_item (Single craftable item) bool true

#This enables a toggle button in the hopper formspecs that allows a hopper
#to be set to "eject" items out into the world if there's no suitable node
#in their target location to receive them. By default hoppers do not eject
#items like this, the player must set this on a per-hopper basis.
hopper_eject_button (Eject items button) bool true


code:
-- internationalization boilerplate
local MP = minetest.get_modpath(minetest.get_current_modname())
local S, NS = dofile(MP.."/intllib.lua")

-- Target inventory retrieval

-- looks first for a registration matching the specific node name, then for a registration
-- matching group and value, then for a registration matching a group and *any* value
hopper.get_registered_inventories_for = function(target_node_name)
	local output = hopper.containers[target_node_name]
	if output ~= nil then return output end

	local target_def = minetest.registered_nodes[target_node_name]
	if target_def == nil or target_def.groups == nil then return nil end

	for group, value in pairs(target_def.groups) do
		local registered_group = hopper.groups[group]
		if registered_group ~= nil then
			output = registered_group[value]
			if output ~= nil then return output end
			output = registered_group["all"]
			if output ~= nil then return output end
		end
	end

	return nil
end

hopper.get_eject_button_texts = function(pos, loc_X, loc_Y)
	if not hopper.config.eject_button_enabled then return "" end

	local eject_button_text, eject_button_tooltip
	if minetest.get_meta(pos):get_string("eject") == "true" then
		eject_button_text = S("Don't\nEject")
		eject_button_tooltip = S("This hopper is currently set to eject items from its output\neven if there isn't a compatible block positioned to receive it.\nClick this button to disable this feature.")
	else
		eject_button_text = S("Eject\nItems")
		eject_button_tooltip = S("This hopper is currently set to hold on to item if there\nisn't a compatible block positioned to receive it.\nClick this button to have it eject items instead.")
	end
	return "button_exit["..loc_X..","..loc_Y..";1,1;eject;"..eject_button_text.."]tooltip[eject;"..eject_button_tooltip.."]"
end

hopper.get_string_pos = function(pos)
	return pos.x .. "," .. pos.y .. "," ..pos.z
end

-- Apparently node_sound_metal_defaults is a newer thing, I ran into games using an older version of the default mod without it.
if default.node_sound_metal_defaults ~= nil then
	hopper.metal_sounds = default.node_sound_metal_defaults()
else
	hopper.metal_sounds = default.node_sound_stone_defaults()
end

-------------------------------------------------------------------------------------------
-- Inventory transfer functions

local delay = function(x)
	return (function() return x end)
end

local get_placer = function(player_name)
	if player_name ~= "" then
		return minetest.get_player_by_name(player_name) or {
			is_player = delay(true),
			get_player_name = delay(player_name),
			is_fake_player = ":hopper",
			get_wielded_item = delay(ItemStack(nil))
		}
	end
	return nil
end

-- Used to remove items from the target block and put it into the hopper's inventory
hopper.take_item_from = function(hopper_pos, target_pos, target_node, target_inventory_name)
	if target_inventory_name == nil then
		return
	end
	local target_def = minetest.registered_nodes[target_node.name]
	if not target_def then
		return
	end

	--hopper inventory
	local hopper_meta = minetest.get_meta(hopper_pos);
	local hopper_inv = hopper_meta:get_inventory()
	local placer = get_placer(hopper_meta:get_string("placer"))

	--source inventory
	local target_inv = minetest.get_meta(target_pos):get_inventory()
	local target_inv_size = target_inv:get_size(target_inventory_name)
	if target_inv:is_empty(target_inventory_name) == false then
		for i = 1,target_inv_size do
			local stack = target_inv:get_stack(target_inventory_name, i)
			local item = stack:get_name()
			if item ~= "" then
				if hopper_inv:room_for_item("main", item) then
					local stack_to_take = stack:take_item(1)
					if target_def.allow_metadata_inventory_take == nil
					  or placer == nil -- backwards compatibility, older versions of this mod didn't record who placed the hopper
					  or target_def.allow_metadata_inventory_take(target_pos, target_inventory_name, i, stack_to_take, placer) > 0 then
						target_inv:set_stack(target_inventory_name, i, stack)
						--add to hopper
						hopper_inv:add_item("main", stack_to_take)
						if target_def.on_metadata_inventory_take ~= nil and placer ~= nil then
							target_def.on_metadata_inventory_take(target_pos, target_inventory_name, i, stack_to_take, placer)
						end
						break
					end
				end
			end
		end
	end
end

-- Used to put items from the hopper inventory into the target block
hopper.send_item_to = function(hopper_pos, target_pos, target_node, target_inventory_name, filtered_items)
	local hopper_meta = minetest.get_meta(hopper_pos)
	local target_def = minetest.registered_nodes[target_node.name]
	if not target_def then
		return false
	end

	local eject_item = hopper.config.eject_button_enabled and hopper_meta:get_string("eject") == "true" and target_def.buildable_to

	if not eject_item and not target_inventory_name then
		return false
	end

	--hopper inventory
	local hopper_meta = minetest.get_meta(hopper_pos);
	local hopper_inv = hopper_meta:get_inventory()
	if hopper_inv:is_empty("main") == true then
		return false
	end
	local hopper_inv_size = hopper_inv:get_size("main")
	local placer = get_placer(hopper_meta:get_string("placer"))

	--target inventory
	local target_inv = minetest.get_meta(target_pos):get_inventory()

	for i = 1,hopper_inv_size do
		local stack = hopper_inv:get_stack("main", i)
		local item = stack:get_name()
		if item ~= "" and (filtered_items == nil or filtered_items[item]) then
			if target_inventory_name then
				if target_inv:room_for_item(target_inventory_name, item) then
					local stack_to_put = stack:take_item(1)
					if target_def.allow_metadata_inventory_put == nil
					or placer == nil -- backwards compatibility, older versions of this mod didn't record who placed the hopper
					or target_def.allow_metadata_inventory_put(target_pos, target_inventory_name, i, stack_to_put, placer) > 0 then
						hopper_inv:set_stack("main", i, stack)
						--add to target node
						target_inv:add_item(target_inventory_name, stack_to_put)
						if target_def.on_metadata_inventory_put ~= nil and placer ~= nil then
							target_def.on_metadata_inventory_put(target_pos, target_inventory_name, i, stack_to_put, placer)
						end
						return true
					end
				end
			elseif eject_item then
				local stack_to_put = stack:take_item(1)
				minetest.add_item(target_pos, stack_to_put)
				hopper_inv:set_stack("main", i, stack)
				return true
			end
		end
	end
	return false
end


code:
# Auto detect text files and perform LF normalization
* text=auto


code:
Ingots
======
A small mod that makes it possible to place ore ingots in the world.

Options
=======
Toggle 'is_big' in conf.lua to make ingots apear bigger.
You can make an individual per ingot type choice in init.lua

API
===
You can ragister your own ingots.
Call "ingots.register_ingots(ingot_item, texture, is_big)"
ingot_item 	- The item which will be consumed to place an ingot. ex.: "default:steel_ingot"
texture 	- Name of texture used on ingot mesh. ex.: "ingot_steel.png"
is_big 		- Boolean which determines which ingot variant will be used.


code:
--[[
	Ingots - allows the placemant of ingots in the world
	Copyright (C) 2018  Skamiz Kazzarch

	This library is free software; you can redistribute it and/or
	modify it under the terms of the GNU Lesser General Public
	License as published by the Free Software Foundation; either
	version 2.1 of the License, or (at your option) any later version.

	This library is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
	Lesser General Public License for more details.

	You should have received a copy of the GNU Lesser General Public
	License along with this library; if not, write to the Free Software
	Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
]]--

-- takes an item name and a texture name and a boolean whether the ingots are big
function ingots.register_ingots(ingot_item, texture, is_big)

	--checks, whether the item name is a valid item (thanks 'puzzlecube')
	if not minetest.registered_items[ingot_item] then
		minetest.log("warning", ingot_item.." is not registered. Skipping ingot registration")
		return
	end

	-- de hardcoded modname, which allows the api to be properly used from within other mods (thanks 'argyle')
	local mod_name = minetest.get_current_modname()
	local mod_prefix = mod_name .. ":"

	local stack_size = 64
	local texture_prefix = "ingot_"
	--gets item name witout mod part, to be used in the deffinition of the new nodes
	local ingot_name = string.sub(ingot_item, string.find(ingot_item, ":", 1, true) +1, -1)

	if is_big then
		ingot_name = ingot_name .. "_big"
		stack_size = 8
		texture_prefix = "ingot_big_"
	end

	--this way there is no need for a separate on_punch function for a stack of 1 ingot
	minetest.register_alias(mod_prefix .. ingot_name .."_0", "air")

	--gives the ingot_item the ability to be placed and increas already placed stacks of ingots
	minetest.override_item(ingot_item, {
		on_place = function (itemstack, placer, pointed_thing)
            local pos = minetest.get_pointed_thing_position(pointed_thing, true)
            if minetest.is_protected(pos, placer:get_player_name()) and not minetest.check_player_privs(placer, "protection_bypass") then
			    return
		    end
			if pointed_thing["type"] == "node" then
				local name = minetest.get_node(pointed_thing.under).name
				-- call on_rightclick function of pointed node if aplicable and not sneak
				-- might or might not break if item is placed by mod devices
				if minetest.registered_nodes[name].on_rightclick and
					not placer:get_player_control().sneak
				then
					minetest.registered_nodes[name].on_rightclick(pointed_thing.under,
						minetest.get_node(pointed_thing.under),
						placer,
						itemstack)
				elseif string.find(name, mod_prefix .. ingot_name) then
					local count = string.gsub(name, "%D*", "")
					if stack_size > minetest.registered_nodes[minetest.get_node(pointed_thing.under).name]._ingot_count then
						minetest.set_node(pointed_thing.under, {name = mod_prefix .. ingot_name .."_" .. count + 1, param2 = minetest.get_node(pointed_thing.under).param2})
						if not (creative and creative.is_enabled_for and creative.is_enabled_for(placer:get_player_name())) then
							itemstack:take_item()
						end
					elseif minetest.get_node(pointed_thing.above).name == "air" then
						minetest.set_node(pointed_thing.above, {name = mod_prefix .. ingot_name .."_1"})
						if not (creative and creative.is_enabled_for and creative.is_enabled_for(placer:get_player_name())) then
							itemstack:take_item()
						end
					end

				elseif minetest.get_node(pointed_thing.above).name == "air" then
					minetest.set_node(pointed_thing.above, {name = mod_prefix .. ingot_name .."_1"})
					if not (creative and creative.is_enabled_for and creative.is_enabled_for(placer:get_player_name())) then
						itemstack:take_item()
					end
				end

				return itemstack
			end
		end
	})

	--registers 'stack_size' number of nodes, each has one more ingot in it than the last
	for i = 1, stack_size do
		local box = {
					type = "fixed",
					fixed = {
						--rectangular box which encompases all placed ingots
						ingots.get_box(is_big, i),
					},
				}
		minetest.register_node(mod_prefix .. ingot_name .. "_" .. i,{
			description = "ingots",
			drawtype = "mesh",
			tiles = {texture},
			mesh = texture_prefix .. i .. ".obj",
			selection_box = box,
			collision_box = box,
			paramtype = 'light',
			paramtype2 = "facedir",
			groups = {cracky = 3, level = 2, not_in_creative_inventory = 1},
			drop = ingot_item .. " " .. i,
			on_punch = function (pos, node, puncher, pointed_thing)
				if puncher then
					local wield = puncher:get_wielded_item()
					--checks, so that a stack can be taken appart only by hand or relevant ingot_item
					if wield:get_name() == ingot_item or
						wield:get_count() == 0 then
                        if minetest.is_protected(pos, puncher:get_player_name()) and not minetest.check_player_privs(puncher, "protection_bypass") then
			                return
		                end
						minetest.set_node(pos, {name = mod_prefix .. ingot_name .."_" .. i - 1, param2 = node.param2})
						if not (creative and creative.is_enabled_for and creative.is_enabled_for(puncher:get_player_name())) then
							local stack = ItemStack(ingot_item)
							puncher:get_inventory():add_item("main", stack)
						end
					end
				end
			end,
			_ingot_name = ingot_name,
			_ingot_count = i,
		})
	end
end

--returns an apropriate nodebox for a given number of ingots
function ingots.get_box(is_big, i)
	if is_big then return {-0.5, -0.5, -0.5, 0.5, (((i + 1 - ((i +1 )%2)) / 8) - 0.5), 0.5}
	else return {-0.5, -0.5, -0.5, 0.5, (((i - 1 - ((i-1)%8)) / 8) - 3) / 8, 0.5}
	end
end


code:
Version 1.5
-support for protections by NathanSalapat

Version 1.4
-fixed API funtionality, ingots now can be registered from other mods
-chests and similar now can be used with an ingot in hand, unless sneaking

Version 1.3
-support for brass, lead, carbon steel, cast iron, chromium and zinc of technic
-support for rainbow ore of rainbow ore

Version 1.2
-support for stainless steel and mixed metal of technic
-added compatibility with 'creative'
-merged big ingots variant
-fixed a bug which allowed to overwrite blocks next to full stacks

Version 1.1
-support for tin, silver and mithril of 'moreores'
-separate big ingots variant

Version 1.0
-release
-support for tin, copper, bronze, steel and gold of 'default'


code:
local conf = {}

-- set this to 'true' to make ingots 8 times larger
conf.is_big = false

return conf


code:
default?
moreores?
technic?
rainbow_ore?


code:
--[[
	Ingots - allows the placemant of ingots in the world
	Copyright (C) 2018  Skamiz Kazzarch

	This library is free software; you can redistribute it and/or
	modify it under the terms of the GNU Lesser General Public
	License as published by the Free Software Foundation; either
	version 2.1 of the License, or (at your option) any later version.

	This library is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
	Lesser General Public License for more details.

	You should have received a copy of the GNU Lesser General Public
	License along with this library; if not, write to the Free Software
	Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
]]--

ingots = {}

local conf = dofile(minetest.get_modpath("ingots").."/conf.lua")
dofile(minetest.get_modpath("ingots").."/api.lua")

--Here you can make individual choices per ingot on which varian will be used.
--To disable the ability of an ingot to be placed just comment out it's registration line.

if minetest.get_modpath("default") then
		ingots.register_ingots("default:copper_ingot", "ingot_copper.png", conf.is_big)
		ingots.register_ingots("default:tin_ingot", "ingot_tin.png", conf.is_big)
		ingots.register_ingots("default:bronze_ingot", "ingot_bronze.png", conf.is_big)
		ingots.register_ingots("default:steel_ingot", "ingot_steel.png", conf.is_big)
		ingots.register_ingots("default:gold_ingot", "ingot_gold.png", conf.is_big)
end

if minetest.get_modpath("moreores") then
		ingots.register_ingots("moreores:silver_ingot", "ingot_silver.png", conf.is_big)
		ingots.register_ingots("moreores:mithril_ingot", "ingot_mithril.png", conf.is_big)
		if not minetest.registered_items["default:tin_ingot"] then
			ingots.register_ingots("moreores:tin_ingot", "ingot_tin.png", conf.is_big)
		end
end

if minetest.get_modpath("technic") then
		ingots.register_ingots("technic:stainless_steel_ingot", "ingot_stainless_steel.png", conf.is_big)
		ingots.register_ingots("technic:carbon_steel_ingot", "ingot_carbon_steel.png", conf.is_big)
		ingots.register_ingots("technic:mixed_metal_ingot", "ingot_mixed_metal.png", conf.is_big)
		ingots.register_ingots("technic:brass_ingot", "ingot_brass.png", conf.is_big)
		ingots.register_ingots("technic:lead_ingot", "ingot_lead.png", conf.is_big)
		ingots.register_ingots("technic:zinc_ingot", "ingot_zinc.png", conf.is_big)
		ingots.register_ingots("technic:chromium_ingot", "ingot_chromium.png", conf.is_big)
		ingots.register_ingots("technic:cast_iron_ingot", "ingot_cast_iron.png", conf.is_big)
end

if minetest.get_modpath("rainbow_ore") then
		ingots.register_ingots("rainbow_ore:rainbow_ore_ingot", "ingot_rainbow_ore.png", conf.is_big)
end


code:
name = ingots
description = makes ingots placable in the world
optional_depends = default, moreores, technic, rainbow_ore


code:
screwdriver = screwdriver or {}

local tmp = {}
local max_objs = tonumber(minetest.settings:get("max_objects_per_block")) or 49

-- item entity

minetest.register_entity("itemframes:item",{
	hp_max = 1,
	visual = "wielditem",
	visual_size = {x = 0.33, y = 0.33},
	collisionbox = {0, 0, 0, 0, 0, 0},
	physical = false,
	textures = {"air"},

	on_activate = function(self, staticdata)

		if not self then
			self.object:remove()
			return
		end

		if tmp.nodename ~= nil
		and tmp.texture ~= nil then

			self.nodename = tmp.nodename
			tmp.nodename = nil
			self.texture = tmp.texture
			tmp.texture = nil
		else
			if staticdata ~= nil
			and staticdata ~= "" then

				local data = staticdata:split(';')

				if data and data[1] and data[2] then

					self.nodename = data[1]
					self.texture = data[2]
				end
			end
		end

		if self.texture ~= nil then
			self.object:set_properties({textures = {self.texture}})
		end

		if self.nodename == "itemframes:pedestal" then
			self.object:set_properties({automatic_rotate = 1})
		end

	end,

	get_staticdata = function(self)

		if self.nodename ~= nil
		and self.texture ~= nil then
			return self.nodename .. ';' .. self.texture
		end

		return ""
	end,
})

-- helper table

local facedir = {}

facedir[0] = {x = 0, y = 0, z = 1}
facedir[1] = {x = 1, y = 0, z = 0}
facedir[2] = {x = 0, y = 0, z = -1}
facedir[3] = {x = -1, y = 0, z = 0}

-- remove entities

local remove_item = function(pos, nodename)

	local ypos = 0

	if nodename == "itemframes:pedestal" then
		ypos = 1
	end

	local objs = minetest.get_objects_inside_radius({
			x = pos.x, y = pos.y + ypos, z = pos.z}, 0.5)

	if objs then

		for _, obj in pairs(objs) do

			if obj and obj:get_luaentity()
			and obj:get_luaentity().name == "itemframes:item" then
				obj:remove()
			end
		end
	end
end

-- update entity

local update_item = function(pos, node)

	remove_item(pos, node.name)

	local meta = minetest.get_meta(pos)

	if not meta then return end

	if meta:get_string("item") ~= "" then

		if node.name == "itemframes:frame" then

			local posad = facedir[node.param2]

			if not posad then return end

			pos.x = pos.x + posad.x * 6.5 / 16
			pos.y = pos.y + posad.y * 6.5 / 16
			pos.z = pos.z + posad.z * 6.5 / 16

		elseif node.name == "itemframes:pedestal" then

			pos.y = pos.y + 12 / 16 + 0.33
		end

		tmp.nodename = node.name
		tmp.texture = ItemStack(meta:get_string("item")):get_name()

		local e = minetest.add_entity(pos,"itemframes:item")

		if node.name == "itemframes:frame" then

			--local yaw = math.pi * 2 - node.param2 * math.pi / 2
			local yaw = 6.28 - node.param2 * 1.57

			e:set_yaw(yaw)
		end
	end
end

-- remove entity and drop as item

local drop_item = function(pos, nodename, metadata)

	local meta = metadata or minetest.get_meta(pos)

	if not meta then return end

	local item = meta:get_string("item")

	meta:set_string("item", "")

	if item ~= "" then

		remove_item(pos, nodename)

		if nodename == "itemframes:pedestal" then
			pos.y = pos.y + 1
		end

		minetest.add_item(pos, item)
	end
end

-- itemframe node and recipe

minetest.register_node("itemframes:frame",{
	description = "Item frame",
	drawtype = "nodebox",
	node_box = {
		type = "fixed",
		fixed = {-0.5, -0.5, 7/16, 0.5, 0.5, 0.5}
	},
	selection_box = {
		type = "fixed",
		fixed = {-0.5, -0.5, 7/16, 0.5, 0.5, 0.5}
	},
	tiles = {"itemframes_frame.png"},
	inventory_image = "itemframes_frame.png",
	wield_image = "itemframes_frame.png",
	paramtype = "light",
	paramtype2 = "facedir",
	sunlight_propagates = true,
	groups = {choppy = 2, dig_immediate = 2, flammable = 2},
	legacy_wallmounted = true,
	sounds = default.node_sound_defaults(),
	on_rotate = screwdriver.disallow,

	after_place_node = function(pos, placer, itemstack)

		local meta = minetest.get_meta(pos)

		meta:set_string("infotext","Item frame (right-click to add or remove item)")
	end,

	on_rightclick = function(pos, node, clicker, itemstack)

		if not itemstack
		or minetest.is_protected(pos, clicker:get_player_name()) then
			return
		end

		local meta = minetest.get_meta(pos)

		if not meta then return end

		if meta:get_string("item") ~= "" then

			drop_item(pos, node.name, meta)
		else
			local s = itemstack:take_item()

			meta:set_string("item", s:to_string())

			update_item(pos, node)

			return itemstack
		end
	end,

	on_destruct = function(pos)
		drop_item(pos, "itemframes:frame")
	end,

	on_punch = function(pos, node, puncher)
		update_item(pos, node)
	end,

	on_blast = function(pos, intensity)

		drop_item(pos, "itemframes:frame")

		minetest.add_item(pos, {name = "itemframes:frame"})

		minetest.remove_node(pos)
	end,

	on_burn = function(pos)

		drop_item(pos, "itemframes:frame")

		minetest.remove_node(pos)
	end,
})

minetest.register_craft({
	output = "itemframes:frame",
	recipe = {
		{"default:stick", "default:stick", "default:stick"},
		{"default:stick", "default:paper", "default:stick"},
		{"default:stick", "default:stick", "default:stick"},
	}
})

-- pedestal node and recipe

minetest.register_node("itemframes:pedestal",{
	description = "Pedestal",
	drawtype = "nodebox",
	node_box = {
		type = "fixed", fixed = {
			{-7/16, -8/16, -7/16, 7/16, -7/16, 7/16}, -- bottom plate
			{-6/16, -7/16, -6/16, 6/16, -6/16, 6/16}, -- bottom plate (upper)
			{-0.25, -6/16, -0.25, 0.25, 11/16, 0.25}, -- pillar
			{-7/16, 11/16, -7/16, 7/16, 12/16, 7/16}, -- top plate
		}
	},
	selection_box = {
		type = "fixed",
		fixed = {-7/16, -0.5, -7/16, 7/16, 12/16, 7/16}
	},
	tiles = {"itemframes_pedestal.png"},
	paramtype = "light",
	groups = {cracky = 3},
	sounds = default.node_sound_defaults(),
	on_rotate = screwdriver.disallow,

	after_place_node = function(pos, placer, itemstack)

		local meta = minetest.get_meta(pos)

		meta:set_string("infotext","Pedestal (right-click to add or remove item)")
	end,

	on_rightclick = function(pos, node, clicker, itemstack)

		if not itemstack
		or minetest.is_protected(pos, clicker:get_player_name()) then
			return
		end

		local meta = minetest.get_meta(pos)

		if not meta then return end

		if meta:get_string("item") ~= "" then

			drop_item(pos, node.name, meta)
		else

			local s = itemstack:take_item()

			meta:set_string("item", s:to_string())

			update_item(pos, node)

			return itemstack
		end
	end,

	on_destruct = function(pos)
		drop_item(pos, "itemframes:pedestal")
	end,

	on_punch = function(pos, node, puncher)
		update_item(pos, node)
	end,

	on_blast = function(pos, intensity)

		drop_item(pos, "itemframes:pedestal")

		minetest.add_item(pos, {name = "itemframes:pedestal"})

		minetest.remove_node(pos)
	end,
})

minetest.register_craft({
	output = "itemframes:pedestal",
	recipe = {
		{"default:stone", "default:stone", "default:stone"},
		{"", "default:stone", ""},
		{"default:stone", "default:stone", "default:stone"},
	}
})

-- automatically restore entities lost from frames/pedestals
-- due to /clearobjects or similar

minetest.register_abm({
	nodenames = {"itemframes:frame", "itemframes:pedestal"},
	interval = 25,
	chance = 1,
	catch_up = false,

	action = function(pos, node, active_object_count, active_object_count_wider)

		if active_object_count >= max_objs then
			return
		end

		local num

		if node.name == "itemframes:frame" then
			num = #minetest.get_objects_inside_radius(pos, 0.5)

		elseif node.name == "itemframes:pedestal" then

			pos.y = pos.y + 1
			num = #minetest.get_objects_inside_radius(pos, 0.5)
			pos.y = pos.y - 1
		end

		if num > 0 then
			return
		end

		update_item(pos, node)
	end
})

-- stop mesecon pistons from pushing itemframe and pedestals

if minetest.get_modpath("mesecons_mvps") then
	mesecon.register_mvps_stopper("itemframes:frame")
	mesecon.register_mvps_stopper("itemframes:pedestal")
end


code:
name = itemframes
description = Adds item frames and pedestals to showcase items.
author = TenPlus1
depends = default
optional_depends = mesecons_mvps


code:
Item Frames mod for minetest

This mod differs from the original and the one included in homedecor modpack in that no one person owns the itemframe or pedestals, they are protected with minetest.is_protected() so can have multiple owners or none at all...

Right-click to add or remove an item from a frame or pedestal, please note that if an item already has on_rightclick registered then it cannot be added to either.  Punch frame or pedestal to force update if items do not appear.


code:
This mod provides set of alphanumeric LED marquee panels, controlled by Mesecons' Digilines mod.

Simply place one or more panels, and set a channel on just the left-most or upper-left one.

Then send a character, a string, or one of several control words or codes to that channel from a Mesecons Lua Controller and the mod will try to display it.

A single character will be displayed on the connected panel.

A numeric message (i.e. not a string) will be converted into a string.

Strings of all types (other than the keywords below) will be displayed using all panels in a lineup, so long as they all face the same way, starting from the panel the Lua Controller is connected to, going left to right. The other panels in the line do not need to be connected to anything - think of them as being connected together internally. Only the panel at the far left need be connected to the Lua Controller.

The string will spread down the line until either a panel is found that faces the wrong way, or has a channel that's not empty/nil and is set to something other than what the first is set to, or if a node is encountered that is not an alpha-numeric panel at all.

Panels to the left of the connected one are ignored (unless they, too, have their own connections).

You can also stack up a wall of LED panels, of any horizontal and vertical amount. If you then set a channel on the upper left panel, leave the others un-set, and connect a LuaController to it via digilines, the whole wall of panels will be treated as a multi-line display.

Long strings sent to that channel will be displayed starting at the upper-left and working from left to right, top to bottom, wrapping from line to line as appropriate (similar to printing to a shell terminal).

As with a single line, printing continues from node to node until the program either finds a panel with a different non-empty channel than the first one, or if it finds a panel that's facing the wrong way.

If the program finds something other than a panel, it wraps to the next line. If it finds something other than a panel twice in a row, that signals that text has wrapped off of the last row, and printing is cut off there.

Lines of panels don't need to be all the same length, the program will wrap as needed, with the left margin always being aligned with the panel the LuaController is connected to.

Strings are trimmed to 6 kB.

Panels are not erased between prints.

Any unrecognized symbol or character, whether part of a string or singularly is ignored, except as noted below.

This mod uses the full ISO-8859-1 character set (see https://en.wikipedia.org/wiki/ISO/IEC_8859-1 for details), plus a bunch of symbols stuffed into the normally-empty 128-159 range that should be useful on this sort of display:

* 128,129: musical notes
* 130-140: box drawing glyphs
* 141-144: block shades
* 145-152: arrows
* 153-156: explosion/splat
* 157-159: smileys

If a string is prefixed with character code 255, it is treated as UTF-8 and passed through a simple translation function.  Only characters with codes greater than 159 are altered; normal ASCII text, color codes, control codes, and the above symbols are passed through unchanged.  Note that in this mode, a character code over 159 is treated as the first byte of a two-byte symbol.

The panels also respond to these control messages:

* "clear" turns all panels in a lineup or wall off, or up to 2048 of them, anyway - essentially a "clear screen" command.
* "allon" fills all panels in a lineup/wall, up to a max of 2048 of them, with char(144), i.e. the reverse of "clear".
* "start_scroll" starts the automatic scrolling function, repeatedly moving the last displayed message to the left one character space each time the scroll timer runs out (and automatically restarting it, natch).  The scroll action will spread across the line, and down a multi-line wall (just set a new, different channel on the first row you want to exclude), and will continue until "stop_scroll" or any displayable message is received.

	As it advances through the message, the scroll code will search through the message for a printable character, on each scroll step, basically stripping-out color code, and using just the last one before the new start position.  This is done in order to keep a constant visible speed (the text will still be colored properly though).
* "stop_scroll" does just what it says - it stops the auto-scroll timer.
* "scroll_speed" followed by a decimal number (in the string, not a byte value) sets the time between scroll steps.  Minimum 0.2s, maximum 5s.
* "scroll_step" will immediately advance the last-displayed message by one character.  Omit the above automatic scrolling keywords, and use ONLY this keyword instead if you want to let your LuaController control the scrolling speed.  Optionally, you can follow this with a number and the scroll code will skip forward that many bytes into the message, starting from the current position, before starting the above-mentioned color-vs-character search.  Essentially, this value will roughly translate to the number of printable characters to skip.
* "get" will read the one character (as a numerical character value) currently displayed by the master panel (by reading its node name)
* "getstr" will read the last-stored message for the entire lineup/wall (from the master panel's meta).  Note that even if the message has been or is being scrolled, you'll get the original stored message.
* "getindex" will read the scroll index position in that message, which will always point at a printable character, per the above color-versus-character search.

During a scroll event, the printed string is padded with spaces (one in auto mode, or as many as the skip value when manually stepping).

If you need vertical scrolling, you will have to handle that yourself (since the size of a screen/wall is not hard-coded).

To change colors, put a "/" followed by a digit or a letter from "A" to "R" (or "a" to "r") into your printed string.  Digits 0 to 9 trigger colors 0 to 9 (obviously :-) ), while A/a through R/r set colors 10 to 27.  Any other sequence is invalid and will just be printed literally.  Two slashes "//" will translated to a single char(30) internally, and displayed as a single slash (doing it that way makes the code easier).

Color values 0 to 11 are:

Red (0), orange, yellow, lime, green, aqua, cyan, sky blue, blue, violet, magenta, or red-violet (11)

Colors 12 to 23 are the same as 0 to 11, but lower brightness.

Colors 24 - 27 are white, light grey, medium grey, and dim grey (or think of them as full bright white, a bit less bright, medium brightness, and dim white).

The last color that was used is stored in the left-most/upper-left "master" panel's metadata, and defaults to red. It should persist across reboots.

char(10) will do its job as linefeed/newline.

char(29) signals a cursor position command. The next two byte values select a column and row, respectively. The next character after the row byte will be printed there, and the rest of the string then continues printing from that spot onward with normal line wrapping, colors and so forth. Note that any string that does NOT contain cursor positioning commands will automatically start printing at the upper-left.

Any number of color, line feed, and cursor position commands may be present in a string, making it possible to "frame-buffer" a screen full of text into a string before printing it.

All panels emit a small amount of light when displaying something.

The panels only mount on a wall.

The "master"/connected panel stores the last-displayed message and some other details in its metadata, so you may occasionally need to dig and re-place the panel if things go wonky (this won't happen during normal use, but it may happen if you're making lots of changes to the panels' layout, channel names, etc).


code:
-- simple LED marquee mod
-- by Vanessa Dannenberg

led_marquee = {}

local S
if minetest.get_modpath("intllib") then
	S = intllib.make_gettext_pair()
else
	S = function(s) return s end
end

local color_to_char = {
	"0",
	"1",
	"2",
	"3",
	"4",
	"5",
	"6",
	"7",
	"8",
	"9",
	"A",
	"B",
	"C",
	"D",
	"E",
	"F",
	"G",
	"H",
	"I",
	"J",
	"K",
	"L",
	"M",
	"N",
	"O",
	"P",
	"Q",
	"R"
}

local char_to_color = {
	["0"] = 0,
	["1"] = 1,
	["2"] = 2,
	["3"] = 3,
	["4"] = 4,
	["5"] = 5,
	["6"] = 6,
	["7"] = 7,
	["8"] = 8,
	["9"] = 9,

	["A"] = 10,
	["B"] = 11,
	["C"] = 12,
	["D"] = 13,
	["E"] = 14,
	["F"] = 15,
	["G"] = 16,
	["H"] = 17,
	["I"] = 18,
	["J"] = 19,
	["K"] = 20,
	["L"] = 21,
	["M"] = 22,
	["N"] = 23,
	["O"] = 24,
	["P"] = 25,
	["Q"] = 26,
	["R"] = 27,

	["a"] = 10,
	["b"] = 11,
	["c"] = 12,
	["d"] = 13,
	["e"] = 14,
	["f"] = 15,
	["g"] = 16,
	["h"] = 17,
	["i"] = 18,
	["j"] = 19,
	["k"] = 20,
	["l"] = 21,
	["m"] = 22,
	["n"] = 23,
	["o"] = 24,
	["p"] = 25,
	["q"] = 26,
	["r"] = 27

}

-- the following functions based on the so-named ones in Jeija's digilines mod

local reset_meta = function(pos)
	minetest.get_meta(pos):set_string("formspec", "field[channel;Channel;${channel}]")
end

local on_digiline_receive_std = function(pos, node, channel, msg)
	local meta = minetest.get_meta(pos)
	local setchan = meta:get_string("channel")
	if setchan ~= channel then return end
	local num = tonumber(msg)
	if msg == "colon" or msg == "period" or msg == "off" or (num and (num >= 0 and num <= 9)) then
			minetest.swap_node(pos, { name = "led_marquee:marquee_"..msg, param2 = node.param2})
	end
end

-- convert Lua's idea of a UTF-8 char to ISO-8859-1

-- first char is non-break space, 0xA0
local iso_chars=" ¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ"

local get_iso = function(c)
	local hb = string.byte(c,1) or 0
	local lb = string.byte(c,2) or 0
	local dec = lb+hb*256
	local char = dec - 49664
	if dec > 49855 then char = dec - 49856 end
	return char
end

local make_iso = function(s)
	local i = 1
	local s2 = ""
	while i <= string.len(s) do
		if string.byte(s,i) > 159 then
			s2 = s2..string.char(get_iso(string.sub(s, i, i+1)))
			i = i + 2
		else
			s2 = s2..string.sub(s, i, i)
			i = i + 1
		end
	end
	return s2
end

-- scrolling

led_marquee.set_timer = function(pos, timeout)
	local timer = minetest.get_node_timer(pos)
	timer:stop()
	if not timeout or timeout < 0.2 or timeout > 5 then return false end

	if timeout > 0 then
		local meta = minetest.get_meta(pos)
		meta:set_int("timeout", timeout)
		timer:start(timeout)
	end
end

led_marquee.scroll_text = function(pos, elapsed, skip)
	skip = skip or 1
	local meta = minetest.get_meta(pos)
	local msg = meta:get_string("last_msg")
	local channel = meta:get_string("channel")
	local index = meta:get_int("index")
	local color = meta:get_int("last_color")
	local colorchar = color_to_char[color+1]
	if not index or index < 1 then index = 1 end
	local len = string.len(msg)
	index = index + skip
	if index > len then index = 1 end

	-- search backward to find the most recent color code in the string
	local r = index
	while r > 0 and not string.match(string.sub(msg, r, r+1), "/[0-9A-Ra-r]") do
		r = r - 1
	end
	if r == 0 then r = 1 end
	if string.match(string.sub(msg, r, r+1), "/[0-9A-Ra-r]") then
		colorchar = string.sub(msg, r+1, r+1)
	end

	-- search forward to find the next printable symbol after the current index
	local f = index
	while f < len do
		if string.match(string.sub(msg, f-1, f), "/[0-9A-Ra-r]") then
			f = f + 2
		else
			break
		end
	end
	led_marquee.display_msg(pos, channel, "/"..colorchar..string.sub(msg, f)..string.rep(" ", skip + 1))

	meta:set_int("index", f)
	if not elapsed or elapsed < 0.2 then return false end
	return true
end

-- the nodes:

local fdir_to_right = {
	{  0, -1 },
	{  0, -1 },
	{  0, -1 },
	{  0,  1 },
	{  1,  0 },
	{ -1,  0 },
}

local cbox = {
	type = "wallmounted",
	wall_top = { -8/16, 7/16, -8/16, 8/16, 8/16, 8/16 },
	wall_bottom = { -8/16, -8/16, -8/16, 8/16, -7/16, 8/16 },
	wall_side = { -8/16, -8/16, -8/16, -7/16, 8/16, 8/16 }
}

led_marquee.decode_color = function(msg)

end

led_marquee.display_msg = function(pos, channel, msg)
	msg = string.sub(msg, 1, 6144).." "
	if string.sub(msg,1,1) == string.char(255) then -- treat it as incoming UTF-8
		msg = make_iso(string.sub(msg, 2, 6144))
	end

	local master_fdir = minetest.get_node(pos).param2 % 8
	local master_meta = minetest.get_meta(pos)
	local last_color = master_meta:get_int("last_color")
	local pos2 = table.copy(pos)
	if not last_color or last_color < 0 or last_color > 27 then
		last_color = 0
		master_meta:set_int("last_color", 0)
	end
	local i = 1
	local len = string.len(msg)
	local wrapped = nil
	while i <= len do
		local node = minetest.get_node(pos2)
		local fdir = node.param2 % 8
		local meta = minetest.get_meta(pos2)
		local setchan = nil
		if meta then setchan = meta:get_string("channel") end
		local asc = string.byte(msg, i, i)
		if not string.match(node.name, "led_marquee:char_") then
			if not wrapped then
				pos2.x = pos.x
				pos2.y = pos2.y-1
				pos2.z = pos.z
				wrapped = true
			else
				break
			end
		elseif string.match(node.name, "led_marquee:char_")
			and fdir ~= master_fdir or (setchan ~= nil and setchan ~= "" and setchan ~= channel) then
			break
		elseif asc == 10 then
			pos2.x = pos.x
			pos2.y = pos2.y-1
			pos2.z = pos.z
			i = i + 1
			wrapped = nil
		elseif asc == 29 then
			local c = string.byte(msg, i+1, i+1) or 0
			local r = string.byte(msg, i+2, i+2) or 0
			pos2.x = pos.x + (fdir_to_right[fdir+1][1])*c
			pos2.y = pos.y - r
			pos2.z = pos.z + (fdir_to_right[fdir+1][2])*c
			i = i + 3
			wrapped = nil
		elseif asc == 30 then -- translate to slash for printing
			minetest.swap_node(pos2, { name = "led_marquee:char_47", param2 = master_fdir + (last_color*8)})
			pos2.x = pos2.x + fdir_to_right[fdir+1][1]
			pos2.z = pos2.z + fdir_to_right[fdir+1][2]
			i = i + 1
		elseif asc == 47 then -- slash
			local ccode = string.sub(msg, i+1, i+1)
			if ccode then
				if char_to_color[ccode] then
					last_color = char_to_color[ccode]
					i = i + 2
				else
					minetest.swap_node(pos2, { name = "led_marquee:char_47", param2 = master_fdir + (last_color*8)})
					pos2.x = pos2.x + fdir_to_right[fdir+1][1]
					pos2.z = pos2.z + fdir_to_right[fdir+1][2]
					i = i + 1
				end
			end
			master_meta:set_int("last_color", last_color)
			wrapped = nil
		elseif asc > 30 and asc < 256 then
			minetest.swap_node(pos2, { name = "led_marquee:char_"..asc, param2 = master_fdir + (last_color*8)})
			pos2.x = pos2.x + fdir_to_right[fdir+1][1]
			pos2.z = pos2.z + fdir_to_right[fdir+1][2]
			i = i + 1
			wrapped = nil
		else
			i = i + 1
		end
	end
end

local on_digiline_receive_string = function(pos, node, channel, msg)
	local meta = minetest.get_meta(pos)
	local setchan = meta:get_string("channel")
	local last_color = meta:get_int("last_color")
	if not last_color or last_color < 0 or last_color > 27 then
		last_color = 0
		meta:set_int("last_color", 0)
	end
	local fdir = node.param2 % 8

	if setchan ~= channel then return end
	if msg and msg ~= "" and type(msg) == "string" then
		if string.len(msg) > 1 then
			if msg == "clear" then
				led_marquee.set_timer(pos, 0)
				msg = string.rep(" ", 2048)
				meta:set_string("last_msg", msg)
				led_marquee.display_msg(pos, channel, msg)
				meta:set_int("index", 1)
			elseif msg == "allon" then
				led_marquee.set_timer(pos, 0)
				msg = string.rep(string.char(144), 2048)
				meta:set_string("last_msg", msg)
				led_marquee.display_msg(pos, channel, msg)
				meta:set_int("index", 1)
			elseif msg == "start_scroll" then
				local timeout = meta:get_int("timeout")
				led_marquee.set_timer(pos, timeout)
			elseif msg == "stop_scroll" then
				led_marquee.set_timer(pos, 0)
				return
			elseif string.sub(msg, 1, 12) == "scroll_speed" then
				local timeout = tonumber(string.sub(msg, 13))
				led_marquee.set_timer(pos, timeout)
			elseif string.sub(msg, 1, 11) == "scroll_step" then
				local skip = tonumber(string.sub(msg, 12))
				led_marquee.scroll_text(pos, nil, skip)
			elseif msg == "get" then -- get the master panel's displayed char as ASCII numerical value
				digilines.receptor_send(pos, digiline.rules.default, channel, tonumber(string.match(minetest.get_node(pos).name,"led_marquee:char_(.+)"))) -- wonderfully horrible string manipulaiton
			elseif msg == "getstr" then -- get the last stored message
				digilines.receptor_send(pos, digiline.rules.default, channel, meta:get_string("last_msg"))
			elseif msg == "getindex" then -- get the scroll index
				digilines.receptor_send(pos, digiline.rules.default, channel, meta:get_int("index"))
			else
				msg = string.gsub(msg, "//", string.char(30))
				led_marquee.set_timer(pos, 0)
				local last_msg = meta:get_string("last_msg")
				meta:set_string("last_msg", msg)
				led_marquee.display_msg(pos, channel, msg)
				if last_msg ~= msg then
					meta:set_int("index", 1)
				end
			end
		else
			local asc = string.byte(msg)
			if asc > 29 and asc < 256 then
				minetest.swap_node(pos, { name = "led_marquee:char_"..asc, param2 = fdir + (last_color*8)})
				meta:set_string("last_msg", tostring(msg))
				meta:set_int("index", 1)
			end
		end
	elseif msg and type(msg) == "number" then
		meta:set_string("last_msg", tostring(msg))
		led_marquee.display_msg(pos, channel, tostring(msg))
		meta:set_int("index", 1)
	end
end

-- the nodes!

for i = 31, 255 do
	local groups = { cracky = 2, not_in_creative_inventory = 1}
	local light = LIGHT_MAX-2
	local description = S("LED marquee panel ("..i..")")
	local leds = "led_marquee_char_"..i..".png^[mask:led_marquee_leds_on.png"

	if i == 31 then
		leds ={
			name = "led_marquee_char_31.png^[mask:led_marquee_leds_on_cursor.png",
			animation = {type = "vertical_frames", aspect_w = 32, aspect_h = 32, length = 0.75}
		}
	end

	local wimage

	if i == 32 then
		groups = {cracky = 2}
		light = nil
		description = S("LED marquee panel")
		wimage = "led_marquee_leds_off.png^(led_marquee_char_155.png^[multiply:red)"
	end

	minetest.register_node("led_marquee:char_"..i, {
		description = description,
		drawtype = "mesh",
		mesh = "led_marquee.obj",
		tiles = {
			{ name = "led_marquee_base.png", color = "white" },
			{ name = "led_marquee_leds_off.png", color = "white" }
		},
		overlay_tiles = { "", leds },
		inventory_image = wimage,
		wield_image = wimage,
		palette="led_marquee_palette.png",
		use_texture_alpha = "clip",
		groups = groups,
		paramtype = "light",
		paramtype2 = "colorwallmounted",
		light_source = light,
		selection_box = cbox,
		node_box = cbox,
		on_construct = function(pos)
			reset_meta(pos)
		end,
		on_receive_fields = function(pos, formname, fields, sender)
			local name = sender:get_player_name()
			if minetest.is_protected(pos, name) and not minetest.check_player_privs(name, {protection_bypass=true}) then
				minetest.record_protection_violation(pos, name)
				return
			end
			if (fields.channel) then
				minetest.get_meta(pos):set_string("channel", fields.channel)
			end
		end,
		digiline = {
			receptor = {},
			effector = {
				action = on_digiline_receive_string,
			},
		},
		drop = "led_marquee:char_32",
		on_timer = led_marquee.scroll_text
	})
end

-- crafts

minetest.register_craft({
	output = "led_marquee:char_32 6",
	recipe = {
		{ "default:glass", "default:glass", "default:glass" },
		{ "mesecons_lamp:lamp_off", "mesecons_lamp:lamp_off", "mesecons_lamp:lamp_off" },
		{ "group:wood", "mesecons_microcontroller:microcontroller0000", "group:wood" }
	},
})


code:
name = led_marquee
author = VanessaE
description = This mod provides a simple LED marquee that accepts single characters, strings, and special control words via digilines.
depends = default, digilines, mesecons_luacontroller
optional_depends = mesecons_microcontroller, mesecons_lamp


code:
# lootchests_modpack
Modpack adding various loot containers across the world to be found and providing an API, resources and integrations for other mods.

![Screenshot](screenshot.png)

## Requirements

- Minetest 5.0.0+
- Minetest_game 5.0.0+
- [magic_materials](https://github.com/ClockGen/magic_materials) (optional)

## Recommended mods
- [decorations_sea](https://github.com/ClockGen/decorations_sea) (Provides decorations when looking for ocean chests)
- [shipwrecks](https://github.com/ClockGen/shipwrecks) (Shipwrecks depend on lootchests_modpack to spawn lootchests in shipwrecks)

## Integrations
lootchests_default adds loot from following mods, if they are installed:

- 3d_armor
- Bonemeal
- farming_redo
- gadgets_modpack
- moreores
- moretrees

## Contents
Default lootchest pack provides:

- Baskets, spawning in caves from 0 to -128, containing various organic items (food, seeds, saplings, etc)
- Urns, spawning in caves from 0 to -128, containing various low-grade craftitems and tools (stone tools, ores up to iron, sticks, etc)
- Ocean chests, spawning in seabed and also very rare in caves, containing many precious materials and tools
- Ancient chests, spawning from -128 to -4096 in solid rock and also very rare in caverns, containing very expensive loot

Magic_materials pack provides:
- Rune urn, spawning in caves from -64 to -4096, containing low grade magic items from magic_materials
- rune chest, spawning from -64 to -4096 in solid rock and very rare in caverns, contains high grade items from magic_materials

## Making your own lootchests
Process of making your own lootchests is described in [documentation](lootchests/documentation.txt)

## License
All code is licensed under GPLv3 [link to the license](https://www.gnu.org/licenses/gpl-3.0.en.html)
All resources are licensed under CC BY 4.0 [link to the license](https://creativecommons.org/licenses/by/4.0/legalcode)


code: Modpack adding a lot of various loot containers across the world to be found and providing an API, resources and integrations for other mods


code:
lootchests = {}

lootchests.loot_table = {}

local debug = minetest.settings:get("lootchests_debug") or false

local function list_length(list)
    local count = 0
    for _,_ in pairs(list) do
        count = count + 1
    end
    return count
end

lootchests.add_to_loot_table = function(key, add)
    if not lootchests.loot_table[key] then
        lootchests.loot_table[key] = {}
    end
    for _,v in pairs(add) do
        table.insert(lootchests.loot_table[key], v)
    end
end

lootchests.register_lootchest = function(def)

    if not def.name or not def.description then
        minetest.log("error", "[lootchests] Missing fields in chest definition!")
        return
    end

    if not lootchests.loot_table[def.name] then
        minetest.log("error", "[lootchests] Missing loot table for " .. def.name .. "!")
        return
    end

    local tiles = def.tiles or {
        "default_chest_top.png",
        "default_chest_top.png",
        "default_chest_side.png",
        "default_chest_side.png",
        "default_chest_front.png",
    }

    local sounds = def.sounds or default.node_sound_wood_defaults()
    local groups = def.groups or {choppy = 2, oddly_breakable_by_hand = 2}
    local rarity = def.spawn_in_rarity or 512
    local fill_ratio = def.spawn_on_rarity or 512
    local ymax = def.ymax or 31000
    local ymin = def.ymin or -31000
    local slot_spawn_chance = def.slot_spawn_chance or 0.25
    local slots = def.slots or 32
    local node_box = def.node_box or {
        type = "fixed",
        fixed = {-0.5, -0.5, -0.5, 0.5, 0.5, 0.5},
    }

    local marker_drawtype = "airlike"
    local marker_groups = {dig_immediate = 2, not_in_creative_inventory = 1}
    if debug then
        marker_drawtype = nil
        marker_groups = {dig_immediate = 2}
    end

    minetest.register_node(def.name .. "_marker", {
        drawtype = marker_drawtype,
        description = def.description .. " Spawn Marker",
        tiles = {"lootchests_marker_top.png", "lootchests_marker_side.png"},
        groups = marker_groups,
        paramtype2 = "facedir",
    })

    minetest.register_node(def.name, {
        description = def.description,
        drawtype = def.drawtype,
        tiles = tiles,
        node_box = node_box,
        selection_box = node_box,
        groups = groups,
        sounds = sounds,
        paramtype = "light",
        on_construct = function(pos)
            local meta = minetest.get_meta(pos)
            meta:set_string("formspec",
                "size[8,9]" ..
                default.gui_bg ..
                default.gui_bg_img ..
                default.gui_slots ..
                "list[current_name;main;0,0;8,5;]" ..
                "list[current_player;main;0,5;8,4;]")
            meta:set_string("infotext", def.description)
            local inv = meta:get_inventory()
            inv:set_size("main", slots)
        end,
        can_dig = function(pos,player)
            local meta = minetest.get_meta(pos)
            local inv = meta:get_inventory()
            return inv:is_empty("main")
        end,
    })

    if not debug then
        minetest.register_lbm({
            label = "Upgrade " .. def.description,
            name = def.name .. "_marker_replace",
            nodenames = def.name .. "_marker",
            run_at_every_load = true,
            action = function(pos, node)
                minetest.set_node(pos, {name = def.name, param2 = minetest.get_node(pos).param2})
                local rand = PcgRandom(pos.x * pos.y * pos.z)
                local inv = minetest.get_inventory({type = "node", pos = pos})
                for i = 1, slots do
                    if rand:next(0,100) <= slot_spawn_chance then
                        local item_def = lootchests.loot_table[def.name][rand:next(1, #lootchests.loot_table[def.name])]
                        local stack = ItemStack(item_def[1])
                        if minetest.registered_tools[item_def[1]] then
                            stack:set_wear(rand:next(1,65535))
                        else
                            stack:set_count(rand:next(1, item_def[2]))
                        end
                        inv:set_stack("main", i, stack)
                    end
                end
            end,
        })
    end

    if def.spawn_in then
        minetest.register_ore({
            ore_type = "scatter",
            ore = def.name .. "_marker",
            wherein = def.spawn_in,
            clust_scarcity = rarity * rarity * rarity,
            clust_num_ores = 1,
            clust_size = 25,
            y_min = ymin,
            y_max = ymax,
        })
    end

    if def.spawn_on then
        minetest.register_decoration({
            deco_type = "simple",
            place_on = def.spawn_on,
            sidelen = 16,
            fill_ratio = 1/fill_ratio,
            y_min = ymin,
            y_max = ymax,
            flags = "force_placement, all_floors",
            decoration = def.name .. "_marker",
        })
    end
end


code:
To spawn the lootchests, world is populated by spawn chest markers of corresponding type.
During the normal gameplay, they are of "airlike" type and are replaced with LBM which replaces them with corresponding chests and fill them with loot.
If minetest.conf setting "lootchests_debug = true" is active, markers are not replaced and can be taken from the creative inventory.
This can be used for placing them manually for various purposes, mainly for adding lootchest markers to schematics.

Creating a lootchest consists of two parts: defining the lootchest itself with lootchests.register_lootchest(def)
and creating a corresponding loot table in lootchests.loot_table[def.name]. Examples are below.

Example of a lootchest definition:
lootchests.register_lootchest({
    --Mandatory
    name = "lootchests:default_chest",                      --ID of the lootchest. A table with corresponding key should be created in tables.loot_table
    description = "Loot Chest",                             --Name as it appears in inventory, also lootchest infobox

    --Visuals
    drawtype = "nodebox",                                   --Drawtype of the node. Don't define for default node look
    node_box = {
        type = "fixed",
        fixed = {-0.5, -0.5, -0.5, 0.5, 0.5, 0.5},
    },                                                      --Nodebox def if drawtype is "nodebox"
    tiles = {
        "default_chest_top.png",
        "default_chest_top.png",
        "default_chest_side.png",
        "default_chest_side.png",
        "default_chest_front.png",
    },                                                      --Node tile definition

    --Sounds and groups
    sounds = default.node_sound_wood_defaults(),            --Node sound definition
    groups = {choppy = 2, oddly_breakable_by_hand = 2},     --Node groups

    --Spawning
    spawn_in = {"default:sand", "default:desert_sand"},     --Spawn in which nodes, may be one itemstring or a list of them
    spawn_on = {"default:stone", "default:desert_stone"},   --Spawn on which nodes, may be one itemstring or a list of them
    spawn_in_rarity = 25,                                   --Spawn in rarity, approximate spacing between each node
    spawn_on_rarity = 1000.                                 --Spawn on rarity, spawns one out of defined value (if default is 1000 then on 1 out of 1000)

    --Height limit
    ymax = 5,                                               --Max Y limit
    ymin = -64,                                             --Min Y limit

    --Container parameters
    slot_spawn_chance = 50,                                 --Chance to spawn something in a container slot. Ranges from 0 to 100
    slots = 32,                                             --Total amount of slots in the container. Max is 32
})

To actually add loot to your lootchests, you need to define a loot table in lootchests.loot_table. Table must be accessible by the key which equals to
the lootchest ID.

Example of a loot table definition for "lootchests:default_chest":
lootchests.loot_table["lootchests:default_chest"] = {
    {"default:stick", 16},                                  --Each item definition is a table with 2 elements - their itemstring and maximum amount
    {"default:axe_stone"},                                  --If defined item is a tool, it doesn't need an amount definition, there will always be one anyway
                                                            --Tools spawn with random wear instead
    {"default:stone", 64},
}

Third-party mods can also access and modify loot tables, you can add custom items to them manually, or by using
lootchests.add_to_loot_table(key, items), where "key" is the lootchest ID and "items" is another table with loot.

Example:

local items = {
    {"custom_mod:custom_item_01", 15},
    {"custom_mod:custom_item_02", 32},
}

lootchests.add_to_loot_table("lootchests:default_chest", items)


code:
name = lootchests
depends = default
description = An API for registering various loot containers
author = ClockGen


code:
lootchests.register_lootchest({
    name = "lootchests_default:ocean_chest",
    description = "Loot Chest",
    spawn_in = {"default:sand", "default:desert_sand", "default:silver_sand"},
    spawn_on = {"default:stone",},
    sounds = default.node_sound_wood_defaults(),
    groups = {choppy = 2, oddly_breakable_by_hand = 2},
    ymax = 5,
    ymin = -512,
    spawn_in_rarity = 1600,
    spawn_on_rarity = 100000,
    slot_spawn_chance = 75,
    slots = 32,
})

lootchests.register_lootchest({
    name = "lootchests_default:basket",
    description = "Basket",
    tiles = {
        "lootchests_default_basket_top.png",
        "lootchests_default_basket_top.png",
        "lootchests_default_basket_side.png",
    },
    spawn_on = {"default:stone"},
    sounds = default.node_sound_wood_defaults(),
    groups = {snappy = 2, oddly_breakable_by_hand = 2},
    ymax = 0,
    ymin = -256,
    spawn_on_rarity = 28000,
    slot_spawn_chance = 35,
    slots = 24,
})

lootchests.register_lootchest({
    name = "lootchests_default:urn",
    description = "Urn",
    tiles = {
        "lootchests_default_urn_top.png",
        "lootchests_default_urn_top.png",
        "lootchests_default_urn_side.png",
    },
    spawn_on = {"default:stone"},
    sounds = default.node_sound_stone_defaults(),
    groups = {cracky = 2, oddly_breakable_by_hand = 2},
    ymax = 0,
    ymin = -256,
    spawn_on_rarity = 18000,
    slot_spawn_chance = 50,
    slots = 24,
})

lootchests.register_lootchest({
    name = "lootchests_default:barrel",
    description = "Barrel",
    tiles = {
        "lootchests_default_barrel_top.png",
        "lootchests_default_barrel_top.png",
        "lootchests_default_barrel_side.png",
    },
    spawn_in = {"default:sand", "default:desert_sand", "default:silver_sand"},
    sounds = default.node_sound_wood_defaults(),
    groups = {choppy = 2, oddly_breakable_by_hand = 2},
    ymax = 5,
    ymin = -32,
    spawn_in_rarity = 1400,
    slot_spawn_chance = 50,
    slots = 24,
})

lootchests.register_lootchest({
    name = "lootchests_default:stone_chest",
    description = "Ancient Chest",
    tiles = {
        "lootchests_default_stone_chest_top.png",
        "lootchests_default_stone_chest_top.png",
        "lootchests_default_stone_chest_side.png",
        "lootchests_default_stone_chest_side.png",
        "lootchests_default_stone_chest_front.png",
    },
    spawn_in = {"default:stone"},
    spawn_on = {"default:stone"},
    sounds = default.node_sound_stone_defaults(),
    groups = {cracky = 2},
    ymax = -128,
    ymin = -4096,
    spawn_in_rarity = 1250,
    spawn_on_rarity = 125000,
    slot_spawn_chance = 75,
    slots = 32,
})


code:
local path = minetest.get_modpath("lootchests_default")
dofile(path .. "/item_tables.lua")
dofile(path .. "/chests.lua")

if minetest.get_modpath("3d_armor") then
    dofile(path .. "/mod_support/3d_armor.lua")
end

if minetest.get_modpath("shields") then
    dofile(path .. "/mod_support/3d_armor_shields.lua")
end

if minetest.get_modpath("bonemeal") then
    dofile(path .. "/mod_support/bonemeal.lua")
end

if minetest.get_modpath("farming") and farming and farming.mod == "redo" then
    dofile(path .. "/mod_support/farming_redo.lua")
end

if minetest.get_modpath("gadgets_consumables") then
    dofile(path .. "/mod_support/gadgets_modpack.lua")
end

if minetest.get_modpath("moreores") then
    dofile(path .. "/mod_support/moreores.lua")
end

if minetest.get_modpath("moretrees") then
    dofile(path .. "/mod_support/moretrees.lua")
end


code:
lootchests.loot_table["lootchests_default:ocean_chest"] = {
    {"default:stick", 16},
    {"default:paper", 4},
    {"default:book", 3},
    {"default:skeleton_key", 1},
    {"default:flint", 4},
    {"default:obsidian_shard", 24},
    {"default:clay_brick", 32},
    {"default:mese_crystal_fragment", 16},
    {"default:gold_ingot", 8},
    {"default:bronze_ingot", 8},
    {"default:tin_ingot", 8},
    {"default:copper_ingot", 8},
    {"default:steel_ingot", 8},
    {"default:coal_lump", 16},
    {"default:pick_stone"},
    {"default:pick_bronze"},
    {"default:pick_steel"},
    {"default:shovel_stone"},
    {"default:shovel_bronze"},
    {"default:shovel_steel"},
    {"default:axe_stone"},
    {"default:axe_bronze"},
    {"default:axe_steel"},
    {"default:sword_stone"},
    {"default:sword_bronze"},
    {"default:sword_steel"},
    {"default:bookshelf", 16},
    {"signs:sign", 4},
    {"default:ladder_wood", 64},
    {"default:ladder_steel", 48},
    {"default:glass", 32},
    {"default:obsidian_glass", 16},
    {"tnt:gunpowder", 16},
    {"vessels:glass_bottle", 16},
    {"vessels:drinking_glass", 16},
    {"screwdriver:screwdriver"},
    {"map:mapping_kit", 1},
    {"fire:flint_and_steel"},
    {"dye:white", 8},
    {"dye:grey", 8},
    {"dye:dark_grey", 8},
    {"dye:black", 8},
    {"dye:violet", 8},
    {"dye:blue", 8},
    {"dye:cyan", 8},
    {"dye:dark_green", 8},
    {"dye:green", 8},
    {"dye:yellow", 8},
    {"dye:brown", 8},
    {"dye:orange", 8},
    {"dye:red", 8},
    {"dye:magenta", 8},
    {"dye:pink", 8},
    {"wool:white", 32},
    {"carts:rail", 48},
    {"carts:powerrail", 16},
    {"carts:brakerail", 16},
    {"carts:cart", 1},
    {"mesecraft_bucket:bucket_empty", 4},
    {"boats:boat", 1},
    {"mesecraft_binoculars:binoculars", 1},
}

lootchests.loot_table["lootchests_default:basket"] = {
    {"default:stick", 16},
    {"group:leaves", 8},
    {"default:tree", 4},
    {"default:jungletree", 4},
    {"default:pine_tree", 4},
    {"default:cactus", 4},
    {"default:large_cactus_seedling", 2},
    {"default:papyrus", 8},
    {"default:junglegrass", 4},
    {"default:blueberry_bush_sapling", 4},
    {"default:sapling", 1},
    {"default:junglesapling", 1},
    {"default:pine_sapling", 1},
    {"default:acacia_sapling", 1},
    {"default:aspen_sapling", 1},
    {"farming:wheat", 8},
    {"farming:flour", 16},
    {"farming:bread", 4},
    {"farming:cotton", 4},
    {"farming:string", 4},
    {"flowers:mushroom_red", 8},
    {"flowers:mushroom_brown", 8},
    {"flowers:waterlily", 4},
}

lootchests.loot_table["lootchests_default:urn"] = {
    {"default:stick", 16},
    {"default:paper", 4},
    {"default:flint", 4},
    {"default:clay_lump", 16},
    {"default:tin_lump", 8},
    {"default:copper_lump", 8},
    {"default:iron_lump", 8},
    {"default:coal_lump", 16},
    {"default:pick_stone"},
    {"default:shovel_stone"},
    {"default:axe_stone"},
    {"default:sword_stone"},
    {"signs:sign", 1},
    {"default:ladder_wood", 8},
    {"vessels:glass_bottle", 4},
    {"fire:flint_and_steel"},
    {"wool:white", 8},
    {"farming:wheat", 8},
    {"farming:flour", 16},
    {"default:tree", 2},
    {"default:jungletree", 2},
    {"default:pine_tree", 2},
}

lootchests.loot_table["lootchests_default:barrel"] = lootchests.loot_table["lootchests_default:urn"]

lootchests.loot_table["lootchests_default:stone_chest"] = {
    {"default:coalblock", 4},
    {"default:steelblock", 2},
    {"default:copperblock", 2},
    {"default:tinblock", 2},
    {"default:bronzeblock", 2},
    {"default:mese", 2},
    {"default:meselamp", 4},
    {"default:stick", 16},
    {"default:paper", 4},
    {"default:book", 3},
    {"default:skeleton_key", 1},
    {"default:flint", 4},
    {"default:obsidian", 24},
    {"default:clay_brick", 32},
    {"default:mese_crystal", 16},
    {"default:diamond", 16},
    {"default:gold_ingot", 16},
    {"default:bronze_ingot", 16},
    {"default:tin_ingot", 16},
    {"default:copper_ingot", 16},
    {"default:steel_ingot", 16},
    {"default:coal_lump", 32},
    {"default:pick_bronze"},
    {"default:pick_steel"},
    {"default:pick_mese"},
    {"default:shovel_bronze"},
    {"default:shovel_steel"},
    {"default:shovel_mese"},
    {"default:axe_bronze"},
    {"default:axe_steel"},
    {"default:axe_mese"},
    {"default:sword_bronze"},
    {"default:sword_steel"},
    {"default:sword_mese"},
    {"default:glass", 32},
    {"default:obsidian_glass", 16},
    {"tnt:gunpowder", 32},
    {"vessels:glass_bottle", 32},
    {"vessels:drinking_glass", 32},
    {"fire:flint_and_steel"},
    {"carts:rail", 64},
    {"carts:powerrail", 32},
    {"carts:brakerail", 32},
    {"carts:cart", 2},
    {"mesecraft_bucket:bucket_empty", 8},
    {"boats:boat", 2},
}


code:
name = lootchests_default
title = Lootchests Default
description = Part of lootchests_modpack, providing lootchests for minetest_game, as well as integration with some mods
author = ClockGen
depends = default, lootchests, tnt, vessels, screwdriver, map, fire, dye, wool, carts, mesecraft_bucket, boats, mesecraft_binoculars, farming, flowers
optional_depends = 3d_armor, shields, bonemeal, moreores, moretrees, gadgets_consumables


code:
local cheap_items = {
    {"3d_armor:helmet_wood"},
    {"3d_armor:chestplate_wood"},
    {"3d_armor:leggings_wood"},
    {"3d_armor:boots_wood"},
    {"3d_armor:helmet_cactus"},
    {"3d_armor:chestplate_cactus"},
    {"3d_armor:leggings_cactus"},
    {"3d_armor:boots_cactus"},
}

local medium_items = {
    {"3d_armor:helmet_steel"},
    {"3d_armor:chestplate_steel"},
    {"3d_armor:leggings_steel"},
    {"3d_armor:boots_steel"},
    {"3d_armor:helmet_bronze"},
    {"3d_armor:chestplate_bronze"},
    {"3d_armor:leggings_bronze"},
    {"3d_armor:boots_bronze"},
}

local expensive_items = {
    {"3d_armor:helmet_gold"},
    {"3d_armor:chestplate_gold"},
    {"3d_armor:leggings_gold"},
    {"3d_armor:boots_gold"},
    {"3d_armor:helmet_diamond"},
    {"3d_armor:chestplate_diamond"},
    {"3d_armor:leggings_diamond"},
    {"3d_armor:boots_diamond"},
}

lootchests.add_to_loot_table("lootchests_default:ocean_chest", medium_items)
lootchests.add_to_loot_table("lootchests_default:urn", cheap_items)
lootchests.add_to_loot_table("lootchests_default:stone_chest", expensive_items)


code:
local cheap_items = {
    {"shields:shield_wood"},
    {"shields:shield_enhanced_wood"},
    {"shields:shield_cactus"},
    {"shields:shield_enhanced_cactus"},
}

local medium_items = {
    {"shields:shield_steel"},
    {"shields:shield_bronze"},
}

local expensive_items = {
    {"shields:shield_gold"},
    {"shields:shield_diamond"},
}

lootchests.add_to_loot_table("lootchests_default:ocean_chest", medium_items)
lootchests.add_to_loot_table("lootchests_default:urn", cheap_items)
lootchests.add_to_loot_table("lootchests_default:stone_chest", expensive_items)


code:
local items = {
    {"bonemeal:bonemeal", 8},
    {"bonemeal:mulch", 8},
    {"bonemeal:fertiliser", 8},
    {"bonemeal:bone", 4},
    {"bonemeal:gelatin_powder", 8},
}

lootchests.add_to_loot_table("lootchests_default:basket", items)


code:
local items = {
    {"farming:seed_wheat", 4},
    {"farming:tomato", 2},
    {"farming:rye", 4},
    {"farming:oat", 4},
    {"farming:rice", 4},
    {"farming:rhubarb", 2},
    {"farming:raspberries", 4},
    {"farming:pumpkin", 1},
    {"farming:potato", 4},
    {"farming:pineapple", 2},
    {"farming:peppercorn", 4},
    {"farming:pepper", 4},
    {"farming:pea_pod", 4},
    {"farming:peas", 8},
    {"farming:onion", 4},
    {"farming:melon", 1},
    {"farming:seed_hemp", 4},
    {"farming:hemp_leaf", 8},
    {"farming:hemp_rope", 4},
    {"farming:grapes", 6},
    {"farming:garlic_clove", 2},
    {"farming:garlic", 4},
    {"farming:cucumber", 2},
    {"farming:seed_cotton", 4},
    {"farming:corn", 8},
    {"farming:coffee_beans", 8},
    {"farming:cocoa_beans", 8},
    {"farming:chili_pepper", 2},
    {"farming:carrot", 4},
    {"farming:blueberries", 8},
    {"farming:beetroot", 8},
    {"farming:beans", 8},
    {"farming:seed_barley", 4},
    {"farming:barley", 8},
}

lootchests.add_to_loot_table("lootchests_default:basket", items)


code:
local chest_items = {
    {"gadgets_consumables:potion_speed_01", 2},
    {"gadgets_consumables:potion_jump_01", 2},
    {"gadgets_consumables:potion_gravity_01", 2},
    {"gadgets_consumables:potion_dispel", 2},
    {"gadgets_consumables:potion_health_regen_01", 2},
    {"gadgets_consumables:potion_water_breath_01", 2},
    {"gadgets_consumables:water_bottle", 2},
}

local stone_chest_items = {
    {"gadgets_consumables:potion_teleport", 2},
    {"gadgets_consumables:potion_fire_shield_02", 2},
    {"gadgets_consumables:potion_speed_02", 2},
    {"gadgets_consumables:potion_jump_02", 2},
    {"gadgets_consumables:potion_gravity_02", 2},
    {"gadgets_consumables:potion_dispel", 2},
    {"gadgets_consumables:potion_health_regen_02", 2},
    {"gadgets_consumables:potion_water_breath_02", 2},
}

lootchests.add_to_loot_table("lootchests_default:ocean_chest", chest_items)
lootchests.add_to_loot_table("lootchests_default:stone_chest", stone_chest_items)

if minetest.get_modpath("lootchests_magic_materials") then
    lootchests.add_to_loot_table("lootchests_magic_materials:rune_urn", chest_items)
    lootchests.add_to_loot_table("lootchests_magic_materials:rune_chest", stone_chest_items)
end


code:
local chest_items = {
    {"moreores:silver_ingot", 8},
    {"moreores:mithril_lump", 2},
    {"moreores:sword_silver"},
    {"moreores:axe_silver"},
    {"moreores:shovel_silver"},
    {"moreores:pick_silver"},
}

local urn_items = {
    {"moreores:silver_lump", 8},
}

local stone_chest_items = {
    {"moreores:sword_silver"},
    {"moreores:axe_silver"},
    {"moreores:shovel_silver"},
    {"moreores:pick_silver"},
    {"moreores:sword_mithril"},
    {"moreores:axe_mithril"},
    {"moreores:shovel_mithril"},
    {"moreores:pick_mithril"},
    {"moreores:mithril_ingot", 4},
    {"moreores:silver_block", 2},
}

lootchests.add_to_loot_table("lootchests_default:ocean_chest", chest_items)
lootchests.add_to_loot_table("lootchests_default:urn", urn_items)
lootchests.add_to_loot_table("lootchests_default:stone_chest", stone_chest_items)


code:
local items = {
    {"moretrees:acorn", 8},
    {"moretrees:acorn_muffin_batter", 4},
    {"moretrees:cedar_nuts", 16},
    {"moretrees:cedar_cone", 4},
    {"moretrees:coconut", 2},
    {"moretrees:date", 16},
    {"moretrees:date_nut_batter", 4},
    {"moretrees:fir_cone", 4},
    {"moretrees:fir_nuts", 8},
    {"moretrees:spruce_cone", 4},
    {"moretrees:spruce_nuts", 8},
    {"moretrees:rubber_tree_sapling", 2},
    {"moretrees:sequoia_sapling", 2},
    {"moretrees:spruce_sapling", 2},
    {"moretrees:willow_sapling", 2},
    {"moretrees:poplar_sapling", 2},
    {"moretrees:palm_sapling", 2},
    {"moretrees:oak_sapling", 2},
    {"moretrees:fir_sapling", 2},
    {"moretrees:date_palm_sapling", 2},
    {"moretrees:apple_tree_sapling", 2},
    {"moretrees:birch_sapling", 2},
    {"moretrees:cedar_sapling", 2},
    {"moretrees:beech_sapling", 2},
}

lootchests.add_to_loot_table("lootchests_default:basket", items)


code:
lootchests.register_lootchest({
    name = "lootchests_magic_materials:rune_chest",
    description = "Rune Chest",
    tiles = {
        "lootchests_magic_materials_rune_chest_top.png",
        "lootchests_magic_materials_rune_chest_top.png",
        "lootchests_magic_materials_rune_chest_side.png",
        "lootchests_magic_materials_rune_chest_side.png",
        "lootchests_magic_materials_rune_chest_front.png",
    },
    spawn_in = "default:stone",
    spawn_on = "default:stone",
    sounds = default.node_sound_stone_defaults(),
    groups = {cracky = 2, oddly_breakable_by_hand = 2},
    ymax = -64,
    ymin = -16000,
    spawn_in_rarity = 10240,
    spawn_on_rarity = 400000,
    slot_spawn_chance = 75,
    slots = 24,
})

lootchests.register_lootchest({
    name = "lootchests_magic_materials:rune_urn",
    description = "Rune Urn",
    tiles = {
        "lootchests_magic_materials_rune_urn_top.png",
        "lootchests_magic_materials_rune_urn_top.png",
        "lootchests_magic_materials_rune_urn_side.png",
    },
    spawn_on = "default:stone",
    sounds = default.node_sound_stone_defaults(),
    groups = {cracky = 2, oddly_breakable_by_hand = 2},
    ymax = -64,
    ymin = -1600,
    spawn_on_rarity = 144000,
    slot_spawn_chance = 50,
    slots = 24,
})


code:
default
lootchests
magic_materials


code: Part of lootchests_modpack, providing lootchests with items from magic_materials


code:
local path = minetest.get_modpath("lootchests_magic_materials")
dofile(path .. "/item_tables.lua")
dofile(path .. "/chests.lua")


code:
lootchests.loot_table["lootchests_magic_materials:rune_chest"] = {
    {"magic_materials:februm_crystal", 16},
    {"magic_materials:egerum_crystal", 16},
    {"magic_materials:arcanite_crystal", 16},
    {"magic_materials:enchanted_page", 2},
    {"magic_materials:enchanted_rune", 2},
    {"magic_materials:enchanted_tome", 1},
    {"magic_materials:enchanted_staff", 1},
    {"magic_materials:magic_root", 16},
    {"magic_materials:magic_flower", 16},
    {"magic_materials:magic_mushroom", 16},
    {"magic_materials:arcanite_fragments", 16},
    {"magic_materials:februm_block", 2},
    {"magic_materials:egerum_block", 2},
    {"magic_materials:arcanite_block", 2},
    {"magic_materials:pickaxe_arcanite"},
    {"magic_materials:axe_arcanite"},
    {"magic_materials:shovel_arcanite"},
    {"magic_materials:sword_arcanite"},
    {"magic_mirror:magic_mirror"},
}

lootchests.loot_table["lootchests_magic_materials:rune_urn"] = {
    {"magic_materials:februm_crystal", 16},
    {"magic_materials:egerum_crystal", 16},
    {"magic_materials:enchanted_page", 2},
    {"magic_materials:enchanted_rune", 2},
    {"magic_materials:magic_root", 8},
    {"magic_materials:magic_flower", 8},
    {"magic_materials:magic_mushroom", 8},
    {"magic_materials:arcanite_fragments", 16},
    {"magic_materials:februm_block", 1},
    {"magic_materials:egerum_block", 1},
}


code:
name = lootchests_magic_materials
depends = default, lootchests, magic_materials
description = Part of lootchests_modpack, providing lootchests with items from magic_materials


code:
# ma_pops_furniture

A revamp of Ma and Pop's Furniture Mod

================================================================

Credits:
Radio Mod Code (Mihobre)
BlockMen (Lots and lots)    |For helping me alot with the code
                            |Letting me experiment with his mod
                            |Supporting me all the time
                            |Encouraging me
DanJohansen(Newgrounds)     |All music used

Nathan.S		     Helped with a ton of the coding and models making this mod possible

Toaster Code:
Special thanks to GreenDimond for the code.

Freezer Mod:
https://forum.minetest.net/viewtopic.php?f=9&t=14925

Textures (c) 2016 Gabriel Pérez-Cerezo, WTFPL

This mod is based on the default furnace, license for the code below:

Copyright (C) 2011-2012 celeron55, Perttu Ahola <celeron55@gmail.com>
Copyright (C) 2016 Gabriel Pérez-Cerezo <gabriel@gpcf.eu>

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.

================================================================

Special thanks to:
Wizzerine
BillyS
Noodlemire

For helping out with the microwave.

=================================================================

Each item used as tool and what it does:

Hammer: Rotates and/or changes the type of sofa.

Shears: "Trims" the hedges making it possible for different rotation and direction of hedge.

==================================================================
License of source code and textures: WTFPL
------------------------------------------------------------------
This program is free software. It comes without any warranty, to
the extent permitted by applicable law. You can redistribute it
and/or modify it under the terms of the do whatever you want with it.
To Public License, Version 2, as published by Sam Hocevar. See
http://sam.zoy.org/wtfpl/COPYING for more details.
==================================================================