code:
if not minetest.settings:get_bool("commoditymarket_enable_caravan_market", true) then
	return
end

local S = commoditymarket_fantasy.S
local coins_per_ingot = commoditymarket_fantasy.coins_per_ingot
local default_items = commoditymarket_fantasy.default_items
local gold_ingot = commoditymarket_fantasy.gold_ingot
local gold_currency = commoditymarket_fantasy.gold_currency
local chest_locked = commoditymarket_fantasy.chest_locked
local wood_sounds = commoditymarket_fantasy.wood_sounds
local usage_help = commoditymarket_fantasy.usage_help

commoditymarket_fantasy.register_gold_coins()

local time_until_caravan = 120 -- caravan arrives in two minutes
local dwell_time = 600 -- caravan leaves ten minutes after last usage

local caravan_def = {
	description = S("Trader's Caravan"),
	long_description = S("Unlike most markets that have well-known fixed locations that travelers congregate to, the network of Trader's Caravans is fluid and dynamic in their locations. A Trader's Caravan can show up anywhere, make modest trades, and then be gone the next time you visit them. These caravans accept gold and gold coins as a currency (one gold ingot to @1 gold coins exchange rate). Any reasonably-wealthy person can create a signpost marking a location where Trader's Caravans will make a stop.", coins_per_ingot),
	currency = gold_currency,
	currency_symbol = "☼", --"\u{263C}"
	inventory_limit = 1000,
	sell_limit = 1000,
	initial_items = default_items,
}

minetest.register_craft({
	output = "commoditymarket_fantasy:caravan_post",
	recipe = {
		{'group:wood', 'group:wood', ''},
		{'group:wood', gold_ingot, ''},
		{'group:wood', chest_locked, ''},
	}
})

commoditymarket.register_market("caravan", caravan_def)

local caravan_nodebox = {
	type = "fixed",
	fixed = {
		-- Note: this nodebox should have a height of 1.5, but using 95/64 as a workaround for
		-- https://github.com/minetest/minetest/issues/9322
		{-0.75, -0.5, -1.25, 0.75, 95/64, 1.25},
	},
}

local create_caravan_def = function(override_table)
local def = {
		description = caravan_def.description,
		_doc_items_longdesc = caravan_def.long_description,
		_doc_items_usagehelp = usage_help,
		drawtype = "mesh",
		mesh = "commoditymarket_wagon.obj",
		tiles = {
			{ name = "commoditymarket_door_wood.png", backface_culling = true }, -- door
			{ name = "default_wood.png", backface_culling = true }, -- base wood
			{ name = "commoditymarket_default_fence_rail_wood.png", backface_culling = true }, -- wheel sides
			{ name = "default_coal_block.png", backface_culling = true }, -- wheel tyre
			{ name = "commoditymarket_shingles_wood.png", backface_culling = true }, -- roof
			{ name = "default_junglewood.png", backface_culling = true }, -- corner wood
			},
		collision_box = caravan_nodebox,
		selection_box = caravan_nodebox,

		paramtype2 = "facedir",
		drop = "",
		groups = {choppy = 2, oddly_breakable_by_hand = 1, not_in_creative_inventory = 1},
		sounds = wood_sounds,
		on_rightclick = function(pos, node, clicker, itemstack, pointed_thing)
			commoditymarket.show_market("caravan", clicker:get_player_name())
			local timer = minetest.get_node_timer(pos)
			timer:start(dwell_time)
		end,
		after_destruct = function(pos, oldnode)
			local facedir = oldnode.param2
			local dir = minetest.facedir_to_dir(facedir)
			local target = vector.add(pos, vector.multiply(dir,-3))
			local target_node = minetest.get_node(target)
			if target_node.name == "commoditymarket_fantasy:caravan_post" then
				local meta = minetest.get_meta(target)
				meta:set_string("infotext", S("Right-click to summon a trader's caravan"))
			end
		end,
		on_timer = function(pos, elapsed)
			minetest.set_node(pos, {name="air"})
			minetest.sound_play("commoditymarket_register_closed", {
				pos = pos,
				gain = 1.0,  -- default
				max_hear_distance = 32,  -- default, uses an euclidean metric
			})
		end,
	}
	if override_table then
		for k, v in pairs(override_table) do
			def[k] = v
		end
	end
	return def
end

-- Create five caravans with different textures, randomly pick which one shows up.
minetest.register_node("commoditymarket_fantasy:caravan_market_1", create_caravan_def())
minetest.register_node("commoditymarket_fantasy:caravan_market_2", create_caravan_def({
tiles = {
	{ name = "commoditymarket_door_wood.png^[multiply:#CCCCFF", backface_culling = true }, -- door
	{ name = "default_acacia_wood.png", backface_culling = true }, -- base wood
	{ name = "commoditymarket_default_fence_rail_wood.png", backface_culling = true }, -- wheel sides
	{ name = "commoditymarket_default_copper_block.png", backface_culling = true }, -- wheel tyre
	{ name = "commoditymarket_shingles_wood.png^[multiply:#CC8888", backface_culling = true }, -- roof
	{ name = "default_wood.png", backface_culling = true }, -- corner wood
}
}))
minetest.register_node("commoditymarket_fantasy:caravan_market_3", create_caravan_def({
tiles = {
	{ name = "commoditymarket_door_wood.png", backface_culling = true }, -- door
	{ name = "commoditymarket_default_aspen_wood.png", backface_culling = true }, -- base wood
	{ name = "commoditymarket_default_fence_aspen_wood.png", backface_culling = true }, -- wheel sides
	{ name = "default_cobble.png", backface_culling = true }, -- wheel tyre
	{ name = "default_stone_brick.png", backface_culling = true }, -- roof
	{ name = "commoditymarket_default_pine_tree.png", backface_culling = true }, -- corner wood
}
}))
minetest.register_node("commoditymarket_fantasy:caravan_market_4", create_caravan_def({
tiles = {
	{ name = "commoditymarket_door_wood.png", backface_culling = true }, -- door
	{ name = "default_junglewood.png", backface_culling = true }, -- base wood
	{ name = "commoditymarket_default_fence_rail_junglewood.png", backface_culling = true }, -- wheel sides
	{ name = "default_obsidian.png", backface_culling = true }, -- wheel tyre
	{ name = "commoditymarket_shingles_wood.png^[multiply:#88FF88", backface_culling = true }, -- roof
	{ name = "default_tree.png", backface_culling = true }, -- corner wood
}
}))
minetest.register_node("commoditymarket_fantasy:caravan_market_5", create_caravan_def({
tiles = {
	{ name = "commoditymarket_door_wood.png", backface_culling = true }, -- door
	{ name = "commoditymarket_default_pine_wood.png", backface_culling = true }, -- base wood
	{ name = "commoditymarket_default_chest_lock.png", backface_culling = true }, -- wheel sides
	{ name = "commoditymarket_default_chest_top.png", backface_culling = true }, -- wheel tyre
	{ name = "default_furnace_top.png", backface_culling = true }, -- roof
	{ name = "default_wood.png", backface_culling = true }, -- corner wood
}
}))

local caravan_protect = minetest.settings:get_bool("commoditymarket_protect_caravan_market", true)
local on_blast
if caravan_protect then
	on_blast = function() end
end

-- This one doesn't delete itself, server admins can place a permanent instance of it that way. Maybe inside towns next to bigger stationary markets.
minetest.register_node("commoditymarket_fantasy:caravan_market_permanent", {
	description = caravan_def.description,
	_doc_items_longdesc = caravan_def.long_description,
	_doc_items_usagehelp = usage_help,
	drawtype = "mesh",
	mesh = "commoditymarket_wagon.obj",
	tiles = {
		{ name = "commoditymarket_door_wood.png", backface_culling = true }, -- door
		{ name = "default_wood.png", backface_culling = true }, -- base wood
		{ name = "commoditymarket_default_fence_rail_wood.png", backface_culling = true }, -- wheel sides
		{ name = "default_coal_block.png", backface_culling = true }, -- wheel tyre
		{ name = "commoditymarket_shingles_wood.png", backface_culling = true }, -- roof
		{ name = "default_junglewood.png", backface_culling = true }, -- corner wood
		},
    collision_box = caravan_nodebox,
	selection_box = caravan_nodebox,

	paramtype2 = "facedir",
	is_ground_content = false,
	groups = {choppy = 2, oddly_breakable_by_hand = 1,},
	sounds = wood_sounds,
	on_rightclick = function(pos, node, clicker, itemstack, pointed_thing)
		commoditymarket.show_market("caravan", clicker:get_player_name())
	end,
	can_dig = function(pos, player)
		return not caravan_protect or minetest.check_player_privs(player, "protection_bypass")
	end,
	on_blast = on_blast,
})

-- is a 5x3 area centered around pos clear of obstruction and has usable ground?
local is_suitable_caravan_space = function(pos, facedir)
	local x_dim = 2
	local z_dim = 2
	local dir = minetest.facedir_to_dir(facedir)
	if dir.x ~= 0 then
		z_dim = 1
	elseif dir.z ~= 0 then
		x_dim = 1
	end

	-- walkable ground?
	for x = pos.x - x_dim, pos.x + x_dim, 1 do
		for z = pos.z - z_dim, pos.z + z_dim, 1 do
			local node = minetest.get_node({x=x, y=pos.y-1, z=z})
			local node_def = minetest.registered_nodes[node.name]
			if node_def == nil or node_def.walkable ~= true then return false end
		end
	end
	-- buildable_to in the rest?
	for y = pos.y, pos.y+2, 1 do
		for x = pos.x - x_dim, pos.x + x_dim, 1 do
			for z = pos.z - z_dim, pos.z + z_dim, 1 do
				local node = minetest.get_node({x=x, y=y, z=z})
				local node_def = minetest.registered_nodes[node.name]
				if node_def == nil or node_def.buildable_to ~= true then return false end
			end
		end
	end
	return true
end

minetest.register_node("commoditymarket_fantasy:caravan_post", {
	description = S("Trading Post"),
	_long_items_longdesc = S("This post signals passing caravan traders that customers can be found here, and signals to customers that caravan traders can be found here. If no caravan is present, right-click to summon one."),
	_doc_items_usagehelp = S("The trader's caravan requires a suitable open space next to the trading post for it to arrive, and takes some time to arrive after being summoned. The post gives a countdown to the caravan's arrival when moused over."),
	tiles = {"commoditymarket_sign.png^[transformR90", "commoditymarket_sign.png^[transformR270",
		"commoditymarket_sign.png^commoditymarket_caravan_sign.png", "commoditymarket_sign.png^commoditymarket_caravan_sign.png^[transformFX",
		"commoditymarket_sign_post.png", "commoditymarket_sign_post.png"},
	groups = {choppy = 2, oddly_breakable_by_hand = 1,},
	sounds = wood_sounds,
	inventory_image = "commoditymarket_caravan_sign_inventory.png",
	paramtype= "light",
	paramtype2 = "facedir",
	drawtype = "nodebox",
	node_box = {
        type = "fixed",
        fixed = {
			{-0.125,-0.5,-0.5,0.125,2.0625,-0.25},
			{-0.0625,1.4375,-0.25,0.0625,2.0,0.5},
		},
    },
	on_construct = function(pos)
		local timer = minetest.get_node_timer(pos)
		timer:start(1.0)
	end,
	on_rightclick = function(pos, node, clicker, itemstack, pointed_thing)
		local timer = minetest.get_node_timer(pos)
		timer:start(1.0)
	end,
	on_timer = function(pos, elapsed)
		local node = minetest.get_node(pos)
		local meta = minetest.get_meta(pos)
		if node.name ~=  "commoditymarket_fantasy:caravan_post" then
			return -- the node was removed
		end
		local facedir = node.param2
		local dir = minetest.facedir_to_dir(facedir)
		local target = vector.add(pos, vector.multiply(dir,3))

		local target_node = minetest.get_node(target)

		if target_node.name:sub(1,string.len("commoditymarket_fantasy:caravan_market")) == "commoditymarket_fantasy:caravan_market" then
			-- It's already here somehow, shut down timer.
			meta:set_string("infotext", "")
			meta:set_float("wait_time", 0)
			return
		end

		local is_suitable_space = is_suitable_caravan_space(target, facedir)

		if not is_suitable_space then
			meta:set_string("infotext", S("Indicated parking area isn't suitable.\nA 5x3 open space with solid ground\nis required for a caravan."))
			meta:set_float("wait_time", 0)
			local timer = minetest.get_node_timer(pos)
			timer:start(1.0)
			return
		end

		local wait_time = (meta:get_float("wait_time") or 0) + elapsed
		meta:set_float("wait_time", wait_time)
		if wait_time < time_until_caravan then
			meta:set_string("infotext", S("Caravan summoned\nETA: @1 seconds.", math.floor(time_until_caravan - wait_time)))
			local timer = minetest.get_node_timer(pos)
			timer:start(1.0)
			return
		end

		-- spawn the caravan. We've already established that the target pos is clear.
		minetest.set_node(target, {name="commoditymarket_fantasy:caravan_market_"..math.random(1,5), param2=facedir})
		minetest.sound_play("commoditymarket_register_opened", {
			pos = target,
			gain = 1.0,  -- default
			max_hear_distance = 32,  -- default, uses an euclidean metric
		})
		local timer = minetest.get_node_timer(target)
		timer:start(dwell_time)
		meta:set_string("infotext", "")
		meta:set_float("wait_time", 0)
	end,
})


code:
local modpath = minetest.get_modpath(minetest.get_current_modname())
dofile(modpath.."/mapgen_dungeon_markets.lua")

local S = commoditymarket_fantasy.S
local coal_lump = commoditymarket_fantasy.coal_lump
local wood_sounds = commoditymarket_fantasy.wood_sounds
local usage_help = commoditymarket_fantasy.usage_help
local default_modpath = commoditymarket_fantasy.default_modpath

local undermarket_currency = commoditymarket_fantasy.undermarket_currency
local undermarket_desc = commoditymarket_fantasy.undermarket_desc
local undermarket_symbol = commoditymarket_fantasy.undermarket_symbol
local stone_sounds = commoditymarket_fantasy.stone_sounds

-------------------------------------------------------------------------------
-- "Goblin Exchange"
if minetest.settings:get_bool("commoditymarket_enable_goblin_market", true) then

local goblin_def = {
	description = S("Goblin Exchange"),
	long_description = S("One does not usually associate Goblins with the sort of sophistication that running a market requires. Usually one just associates Goblins with savagery and violence. But they understand the principle of tit-for-tat exchange, and if approached correctly they actually respect the concepts of ownership and debt. However, for some peculiar reason they understand this concept in the context of coal lumps. Goblins deal in the standard coal lump as their form of currency, conceptually divided into 100 coal centilumps (though Goblin brokers prefer to \"keep the change\" when giving back actual coal lumps)."),
	currency = {
		[coal_lump] = 100
	},
	currency_symbol = "¢", --"\u{00A2}" cent symbol
	inventory_limit = 1000,
	--sell_limit =, -- no sell limit
}

commoditymarket.register_market("goblin", goblin_def)

local goblin_protect = minetest.settings:get_bool("commoditymarket_protect_goblin_market", true)
local on_blast
if goblin_protect then
	on_blast = function() end
end

minetest.register_node("commoditymarket_fantasy:goblin_market", {
	description = goblin_def.description,
	_doc_items_longdesc = goblin_def.long_description,
	_doc_items_usagehelp = usage_help,
	tiles = {"commoditymarket_default_chest_top.png^(default_coal_block.png^[opacity:128)","commoditymarket_default_chest_top.png^(default_coal_block.png^[opacity:128)",
		"commoditymarket_default_chest_side.png^(default_coal_block.png^[opacity:128)","commoditymarket_default_chest_side.png^(default_coal_block.png^[opacity:128)",
		"commoditymarket_empty_shelf.png^(default_coal_block.png^[opacity:128)","commoditymarket_default_chest_side.png^(default_coal_block.png^[opacity:128)^commoditymarket_goblin.png",},
	paramtype2 = "facedir",
	is_ground_content = false,
	groups = {choppy = 2, oddly_breakable_by_hand = 1,},
	sounds = wood_sounds,
	on_rightclick = function(pos, node, clicker, itemstack, pointed_thing)
		commoditymarket.show_market("goblin", clicker:get_player_name())
	end,
	can_dig = function(pos, player)
		return not goblin_protect or minetest.check_player_privs(player, "protection_bypass")
	end,
	on_blast = on_blast,
})
end

--------------------------------------------------------------------------------
-- Undermarket

if minetest.settings:get_bool("commoditymarket_enable_under_market", true) then

local undermarket_def = {
	description = S("Undermarket"),
	long_description = undermarket_desc,
	currency = undermarket_currency,
	currency_symbol = undermarket_symbol,
	inventory_limit = 10000,
	--sell_limit =, -- no sell limit
}

commoditymarket.register_market("under", undermarket_def)

local under_protect = minetest.settings:get_bool("commoditymarket_protect_under_market", true)
local on_blast
if under_protect then
	on_blast = function() end
end

minetest.register_node("commoditymarket_fantasy:under_market", {
	description = undermarket_def.description,
	_doc_items_longdesc = undermarket_def.long_description,
	_doc_items_usagehelp = usage_help,
	tiles = {"commoditymarket_under_top.png","commoditymarket_under_top.png",
		"commoditymarket_under.png","commoditymarket_under.png","commoditymarket_under.png","commoditymarket_under.png"},
	paramtype2 = "facedir",
	is_ground_content = false,
	groups = {choppy = 2, oddly_breakable_by_hand = 1,},
	sounds = stone_sounds,
	on_rightclick = function(pos, node, clicker, itemstack, pointed_thing)
		commoditymarket.show_market("under", clicker:get_player_name())
	end,
	can_dig = function(pos, player)
		return not under_protect or minetest.check_player_privs(player, "protection_bypass")
	end,
	on_blast = on_blast,
})
end
------------------------------------------------------------------

repo_name: korkut/MeseCraft

license: mit


code:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# Script to generate the template file and update the translation files.
# Copy the script into the mod or modpack root folder and run it there.
#
# Copyright (C) 2019 Joachim Stolberg, 2020 FaceDeer, 2020 Louis Royer
# LGPLv2.1+
#
# See https://github.com/minetest-tools/update_translations for
# potential future updates to this script.

from __future__ import print_function
import os, fnmatch, re, shutil, errno
from sys import argv as _argv

# Running params
params = {"recursive": False,
    "help": False,
    "mods": False,
    "verbose": False,
    "folders": []
}
# Available CLI options
options = {"recursive": ['--recursive', '-r'],
    "help": ['--help', '-h'],
    "mods": ['--installed-mods'],
    "verbose": ['--verbose', '-v']
}

# Strings longer than this will have extra space added between
# them in the translation files to make it easier to distinguish their
# beginnings and endings at a glance
doublespace_threshold = 60

def set_params_folders(tab: list):
    '''Initialize params["folders"] from CLI arguments.'''
    # Discarding argument 0 (tool name)
    for param in tab[1:]:
        stop_param = False
        for option in options:
            if param in options[option]:
                stop_param = True
                break
        if not stop_param:
            params["folders"].append(os.path.abspath(param))

def set_params(tab: list):
    '''Initialize params from CLI arguments.'''
    for option in options:
        for option_name in options[option]:
            if option_name in tab:
                params[option] = True
                break

def print_help(name):
    '''Prints some help message.'''
    print(f'''SYNOPSIS
    {name} [OPTIONS] [PATHS...]
DESCRIPTION
    {', '.join(options["help"])}
        prints this help message
    {', '.join(options["recursive"])}
        run on all subfolders of paths given
    {', '.join(options["mods"])}
        run on locally installed modules
    {', '.join(options["verbose"])}
        add output information
''')

def main():
    '''Main function'''
    set_params(_argv)
    set_params_folders(_argv)
    if params["help"]:
        print_help(_argv[0])
    elif params["recursive"] and params["mods"]:
        print("Option --installed-mods is incompatible with --recursive")
    else:
        # Add recursivity message
        print("Running ", end='')
        if params["recursive"]:
            print("recursively ", end='')
        # Running
        if params["mods"]:
            print(f"on all locally installed modules in {os.path.abspath('~/.minetest/mods/')}")
            run_all_subfolders("~/.minetest/mods")
        elif len(params["folders"]) >= 2:
            print("on folder list:", params["folders"])
            for f in params["folders"]:
                if params["recursive"]:
                    run_all_subfolders(f)
                else:
                    update_folder(f)
        elif len(params["folders"]) == 1:
            print("on folder", params["folders"][0])
            if params["recursive"]:
                run_all_subfolders(params["folders"][0])
            else:
                update_folder(params["folders"][0])
        else:
            print("on folder", os.path.abspath("./"))
            if params["recursive"]:
                run_all_subfolders(os.path.abspath("./"))
            else:
                update_folder(os.path.abspath("./"))

#group 2 will be the string, groups 1 and 3 will be the delimiters (" or ')
#See https://stackoverflow.com/questions/46967465/regex-match-text-in-either-single-or-double-quote
pattern_lua = re.compile(r'[\.=^\t,{\(\s]N?S\(\s*(["\'])((?:\\\1|(?:(?!\1)).)*)(\1)[\s,\)]', re.DOTALL)
pattern_lua_bracketed = re.compile(r'[\.=^\t,{\(\s]N?S\(\s*\[\[(.*?)\]\][\s,\)]', re.DOTALL)

# Handles "concatenation" .. " of strings"
pattern_concat = re.compile(r'["\'][\s]*\.\.[\s]*["\']', re.DOTALL)

pattern_tr = re.compile(r'(.+?[^@])=(.*)')
pattern_name = re.compile(r'^name[ ]*=[ ]*([^ \n]*)')
pattern_tr_filename = re.compile(r'\.tr$')
pattern_po_language_code = re.compile(r'(.*)\.po$')

#attempt to read the mod's name from the mod.conf file. Returns None on failure
def get_modname(folder):
    try:
        with open(os.path.join(folder, "mod.conf"), "r", encoding='utf-8') as mod_conf:
            for line in mod_conf:
                match = pattern_name.match(line)
                if match:
                    return match.group(1)
    except FileNotFoundError:
        pass
    return None

#If there are already .tr files in /locale, returns a list of their names
def get_existing_tr_files(folder):
    out = []
    for root, dirs, files in os.walk(os.path.join(folder, 'locale/')):
        for name in files:
            if pattern_tr_filename.search(name):
                out.append(name)
    return out

# A series of search and replaces that massage a .po file's contents into
# a .tr file's equivalent
def process_po_file(text):
    # The first three items are for unused matches
    text = re.sub(r'#~ msgid "', "", text)
    text = re.sub(r'"\n#~ msgstr ""\n"', "=", text)
    text = re.sub(r'"\n#~ msgstr "', "=", text)
    # comment lines
    text = re.sub(r'#.*\n', "", text)
    # converting msg pairs into "=" pairs
    text = re.sub(r'msgid "', "", text)
    text = re.sub(r'"\nmsgstr ""\n"', "=", text)
    text = re.sub(r'"\nmsgstr "', "=", text)
    # various line breaks and escape codes
    text = re.sub(r'"\n"', "", text)
    text = re.sub(r'"\n', "\n", text)
    text = re.sub(r'\\"', '"', text)
    text = re.sub(r'\\n', '@n', text)
    # remove header text
    text = re.sub(r'=Project-Id-Version:.*\n', "", text)
    # remove double-spaced lines
    text = re.sub(r'\n\n', '\n', text)
    return text

# Go through existing .po files and, if a .tr file for that language
# *doesn't* exist, convert it and create it.
# The .tr file that results will subsequently be reprocessed so
# any "no longer used" strings will be preserved.
# Note that "fuzzy" tags will be lost in this process.
def process_po_files(folder, modname):
    for root, dirs, files in os.walk(os.path.join(folder, 'locale/')):
        for name in files:
            code_match = pattern_po_language_code.match(name)
            if code_match == None:
                continue
            language_code = code_match.group(1)
            tr_name = modname + "." + language_code + ".tr"
            tr_file = os.path.join(root, tr_name)
            if os.path.exists(tr_file):
                if params["verbose"]:
                    print(f"{tr_name} already exists, ignoring {name}")
                continue
            fname = os.path.join(root, name)
            with open(fname, "r", encoding='utf-8') as po_file:
                if params["verbose"]:
                    print(f"Importing translations from {name}")
                text = process_po_file(po_file.read())
                with open(tr_file, "wt", encoding='utf-8') as tr_out:
                    tr_out.write(text)

# from https://stackoverflow.com/questions/600268/mkdir-p-functionality-in-python/600612#600612
# Creates a directory if it doesn't exist, silently does
# nothing if it already exists
def mkdir_p(path):
    try:
        os.makedirs(path)
    except OSError as exc: # Python >2.5
        if exc.errno == errno.EEXIST and os.path.isdir(path):
            pass
        else: raise

# Converts the template dictionary to a text to be written as a file
# dKeyStrings is a dictionary of localized string to source file sets
# dOld is a dictionary of existing translations and comments from
# the previous version of this text
def strings_to_text(dkeyStrings, dOld, mod_name):
    lOut = [f"# textdomain: {mod_name}\n"]

    dGroupedBySource = {}

    for key in dkeyStrings:
        sourceList = list(dkeyStrings[key])
        sourceList.sort()
        sourceString = "\n".join(sourceList)
        listForSource = dGroupedBySource.get(sourceString, [])
        listForSource.append(key)
        dGroupedBySource[sourceString] = listForSource

    lSourceKeys = list(dGroupedBySource.keys())
    lSourceKeys.sort()
    for source in lSourceKeys:
        localizedStrings = dGroupedBySource[source]
        localizedStrings.sort()
        lOut.append("")
        lOut.append(source)
        lOut.append("")
        for localizedString in localizedStrings:
            val = dOld.get(localizedString, {})
            translation = val.get("translation", "")
            comment = val.get("comment")
            if len(localizedString) > doublespace_threshold and not lOut[-1] == "":
                lOut.append("")
            if comment != None:
                lOut.append(comment)
            lOut.append(f"{localizedString}={translation}")
            if len(localizedString) > doublespace_threshold:
                lOut.append("")

    unusedExist = False
    for key in dOld:
        if key not in dkeyStrings:
            val = dOld[key]
            translation = val.get("translation")
            comment = val.get("comment")
            # only keep an unused translation if there was translated
            # text or a comment associated with it
            if translation != None and (translation != "" or comment):
                if not unusedExist:
                    unusedExist = True
                    lOut.append("\n\n##### not used anymore #####\n")
                if len(key) > doublespace_threshold and not lOut[-1] == "":
                    lOut.append("")
                if comment != None:
                    lOut.append(comment)
                lOut.append(f"{key}={translation}")
                if len(key) > doublespace_threshold:
                    lOut.append("")
    return "\n".join(lOut) + '\n'

# Writes a template.txt file
# dkeyStrings is the dictionary returned by generate_template
def write_template(templ_file, dkeyStrings, mod_name):
    # read existing template file to preserve comments
    existing_template = import_tr_file(templ_file)

    text = strings_to_text(dkeyStrings, existing_template[0], mod_name)
    mkdir_p(os.path.dirname(templ_file))
    with open(templ_file, "wt", encoding='utf-8') as template_file:
        template_file.write(text)

# Gets all translatable strings from a lua file
def read_lua_file_strings(lua_file):
    lOut = []
    with open(lua_file, encoding='utf-8') as text_file:
        text = text_file.read()
        #TODO remove comments here

        text = re.sub(pattern_concat, "", text)

        strings = []
        for s in pattern_lua.findall(text):
            strings.append(s[1])
        for s in pattern_lua_bracketed.findall(text):
            strings.append(s)

        for s in strings:
            s = re.sub(r'"\.\.\s+"', "", s)
            s = re.sub("@[^@=0-9]", "@@", s)
            s = s.replace('\\"', '"')
            s = s.replace("\\'", "'")
            s = s.replace("\n", "@n")
            s = s.replace("\\n", "@n")
            s = s.replace("=", "@=")
            lOut.append(s)
    return lOut

# Gets strings from an existing translation file
# returns both a dictionary of translations
# and the full original source text so that the new text
# can be compared to it for changes.
def import_tr_file(tr_file):
    dOut = {}
    text = None
    if os.path.exists(tr_file):
        with open(tr_file, "r", encoding='utf-8') as existing_file :
            # save the full text to allow for comparison
            # of the old version with the new output
            text = existing_file.read()
            existing_file.seek(0)
            # a running record of the current comment block
            # we're inside, to allow preceeding multi-line comments
            # to be retained for a translation line
            latest_comment_block = None
            for line in existing_file.readlines():
                line = line.rstrip('\n')
                if line[:3] == "###":
                    # Reset comment block if we hit a header
                    latest_comment_block = None
                    continue
                if line[:1] == "#":
                    # Save the comment we're inside
                    if not latest_comment_block:
                        latest_comment_block = line
                    else:
                        latest_comment_block = latest_comment_block + "\n" + line
                    continue
                match = pattern_tr.match(line)
                if match:
                    # this line is a translated line
                    outval = {}
                    outval["translation"] = match.group(2)
                    if latest_comment_block:
                        # if there was a comment, record that.
                        outval["comment"] = latest_comment_block
                    latest_comment_block = None
                    dOut[match.group(1)] = outval
    return (dOut, text)

# Walks all lua files in the mod folder, collects translatable strings,
# and writes it to a template.txt file
# Returns a dictionary of localized strings to source file sets
# that can be used with the strings_to_text function.
def generate_template(folder, mod_name):
    dOut = {}
    for root, dirs, files in os.walk(folder):
        for name in files:
            if fnmatch.fnmatch(name, "*.lua"):
                fname = os.path.join(root, name)
                found = read_lua_file_strings(fname)
                if params["verbose"]:
                    print(f"{fname}: {str(len(found))} translatable strings")

                for s in found:
                    sources = dOut.get(s, set())
                    sources.add(f"### {os.path.basename(fname)} ###")
                    dOut[s] = sources

    if len(dOut) == 0:
        return None
    templ_file = os.path.join(folder, "locale/template.txt")
    write_template(templ_file, dOut, mod_name)
    return dOut

# Updates an existing .tr file, copying the old one to a ".old" file
# if any changes have happened
# dNew is the data used to generate the template, it has all the
# currently-existing localized strings
def update_tr_file(dNew, mod_name, tr_file):
    if params["verbose"]:
        print(f"updating {tr_file}")

    tr_import = import_tr_file(tr_file)
    dOld = tr_import[0]
    textOld = tr_import[1]

    textNew = strings_to_text(dNew, dOld, mod_name)

    if textOld and textOld != textNew:
        print(f"{tr_file} has changed.")
        shutil.copyfile(tr_file, f"{tr_file}.old")

    with open(tr_file, "w", encoding='utf-8') as new_tr_file:
        new_tr_file.write(textNew)

# Updates translation files for the mod in the given folder
def update_mod(folder):
    modname = get_modname(folder)
    if modname is not None:
        process_po_files(folder, modname)
        print(f"Updating translations for {modname}")
        data = generate_template(folder, modname)
        if data == None:
            print(f"No translatable strings found in {modname}")
        else:
            for tr_file in get_existing_tr_files(folder):
                update_tr_file(data, modname, os.path.join(folder, "locale/", tr_file))
    else:
        print("Unable to find modname in folder " + folder)

# Determines if the folder being pointed to is a mod or a mod pack
# and then runs update_mod accordingly
def update_folder(folder):
    is_modpack = os.path.exists(os.path.join(folder, "modpack.txt")) or os.path.exists(os.path.join(folder, "modpack.conf"))
    if is_modpack:
        subfolders = [f.path for f in os.scandir(folder) if f.is_dir()]
        for subfolder in subfolders:
            update_mod(subfolder + "/")
    else:
        update_mod(folder)
    print("Done.")

def run_all_subfolders(folder):
    for modfolder in [f.path for f in os.scandir(folder) if f.is_dir()]:
        update_folder(modfolder + "/")

main()


code:
# textdomain: commoditymarket_fantasy

### caravan_markets.lua ###

Caravan summoned@nETA: @1 seconds.=

Indicated parking area isn't suitable.@nA 5x3 open space with solid ground@nis required for a caravan.=

Right-click to summon a trader's caravan=

The trader's caravan requires a suitable open space next to the trading post for it to arrive, and takes some time to arrive after being summoned. The post gives a countdown to the caravan's arrival when moused over.=

This post signals passing caravan traders that customers can be found here, and signals to customers that caravan traders can be found here. If no caravan is present, right-click to summon one.=

Trader's Caravan=
Trading Post=

Unlike most markets that have well-known fixed locations that travelers congregate to, the network of Trader's Caravans is fluid and dynamic in their locations. A Trader's Caravan can show up anywhere, make modest trades, and then be gone the next time you visit them. These caravans accept gold and gold coins as a currency (one gold ingot to @1 gold coins exchange rate). Any reasonably-wealthy person can create a signpost marking a location where Trader's Caravans will make a stop.=

### dungeon_markets.lua ###

Goblin Exchange=

One does not usually associate Goblins with the sort of sophistication that running a market requires. Usually one just associates Goblins with savagery and violence. But they understand the principle of tit-for-tat exchange, and if approached correctly they actually respect the concepts of ownership and debt. However, for some peculiar reason they understand this concept in the context of coal lumps. Goblins deal in the standard coal lump as their form of currency, conceptually divided into 100 coal centilumps (though Goblin brokers prefer to "keep the change" when giving back actual coal lumps).=

Undermarket=

### init.lua ###

A gold ingot is far too valuable to use as a basic unit of value, so it has become common practice to divide the standard gold bar into @1 small disks to make trade easier.=

Deep in the bowels of the world, below even the goblin-infested warrens and ancient delvings of the dwarves, dark and mysterious beings once dwelt. A few still linger to this day, and facilitate barter for those brave souls willing to travel in their lost realms. The Undermarket uses Mese chips ('₥') as a currency - twenty chips to the Mese fragment. Though traders are loathe to physically break Mese crystals up into units that small, as it renders it useless for other purposes.=

Deep in the bowels of the world, below even the goblin-infested warrens and ancient delvings of the dwarves, dark and mysterious beings once dwelt. A few still linger to this day, and facilitate barter for those brave souls willing to travel in their lost realms. The Undermarket uses Redstone ('₨') as a currency.=

Gold Coins=

Gold coins can be deposited and withdrawn from markets that accept them as currency. These markets can make change if you have @1 coins and would like them back in ingot form again.=

Right-click on this to open the market interface.=

### village_markets.lua ###

At this time of day the King's Market is closed.=
At this time of day the Night Market is closed.=
King's Market=
Night Market=

The largest and most accessible market for the common man, the King's Market uses gold coins as its medium of exchange (or the equivalent in gold ingots - @1 coins to the ingot). However, as a respectable institution of the surface world, the King's Market operates only during the hours of daylight. Gold coins are represented by a '☼' symbol.=

When the sun sets and the stalls of the King's Market close, other vendors are just waking up to share their wares. The Night Market is not as voluminous as the King's Market but sometimes it's the only option. It accepts the same gold coinage of the realm, @1 coins to the gold ingot.=


code:
local goblin_enabled = minetest.settings:get_bool("commoditymarket_enable_goblin_market")
local under_enabled = minetest.settings:get_bool("commoditymarket_enable_under_market")
local goblin_prob = tonumber(minetest.settings:get("commoditymarket_goblin_market_dungeon_prob")) or 0.25
local under_prob = tonumber(minetest.settings:get("commoditymarket_under_market_dungeon_prob")) or 0.1

local goblin_max = tonumber(minetest.settings:get("commoditymarket_goblin_market_dungeon_max")) or 100
local goblin_min = tonumber(minetest.settings:get("commoditymarket_goblin_market_dungeon_min")) or -400
local under_max = tonumber(minetest.settings:get("commoditymarket_under_market_dungeon_max")) or -500
local under_min = tonumber(minetest.settings:get("commoditymarket_under_market_dungeon_min")) or -31000

local bad_goblin_range = goblin_min >= goblin_max
local bad_under_range = under_min >= under_max

if bad_goblin_range then
	minetest.log("error", "[commoditymarket] Goblin market dungeon generation range has a higher minimum y than maximum y")
end
if bad_under_range then
	minetest.log("error", "[commoditymarket] Undermarket dungeon generation range has a higher minimum y than maximum y")
end

local gen_goblin = goblin_enabled and goblin_prob > 0 and not bad_goblin_range
local gen_under = under_enabled and under_prob > 0 and not bad_under_range

if not (gen_goblin or gen_under) then
	return
end

-------------------------------------------------------
-- The following is shamelessly copied from dungeon_loot and tweaked for placing markets instead of chests
--Licensed under the MIT License (MIT) Copyright (C) 2017 sfan5

minetest.set_gen_notify({dungeon = true, temple = true})

local function noise3d_integer(noise, pos)
	return math.abs(math.floor(noise:get_3d(pos) * 0x7fffffff))
end

local is_wall = function(node)
	return node.name ~= "air" and node.name ~= "ignore"
end

local function find_walls(cpos)
	local dirs = {{x=1, z=0}, {x=-1, z=0}, {x=0, z=1}, {x=0, z=-1}}
	local get_node = minetest.get_node

	local ret = {}
	local mindist = {x=0, z=0}
	local min = function(a, b) return a ~= 0 and math.min(a, b) or b end
	for _, dir in ipairs(dirs) do
		for i = 1, 9 do -- 9 = max room size / 2
			local pos = vector.add(cpos, {x=dir.x*i, y=0, z=dir.z*i})

			-- continue in that direction until we find a wall-like node
			local node = get_node(pos)
			if is_wall(node) then
				local front_below = vector.subtract(pos, {x=dir.x, y=1, z=dir.z})
				local above = vector.add(pos, {x=0, y=1, z=0})

				-- check that it:
				--- is at least 2 nodes high (not a staircase)
				--- has a floor
				if is_wall(get_node(front_below)) and is_wall(get_node(above)) then
					pos = vector.subtract(pos, {x=dir.x, y=0, z=dir.z}) -- move goblin markets one node away from the wall
					table.insert(ret, {pos = pos, facing = {x=-dir.x, y=0, z=-dir.z}})
					if dir.z == 0 then
						mindist.x = min(mindist.x, i-1)
					else
						mindist.z = min(mindist.z, i-1)
					end
				end
				-- abort even if it wasn't a wall cause something is in the way
				break
			end
		end
	end

	return {
		walls = ret,
		size = {x=mindist.x*2, z=mindist.z*2},
		cpos = cpos,
	}
end

minetest.register_on_generated(function(minp, maxp, blockseed)
	local min_y = minp.y
	local max_y = maxp.y

	local gen_goblin_range = gen_goblin and not (min_y > goblin_max or max_y < goblin_min)
	local gen_under_range = gen_under and not (min_y > under_max or max_y < under_min)

	if not (gen_goblin_range or gen_under_range) then
		-- out of both ranges
		return
	end

	local gennotify = minetest.get_mapgen_object("gennotify")
	local poslist = gennotify["dungeon"] or {}
	for _, entry in ipairs(gennotify["temple"] or {}) do
		table.insert(poslist, entry)
	end
	if #poslist == 0 then return end

	local noise = minetest.get_perlin(151994, 4, 0.5, 1)
	local rand = PcgRandom(noise3d_integer(noise, poslist[1]))

	local rooms = {}
	-- process at most 8 rooms to keep runtime of this predictable
	local num_process = math.min(#poslist, 8)
	for i = 1, num_process do
		local room = find_walls(poslist[i])
		-- skip small rooms and everything that doesn't at least have 3 walls
		if math.min(room.size.x, room.size.z) >= 4 and #room.walls >= 3 then
			table.insert(rooms, room)
		end
	end
	if #rooms == 0 then return end

	if gen_under_range and rand:next(0, 2147483647)/2147483647 < under_prob then
		-- choose a random room
		local room = rooms[rand:next(1, #rooms)]
		local under_loc = room.cpos

		-- put undermarkets in the center of the room
		if minetest.get_node(under_loc).name == "air"
			and is_wall(vector.subtract(under_loc, {x=0, y=1, z=0})) then
			minetest.add_node(under_loc, {name="commoditymarket:under_market"})
		end
	end

	if gen_goblin_range and rand:next(0, 2147483647)/2147483647 < goblin_prob then
		-- choose a random room
		local room = rooms[rand:next(1, #rooms)]

		-- choose place somewhere in front of any of the walls
		local wall = room.walls[rand:next(1, #room.walls)]
		local v, vi -- vector / axis that runs alongside the wall
		if wall.facing.x ~= 0 then
			v, vi = {x=0, y=0, z=1}, "z"
		else
			v, vi = {x=1, y=0, z=0}, "x"
		end
		local marketpos = vector.add(wall.pos, wall.facing)
		local off = rand:next(-room.size[vi]/2 + 1, room.size[vi]/2 - 1)
		marketpos = vector.add(marketpos, vector.multiply(v, off))

		if minetest.get_node(marketpos).name == "air" then
			-- make it face inwards to the room
			local facedir = minetest.dir_to_facedir(vector.multiply(wall.facing, -1))
			minetest.add_node(marketpos, {name = "commoditymarket:goblin_market", param2 = facedir})
		end
	end
end)


code:
name = commoditymarket_fantasy
description = Adds a number of fantasy-themed marketplaces
depends = commoditymarket
optional_depends = doc, lorebooks, default, mcl_core, mcl_sounds, mcl_chests, mesecons_wires


code:
This mod depends on [commoditymarket](https://github.com/FaceDeer/commoditymarket). It implements a number of fantasy-themed marketplaces where players can post buy and sell offers for various items, allowing for organic market forces to determine the relative values of the resources in a world.

* King's Market - a basic sort of "commoner's marketplace", only open during the day
* Night Market - the shadier side of commerce, only open during the night
* Trader's Caravan - a type of market that players can build and place themselves, with a small inventory capacity.
* Goblin Exchange - a strange marketplace that uses coal as a currency
* Undermarket - where dark powers make their trades, using Mese as a currency

![](screenshot.jpg)

All of these except for the Trader's Caravan are intended to be placed in specific locations by server administrators, they don't have crafting recipes.

An option exists to allow Goblin markets and Undermarkets to be automatically placed inside dungeons by world gen.

The [settlements](https://github.com/FaceDeer/settlements) mod scatters small settlements around on the surface of the world that can contain King's Markets and Night Markets.


code:
[Enable default markets]
commoditymarket_enable_kings_market (Enable King's Market) bool true
commoditymarket_enable_night_market (Enable Night Market) bool true
commoditymarket_enable_caravan_market (Enable Trader's Caravan) bool true
commoditymarket_enable_goblin_market (Enable Goblin Exchange) bool true
commoditymarket_enable_under_market (Enable Undermarket) bool true

[Market node protection]
commoditymarket_protect_kings_market (Protect King's Market node) bool true
commoditymarket_protect_night_market (Protect Night Market node) bool true
commoditymarket_protect_caravan_market (Protect permanent Caravan node) bool true
commoditymarket_protect_goblin_market (Protect Goblin Market node) bool true
commoditymarket_protect_under_market (Protect Undermarket node) bool true

[Currency denominations]
commoditymarket_coins_per_ingot (Coins per ingot) int 1000

[Dungeon market generation]
commoditymarket_goblin_market_dungeon_prob (Goblin market probability per dungeon mapblock) float 0.25 0 1
commoditymarket_goblin_market_dungeon_max (Upper y limit of goblin markets) int 100
commoditymarket_goblin_market_dungeon_min (Lower y limit of goblin markets) int -400
commoditymarket_under_market_dungeon_prob (Undermarket probability per dungeon mapblock) float 0.1 0 1
commoditymarket_under_market_dungeon_max (Upper y limit of undermarkets) int -500
commoditymarket_under_market_dungeon_min (Lower y limit of undermarkets) int -31000


code:
local S = commoditymarket_fantasy.S
local coins_per_ingot = commoditymarket_fantasy.coins_per_ingot
local default_items = commoditymarket_fantasy.default_items
local gold_ingot = commoditymarket_fantasy.gold_ingot
local gold_currency = commoditymarket_fantasy.gold_currency
local wood_sounds = commoditymarket_fantasy.wood_sounds
local usage_help = commoditymarket_fantasy.usage_help

commoditymarket_fantasy.register_gold_coins()

------------------------------------------------------------------------------
-- King's Market

if minetest.settings:get_bool("commoditymarket_enable_kings_market", true) then

local kings_def = {
	description = S("King's Market"),
	long_description = S("The largest and most accessible market for the common man, the King's Market uses gold coins as its medium of exchange (or the equivalent in gold ingots - @1 coins to the ingot). However, as a respectable institution of the surface world, the King's Market operates only during the hours of daylight. Gold coins are represented by a '☼' symbol.", coins_per_ingot),
	currency = gold_currency,
	currency_symbol = "☼", -- "\u{263C}" Alchemical symbol for gold
	inventory_limit = 100000,
	--sell_limit =, -- no sell limit for the King's Market
	initial_items = default_items,
}

commoditymarket_fantasy.register_gold_coins()

commoditymarket.register_market("kings", kings_def)

local kings_protect = minetest.settings:get_bool("commoditymarket_protect_kings_market", true)
local on_blast
if kings_protect then
	on_blast = function() end
end

minetest.register_node("commoditymarket_fantasy:kings_market", {
	description = kings_def.description,
	_doc_items_longdesc = kings_def.long_description,
	_doc_items_usagehelp = usage_help,
	tiles = {"commoditymarket_default_chest_top.png","commoditymarket_default_chest_top.png",
		"commoditymarket_default_chest_side.png","commoditymarket_default_chest_side.png",
		"commoditymarket_empty_shelf.png","commoditymarket_default_chest_side.png^commoditymarket_crown.png",},
	paramtype2 = "facedir",
	is_ground_content = false,
	groups = {choppy = 2, oddly_breakable_by_hand = 1,},
	sounds = wood_sounds,
	on_rightclick = function(pos, node, clicker, itemstack, pointed_thing)
		local timeofday =  minetest.get_timeofday()
		if timeofday > 0.2 and timeofday < 0.8 then
			commoditymarket.show_market("kings", clicker:get_player_name())
		else
			minetest.chat_send_player(clicker:get_player_name(), S("At this time of day the King's Market is closed."))
			minetest.sound_play({name = "commoditymarket_error", gain = 0.1}, {to_player=clicker:get_player_name()})
		end
	end,
	can_dig = function(pos, player)
		return not kings_protect or minetest.check_player_privs(player, "protection_bypass")
	end,
	on_blast = on_blast,
})
end
-------------------------------------------------------------------------------
-- Night Market

if minetest.settings:get_bool("commoditymarket_enable_night_market", true) then
local night_def = {
	description = S("Night Market"),
	long_description = S("When the sun sets and the stalls of the King's Market close, other vendors are just waking up to share their wares. The Night Market is not as voluminous as the King's Market but sometimes it's the only option. It accepts the same gold coinage of the realm, @1 coins to the gold ingot.", coins_per_ingot),
	currency = gold_currency,
	currency_symbol = "☼", --"\u{263C}"
	inventory_limit = 10000,
	--sell_limit =, -- no sell limit for the Night Market
	initial_items = default_items,
	anonymous = true,
}

commoditymarket_fantasy.register_gold_coins()

commoditymarket.register_market("night", night_def)

local night_protect = minetest.settings:get_bool("commoditymarket_protect_night_market", true)
local on_blast
if night_protect then
	on_blast = function() end
end

minetest.register_node("commoditymarket_fantasy:night_market", {
	description = night_def.description,
	_doc_items_longdesc = night_def.long_description,
	_doc_items_usagehelp = usage_help,
	tiles = {"commoditymarket_default_chest_top.png","commoditymarket_default_chest_top.png",
		"commoditymarket_default_chest_side.png","commoditymarket_default_chest_side.png",
		"commoditymarket_empty_shelf.png","commoditymarket_default_chest_side.png^commoditymarket_moon.png",},
	paramtype2 = "facedir",
	is_ground_content = false,
	groups = {choppy = 2, oddly_breakable_by_hand = 1,},
	sounds = wood_sounds,
	on_rightclick = function(pos, node, clicker, itemstack, pointed_thing)
		local timeofday =  minetest.get_timeofday()
		if timeofday < 0.2 or timeofday > 0.8 then
			commoditymarket.show_market("night", clicker:get_player_name())
		else
			minetest.chat_send_player(clicker:get_player_name(), S("At this time of day the Night Market is closed."))
			minetest.sound_play({name = "commoditymarket_error", gain = 0.1}, {to_player=clicker:get_player_name()})
		end
	end,
	can_dig = function(pos, player)
		return not night_protect or minetest.check_player_privs(player, "protection_bypass")
	end,
	on_blast = on_blast,
})
end


code:
Compost Mod v0.0.3
==================
Compost bins turn organic matter like leaves, flowers, grass etc into soil. The
bin is operated by punching or right-clicking.

Punching
--------
On punching (left-clicking) a compost bin, the pointed_thing is inserted into
the bin and dirt is transferred back. If pointed_thing is compostable and there
is a free slot for it, it disappears inside the bin. After that, if hand is
empty and there is soil in the bin, it appears in the hand.

Menu
----
Right-clicking the bin opens its menu. 8 (2 rows by 4 cols) slots for intputs,
one slot for output.

Once there are at least 8 pieces of acceptable inputs are inserted (stacks
OK), a timer starts. Every 30 seconds it adds 10% to the composting progress, so
it takes 5 minutes to create a block of soil.

Once 100% is reached, 8 pieces of input disappear and one block of soil is
added to output. The progress counter is reset to 0. Optionally, wear-out is
computed.

If there are not enough inputs, the timer (if it was set) is stopped (and it
works like a small chest).

Inputs
------
Acceptable inputs are group:flora (flowers and grasses) and group:leaves. Other
inputs are rejected. Food is rejected on esthetical grounds :-)

Nodebox
-------
If there is nothing in the bin, it looks empty when viewed from top (node
"compost:wood_barrel_empty"). If there's anything in it (inputs or soil), it
looks full of dirt (node "compost:wood_barrel").

Internationalization
--------------------
The mod is compatible with intllib. Currently available translations:
* French
* Portuguese
* Romanian
* Russian

License
-------
see LICENSE.txt

Crafting
--------
Wood barrel:

| W |   | W |
|---|---|---|
| W |   | W |
| W | S | W |

W : wood
S : wood slab

Bugs
----
Report bugs on the forum topic or open a issue on GitHub.

Authors
-------
* Original Work: cd2 (cdqwertz) - cdqwertz.github.io
* Modified Work: Vitalie Ciubotaru <vitalie at ciubotaru dot tk>


code:
--File name: init.lua
--Project name: compost, a Mod for Minetest
--License: General Public License, version 3 or later
--Original Work Copyright (C) 2016 cd2 (cdqwertz) <cdqwertz@gmail.com>
--Modified Work Copyright (C) Vitalie Ciubotaru <vitalie at ciubotaru dot tk>

-- Load support for intllib.
local MP = minetest.get_modpath(minetest.get_current_modname())
local S, NS = dofile(MP.."/intllib.lua")

compost_version = '0.0.3'

compost = {}
compost.compostable_groups = {'flora', 'leaves', 'flower'}
compost.compostable_nodes = {
	'default:cactus',
	'default:papyrus',
	'default:dry_shrub',
	'default:junglegrass',
	'default:grass_1',
	'default:dry_grass_1',
	'farming:wheat',
	'farming:straw',
	'farming:cotton',
}
compost.compostable_items = {}
for _, v in pairs(compost.compostable_nodes) do
	compost.compostable_items[v] = true
end

local function formspec(pos)
	local spos = pos.x..','..pos.y..','..pos.z
	local formspec =
		'size[8,8.5]'..
		default.gui_bg..
		default.gui_bg_img..
		default.gui_slots..
		'list[nodemeta:'..spos..';src;0,0;8,1;]'..
		'list[nodemeta:'..spos..';dst;3.5,2;1,1;]'..
		'list[current_player;main;0,4.25;8,1;]'..
		'list[current_player;main;0,5.5;8,3;8]'..
		'listring[nodemeta:'..spos ..';dst]'..
		'listring[current_player;main]'..
		'listring[nodemeta:'..spos ..';src]'..
		'listring[current_player;main]'..
		default.get_hotbar_bg(0, 4.25)
	return formspec
end

local function is_compostable(input)
	if compost.compostable_items[input] then
		return true
	end
	for _, v in pairs(compost.compostable_groups) do
		if minetest.get_item_group(input, v) > 0 then
			return true
		end
	end
	return false
end

local function swap_node(pos, name)
	local node = minetest.get_node(pos)
	if node.name == name then
		return
	end
	node.name = name
	minetest.swap_node(pos, node)
end

local function count_input(pos)
	local q = 0
	local meta = minetest.get_meta(pos)
	local inv = meta:get_inventory()
	local stacks = inv:get_list('src')
	for k in pairs(stacks) do
		q = q + inv:get_stack('src', k):get_count()
	end
	return q
end

local function is_empty(pos)
	local meta = minetest.get_meta(pos)
	local inv = meta:get_inventory()
	local stacks = inv:get_list('src')
	for k in pairs(stacks) do
		if not inv:get_stack('src', k):is_empty() then
			return false
		end
	end
	if not inv:get_stack('dst', 1):is_empty() then
		return false
	end
	return true
end

local function update_nodebox(pos)
	if is_empty(pos) then
		swap_node(pos, "compost:wood_barrel_empty")
	else
		swap_node(pos, "compost:wood_barrel")
	end
end

local function update_timer(pos)
	local timer = minetest.get_node_timer(pos)
	local meta = minetest.get_meta(pos)
	local count = count_input(pos)
	if not timer:is_started() and count >= 8 then
		timer:start(30)
		meta:set_int('Progress', 0)
		meta:set_string('infotext', S("progress: @1%", "0"))
		return
	end
	if timer:is_started() and count < 8 then
		timer:stop()
		meta:set_string('infotext', S("To start composting, place some organic matter inside."))
		meta:set_int('progress', 0)
	end
end

local function create_compost(pos)
	local q = 8
	local meta = minetest.get_meta(pos)
	local inv = meta:get_inventory()
	local stacks = inv:get_list('src')
	for k in pairs(stacks) do
		local stack = inv:get_stack('src', k)
		if not stack:is_empty() then
			local count = stack:get_count()
			if count <= q then
				inv:set_stack('src', k, '')
				q = q - count
			else
				inv:set_stack('src', k, stack:get_name() .. ' ' .. (count - q))
				q = 0
				break
			end
		end
	end
	local dirt_count = inv:get_stack('dst', 1):get_count()
	inv:set_stack('dst', 1, 'default:dirt ' .. (dirt_count + 1))
end

local function on_timer(pos)
	local timer = minetest.get_node_timer(pos)
	local meta = minetest.get_meta(pos)
	local progress = meta:get_int('progress') + 10
	if progress >= 100 then
		create_compost(pos)
		meta:set_int('progress', 0)
	else
		meta:set_int('progress', progress)
	end
	if count_input(pos) >= 8 then
		meta:set_string('infotext', S("progress: @1%", progress))
		return true
	else
		timer:stop()
		meta:set_string('infotext', S("To start composting, place some organic matter inside."))
		meta:set_int('progress', 0)
		return false
	end
end

local function on_construct(pos)
	local meta = minetest.get_meta(pos)
	local inv = meta:get_inventory()
	inv:set_size('src', 8)
	inv:set_size('dst', 1)
	meta:set_string('infotext', S("To start composting, place some organic matter inside."))
	meta:set_int('progress', 0)
end

local function on_rightclick(pos, node, clicker, itemstack)
	minetest.show_formspec(
		clicker:get_player_name(),
		'compost:wood_barrel',
		formspec(pos)
	)
end

local function can_dig(pos,player)
	local meta = minetest.get_meta(pos)
	local inv  = meta:get_inventory()
	if inv:is_empty('src') and inv:is_empty('dst') then
		return true
	else
		return false
	end
end

local function allow_metadata_inventory_put(pos, listname, index, stack, player)
	if listname == 'src' and is_compostable(stack:get_name()) then
		return stack:get_count()
	else
		return 0
	end
end

local function on_metadata_inventory_put(pos, listname, index, stack, player)
	update_timer(pos)
	update_nodebox(pos)
	return
end

local function on_metadata_inventory_take(pos, listname, index, stack, player)
	update_timer(pos)
	update_nodebox(pos)
	return
end

local function allow_metadata_inventory_move(pos, from_list, from_index, to_list, to_index, count, player)
	local inv = minetest.get_meta(pos):get_inventory()
	if from_list == to_list then
		return inv:get_stack(from_list, from_index):get_count()
	else
		return 0
	end
end

local function on_punch(pos, node, player, pointed_thing)
	local meta = minetest.get_meta(pos)
	local inv = meta:get_inventory()
	local wielded_item = player:get_wielded_item()
	if not wielded_item:is_empty() then
		local wielded_item_name = wielded_item:get_name()
		local wielded_item_count = wielded_item:get_count()
		if is_compostable(wielded_item_name) and inv:room_for_item('src', wielded_item_name) then
			player:set_wielded_item('')
			inv:add_item('src', wielded_item_name .. ' ' .. wielded_item_count)
			update_nodebox(pos)
			update_timer(pos)
		end
	end
	local compost_count = inv:get_stack('dst', 1):get_count()
	local wielded_item = player:get_wielded_item() --recheck
	if compost_count > 0 and wielded_item:is_empty() then
		inv:set_stack('dst', 1, '')
		player:set_wielded_item('default:dirt ' .. compost_count)
		update_nodebox(pos)
		update_timer(pos)
	end
end

minetest.register_node("compost:wood_barrel_empty", {
	description = S("Empty Compost Bin"),
	tiles = {
		"default_wood.png",
	},
	drawtype = "nodebox",
	node_box = {
		type = "fixed",
		fixed = {{-1/2, -1/2, -1/2, 1/2, -3/8, 1/2},
			{-1/2, -1/2, -1/2, -3/8, 1/2, 1/2},
			{3/8, -1/2, -1/2, 1/2, 1/2, 1/2},
			{-1/2, -1/2, -1/2, 1/2, 1/2, -3/8},
			{-1/2, -1/2, 3/8, 1/2, 1/2, 1/2}},
	},
	selection_box = {
		type = "fixed",
		fixed = {-0.5, -0.5, -0.5, 0.5, 0.5, 0.5}
	},
	paramtype = "light",
	is_ground_content = false,
	groups = {choppy = 3},
	sounds =  default.node_sound_wood_defaults(),
	on_timer = on_timer,
	on_construct = on_construct,
	on_rightclick = on_rightclick,
	can_dig = can_dig,
	allow_metadata_inventory_put = allow_metadata_inventory_put,
	allow_metadata_inventory_move = allow_metadata_inventory_move,
	on_metadata_inventory_put = on_metadata_inventory_put,
	on_metadata_inventory_take = on_metadata_inventory_take,
	on_punch = on_punch,
})

minetest.register_node("compost:wood_barrel", {
	description = S("Compost Bin"),
	tiles = {
		"default_wood.png^compost_compost.png",
		"default_wood.png",
	},
	drawtype = "nodebox",
	node_box = {
		type = "fixed",
		fixed = {{-1/2, -1/2, -1/2, 1/2, -3/8, 1/2},
			{-1/2, -1/2, -1/2, -3/8, 1/2, 1/2},
			{3/8, -1/2, -1/2, 1/2, 1/2, 1/2},
			{-1/2, -1/2, -1/2, 1/2, 1/2, -3/8},
			{-1/2, -1/2, 3/8, 1/2, 1/2, 1/2},
			{-3/8, -1/2, -3/8, 3/8, 3/8, 3/8}},
	},
	selection_box = {
		type = "fixed",
		fixed = {-0.5, -0.5, -0.5, 0.5, 0.5, 0.5}
	},
	paramtype = "light",
	is_ground_content = false,
	groups = {choppy = 3, not_in_creative_inventory = 1},
	sounds =  default.node_sound_wood_defaults(),
	on_timer = on_timer,
	on_construct = on_construct,
	on_rightclick = on_rightclick,
	can_dig = can_dig,
	allow_metadata_inventory_put = allow_metadata_inventory_put,
	allow_metadata_inventory_move = allow_metadata_inventory_move,
	on_metadata_inventory_put = on_metadata_inventory_put,
	on_metadata_inventory_take = on_metadata_inventory_take,
	on_punch = on_punch,
})

minetest.register_craft({
	output = "compost:wood_barrel_empty",
	recipe = {
		{"group:wood", "", "group:wood"},
		{"group:wood", "", "group:wood"},
		{"group:wood", "stairs:slab_wood", "group:wood"}
	}
})


code:
-- Fallback functions for when `intllib` is not installed.
-- Code released under Unlicense <http://unlicense.org>.

-- Get the latest version of this file at:
--   https://raw.githubusercontent.com/minetest-mods/intllib/master/lib/intllib.lua

local function format(str, ...)
	local args = { ... }
	local function repl(escape, open, num, close)
		if escape == "" then
			local replacement = tostring(args[tonumber(num)])
			if open == "" then
				replacement = replacement..close
			end
			return replacement
		else
			return "@"..open..num..close
		end
	end
	return (str:gsub("(@?)@(%(?)(%d+)(%)?)", repl))
end

local gettext, ngettext
if minetest.get_modpath("intllib") then
	if intllib.make_gettext_pair then
		-- New method using gettext.
		gettext, ngettext = intllib.make_gettext_pair()
	else
		-- Old method using text files.
		gettext, ngettext = intllib.Getter()
	end
end

-- Fill in missing functions.

gettext = gettext or function(msgid, ...)
	return format(msgid, ...)
end

ngettext = ngettext or function(msgid, msgid_plural, n, ...)
	return format(n==1 and msgid or msgid_plural, ...)
end

return gettext, ngettext


code:
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: compost (a Minetest mod)\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-07-31 12:08+0200\n"
"PO-Revision-Date: 2017-05-16 14:25+0900\n"
"Last-Translator: Vitalie Ciubotaru <vitalie@ciubotaru.tk>\n"
"Language-Team: \n"
"Language: DE\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 1.5.4\n"

#: init.lua
msgid "loading..."
msgstr ""

#: init.lua
msgid "progress: @1%"
msgstr "Fortschritt: @1%"

#: init.lua
msgid "To start composting, place some organic matter inside."
msgstr "Zum Kompostieren mit organischem Material befüllen."

#: init.lua
msgid " moves stuff to compost bin at "
msgstr ""

#: init.lua
msgid " takes stuff from compost bin at "
msgstr ""

#: init.lua
msgid "Empty Compost Bin"
msgstr "Leeres Kompostsilo"

#: init.lua
msgid "Compost Bin"
msgstr "Kompostsilo"

#: init.lua
msgid " loaded."
msgstr ""


code:
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-07-31 12:08+0200\n"
"PO-Revision-Date: 2017-05-07 06:44+0200\n"
"Last-Translator: fat115 <fat115@framasoft.org>\n"
"Language-Team: \n"
"Language: fr\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 1.8.12\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"

#: init.lua
msgid "loading..."
msgstr "en cours de chargement..."

#: init.lua
msgid "progress: @1%"
msgstr "progression : @1%"

#: init.lua
msgid "To start composting, place some organic matter inside."
msgstr "Ajoutez de la matière organique pour obtenir du compost."

#: init.lua
msgid " moves stuff to compost bin at "
msgstr " a mis quelque chose dans le composteur à "

#: init.lua
msgid " takes stuff from compost bin at "
msgstr " a pris quelque chose dans le composteur à "

#: init.lua
msgid "Empty Compost Bin"
msgstr "Composteur vide."

#: init.lua
msgid "Compost Bin"
msgstr "Composteur"

#: init.lua
msgid " loaded."
msgstr " chargé."


code:
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-07-31 12:08+0200\n"
"PO-Revision-Date: 2017-05-07 06:46+0200\n"
"Last-Translator: BrunoMine <borgesdossantosbruno@gmail.com>\n"
"Language-Team: \n"
"Language: pt\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 1.8.12\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"

#: init.lua
msgid "loading..."
msgstr ""

#: init.lua
msgid "progress: @1%"
msgstr "progresso: @1%"

#: init.lua
msgid "To start composting, place some organic matter inside."
msgstr "Para iniciar a compostagem, coloque alguma materia organica dentro."

#: init.lua
msgid " moves stuff to compost bin at "
msgstr ""

#: init.lua
msgid " takes stuff from compost bin at "
msgstr ""

#: init.lua
msgid "Empty Compost Bin"
msgstr "Silo de Compostagem Vazio"

#: init.lua
msgid "Compost Bin"
msgstr "Silo de Compostagem"

#: init.lua
msgid " loaded."
msgstr ""


code:
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-07-31 12:08+0200\n"
"PO-Revision-Date: 2017-08-15 19:30+0900\n"
"Last-Translator: \n"
"Language-Team: \n"
"Language: ro\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 1.5.4\n"

#: init.lua
msgid "loading..."
msgstr "se încarcă..."

#: init.lua
msgid "progress: @1%"
msgstr "progres: @1%"

#: init.lua
msgid "To start composting, place some organic matter inside."
msgstr "Pentru a începe compostarea, adaugă materie organică."

#: init.lua
msgid " moves stuff to compost bin at "
msgstr "pune ceva în cutia de compost la"

#: init.lua
msgid " takes stuff from compost bin at "
msgstr "scoate ceva din cutia de compost la"

#: init.lua
msgid "Empty Compost Bin"
msgstr "Cutie de compost goală"

#: init.lua
msgid "Compost Bin"
msgstr "Cutie de compost"

#: init.lua
msgid " loaded."
msgstr "s-a încărcat."


code:
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-07-31 12:08+0200\n"
"PO-Revision-Date: 2017-08-15 19:31+0900\n"
"Last-Translator: \n"
"Language-Team: \n"
"Language: ru\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 1.5.4\n"

#: init.lua
msgid "loading..."
msgstr "загружается..."

#: init.lua
msgid "progress: @1%"
msgstr "прогресс: @1%"

#: init.lua
msgid "To start composting, place some organic matter inside."
msgstr "Чтобы начать компостирование, добавьте органику внутрь"

#: init.lua
msgid " moves stuff to compost bin at "
msgstr "кладёт что-то в компостный ящик на"

#: init.lua
msgid " takes stuff from compost bin at "
msgstr "берёт что-то из компостного ящика на"

#: init.lua
msgid "Empty Compost Bin"
msgstr "Пустой компостный ящик"

#: init.lua
msgid "Compost Bin"
msgstr "Компостный ящик"

#: init.lua
msgid " loaded."
msgstr "загрузился."


code:
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-07-31 12:08+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=CHARSET\n"
"Content-Transfer-Encoding: 8bit\n"

#: init.lua
msgid "loading..."
msgstr ""

#: init.lua
msgid "progress: @1%"
msgstr ""

#: init.lua
msgid "To start composting, place some organic matter inside."
msgstr ""

#: init.lua
msgid " moves stuff to compost bin at "
msgstr ""

#: init.lua
msgid " takes stuff from compost bin at "
msgstr ""

#: init.lua
msgid "Empty Compost Bin"
msgstr ""

#: init.lua
msgid "Compost Bin"
msgstr ""

#: init.lua
msgid " loaded."
msgstr ""


code:
name = compost
description = With this mod, you are able to compost grass, leaves, flowers.
depends = default, stairs
optional_depends = intllib
author = cdqwertz
title = Compost


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

# Custom for Visual Studio
*.cs     diff=csharp

# Standard to msysgit
*.doc	 diff=astextplain
*.DOC	 diff=astextplain
*.docx diff=astextplain
*.DOCX diff=astextplain
*.dot  diff=astextplain
*.DOT  diff=astextplain
*.pdf  diff=astextplain
*.PDF	 diff=astextplain
*.rtf	 diff=astextplain
*.RTF	 diff=astextplain


code:
# Windows image file caches
Thumbs.db
ehthumbs.db

# Folder config file
Desktop.ini

# Recycle Bin used on file shares
$RECYCLE.BIN/

# Windows Installer files
*.cab
*.msi
*.msm
*.msp

# Windows shortcuts
*.lnk

# =========================
# Operating System Files
# =========================

# OSX
# =========================

.DS_Store
.AppleDouble
.LSOverride

# Thumbnails
._*

# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns

# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk


code:
=-=-=-=-=-=-=-=-=-=

Crafting Bench
by: Philipbenr And DanDuncombe

=-=-=-=-=-=-=-=-=-=

Licence: MIT

see: LICENSE

=-=-=-=-=-=-=-=-=-=

An auto-crafting bench. Place raw materials into its inventory, define a crafting recipe in its main grid, and then every five seconds it will generate a crafted output.

This mod is compatible with hoppers.

The crafting rate can be modified in advanced settings.


code:
default
intllib?
hopper?
doc?


code: An auto-crafting bench


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

minetest.register_alias("castle:workbench", "crafting_bench:workbench")

local usage_help = S("The inventory on the left is for raw materials, the inventory on the right holds finished products. The crafting grid in the center defines what recipe this workbench will make use of; place raw materials into it in the crafting pattern corresponding to what you want to build.")

if minetest.get_modpath("hopper") and hopper ~= nil and hopper.add_container ~= nil then
	usage_help = usage_help .. "\n\n" .. S("This workbench is compatible with hoppers. Hoppers will insert into the raw material inventory and remove items from the finished goods inventory.")
end

local crafting_rate = minetest.settings:get("crafting_bench_crafting_rate")
if crafting_rate == nil then crafting_rate = 5 end

minetest.register_node("crafting_bench:workbench",{
	description = S("Autocrafting Workbench"),
	_doc_items_longdesc = string.format(S("A workbench that does work for you. Set a crafting recipe and provide raw materials and items will magically craft themselves once every %i seconds."), crafting_rate),
	_doc_items_usagehelp = usage_help,
	tiles = {
		"crafting_bench_workbench_top.png",
		"crafting_bench_workbench_bottom.png",
		"crafting_bench_workbench_side.png",
		"crafting_bench_workbench_side.png",
		"crafting_bench_workbench_back.png",
		"crafting_bench_workbench_front.png"
		},
	paramtype2 = "facedir",
	paramtype = "light",
	groups = {choppy=2,oddly_breakable_by_hand=2,flammable=2},
	sounds = default.node_sound_wood_defaults(),
	drawtype = "normal",
	on_construct = function ( pos )
		local meta = minetest.get_meta( pos )
			meta:set_string( 'formspec',
			'size[10,10;]' ..
			default.gui_bg ..
			default.gui_bg_img ..
			default.gui_slots ..
			'label[1,0;'..S('Source Material')..']' ..
			'list[context;src;1,1;2,4;]' ..
			'label[4,0;'..S('Recipe to Use')..']' ..
			'list[context;rec;4,1;3,3;]' ..
			'label[7.5,0;'..S('Craft Output')..']' ..
			'list[context;dst;8,1;1,4;]' ..
			'list[current_player;main;1,6;8,4;]' )
		meta:set_string( 'infotext', S('Workbench'))
		local inv = meta:get_inventory()
		inv:set_size( 'src', 2 * 4 )
		inv:set_size( 'rec', 3 * 3 )
		inv:set_size( 'dst', 1 * 4 )
	end,
	can_dig = function(pos,player)
		local meta = minetest.get_meta(pos);
		local inv = meta:get_inventory()
		return inv:is_empty("main")
	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 workbench 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 workbench 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 takes stuff from workbench at @2", player:get_player_name(), minetest.pos_to_string(pos)))
	end,
})
local get_recipe = function ( inv )
	local result, needed, input
	needed = inv:get_list( 'rec' )

	result, input = minetest.get_craft_result( {
		method = 'normal',
		width = 3,
		items = needed
	})

	local totalneed = {}

	if result.item:is_empty() then
		result = nil
	else
		result = result.item
		for _, item in ipairs( needed ) do
			if item ~= nil and not item:is_empty() and not inv:contains_item( 'src', item ) then
				result = nil
				break
			end
			if item ~= nil and not item:is_empty() then
				if totalneed[item:get_name()] == nil then
					totalneed[item:get_name()] = 1
				else
					totalneed[item:get_name()] = totalneed[item:get_name()] + 1
				end
			end
		end
		for name, number in pairs( totalneed ) do
			local totallist = inv:get_list( 'src' )
			for i, srcitem in pairs( totallist ) do
				if srcitem:get_name() == name then
					local taken = srcitem:take_item( number )
					number = number - taken:get_count()
					totallist[i] = srcitem
				end
				if number <= 0 then
					break
				end
			end
			if number > 0 then
				result = nil
				break
			end
		end
	end

	return needed, input, result
end

minetest.register_abm( {
	nodenames = { 'crafting_bench:workbench' },
	interval = crafting_rate,
	chance = 1,
	action = function ( pos, node )
		local meta = minetest.get_meta( pos )
		local inv = meta:get_inventory()
		local result, newinput, needed
		if not inv:is_empty( 'src' ) then
			-- Check for a valid recipe and sufficient resources to craft it
			needed, newinput, result = get_recipe( inv )
			if result ~= nil and inv:room_for_item( 'dst', result ) then
				inv:add_item( 'dst', result )
				for i, item in pairs( needed ) do
					if item ~= nil and item ~= '' then
						inv:remove_item( 'src', ItemStack( item ) )
					end
					if newinput[i] ~= nil and not newinput[i]:is_empty() then
						inv:add_item( 'src', newinput[i] )
					end
				end
			end
		end
	end
} )

local function has_locked_chest_privilege(meta, player)
	if player:get_player_name() ~= meta:get_string("owner") then
		return false
	end
	return true
end

minetest.register_craft({
	output = "crafting_bench:workbench",
	recipe = {
		{"default:steel_ingot","default:steel_ingot","default:steel_ingot"},
		{"default:wood", "default:wood","default:steel_ingot"},
		{"default:tree", "default:tree","default:steel_ingot"},
	}
})

-- Hopper compatibility
if minetest.get_modpath("hopper") and hopper ~= nil and hopper.add_container ~= nil then
	hopper:add_container({
		{"top", "crafting_bench:workbench", "dst"},
		{"side", "crafting_bench:workbench", "src"},
		{"bottom", "crafting_bench:workbench", "src"},
	})
end


code:
-- Fallback functions for when `intllib` is not installed.
-- Code released under Unlicense <http://unlicense.org>.

-- Get the latest version of this file at:
--   https://raw.githubusercontent.com/minetest-mods/intllib/master/lib/intllib.lua

local function format(str, ...)
	local args = { ... }
	local function repl(escape, open, num, close)
		if escape == "" then
			local replacement = tostring(args[tonumber(num)])
			if open == "" then
				replacement = replacement..close
			end
			return replacement
		else
			return "@"..open..num..close
		end
	end
	return (str:gsub("(@?)@(%(?)(%d+)(%)?)", repl))
end

local gettext, ngettext
if minetest.get_modpath("intllib") then
	if intllib.make_gettext_pair then
		-- New method using gettext.
		gettext, ngettext = intllib.make_gettext_pair()
	else
		-- Old method using text files.
		gettext = intllib.Getter()
	end
end

-- Fill in missing functions.

gettext = gettext or function(msgid, ...)
	return format(msgid, ...)
end

ngettext = ngettext or function(msgid, msgid_plural, n, ...)
	return format(n==1 and msgid or msgid_plural, ...)
end

return gettext, ngettext


code:
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-02-28 21:08-0700\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=CHARSET\n"
"Content-Transfer-Encoding: 8bit\n"

#: init.lua:7
msgid ""
"The inventory on the left is for raw materials, the inventory on the right "
"holds finished products. The crafting grid in the center defines what recipe "
"this workbench will make use of; place raw materials into it in the crafting "
"pattern corresponding to what you want to build."
msgstr ""

#: init.lua:10
msgid ""
"This workbench is compatible with hoppers. Hoppers will insert into the raw "
"material inventory and remove items from the finished goods inventory."
msgstr ""

#: init.lua:19 init.lua:49
msgid "Workbench"
msgstr ""

#: init.lua:20
#, lua-format
msgid ""
"A workbench that does work for you. Set a crafting recipe and provide raw "
"materials and items will magically craft themselves once every %i seconds."
msgstr ""

#: init.lua:42
msgid "Source Material"
msgstr ""

#: init.lua:44
msgid "Recipe to Use"
msgstr ""

#: init.lua:46
msgid "Craft Output"
msgstr ""

#: init.lua:61
msgid "@1 moves stuff in workbench at @2"
msgstr ""

#: init.lua:64
msgid "@1 moves stuff to workbench at @2"
msgstr ""

#: init.lua:67
msgid "@1 takes stuff from workbench at @2"
msgstr ""


code:
name = crafting_bench
depends = default
optional_depends = intllib, hopper, doc
description = An auto-crafting bench


code:
# The number of seconds between each operation of the crafting bench.
crafting_bench_crafting_rate (Crafting rate) int 5 1 60


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


code:
# curtains
Curtains

Mod adding coloured curtains, very lightweight

by Jozet

based at 'xdecor' curtain code
![Screenshot](https://forum.minetest.net/download/file.php?mode=view&id=16032&sid=757c1d3ea0bd43382b8ab4776fd27758)


code:
-- Wooden tapestry top
minetest.register_node("curtains:tapestry_top", {
        drawtype = "nodebox",
        description = "Wooden Curtain Rod",
        tiles = {"default_wood.png"},
        sunlight_propagates = true,
        groups = {flammable=3,choppy=2,oddly_breakable_by_hand=1},
--        sounds = default.node_sound_defaults(),
        paramtype = "light",
        paramtype2 = "facedir",
        node_box = {
                type = "fixed",
                fixed = {-0.6,-0.5,0.375,0.6,-0.375,0.5},
        },
        selection_box = {
                type = "fixed",
                fixed = {-0.6,-0.5,0.375,0.6,-0.375,0.5},
        },
})

minetest.register_craft({
        type = "shapeless",
        output = "curtains:tapestry_top",
        recipe = {"default:stick"},
})

minetest.register_craft({
        type = "shapeless",
        recipe = {"curtains:tapestry_top"},
        output = "default:stick",
})

-- Curtains
local curtain_colors = { --name, color, colorize(hex or color name:intensity(1-255))
{'Black', 'black', '#26231f:200'},
{'Blue', 'blue', '#284965:200'},
{'Brown', 'brown', '#65493f:200'},
{'Cyan', 'cyan', '#3a8d94:200'},
{'Dark Green', 'dark_green', '#485831:200'},
{'Dark Grey', 'dark_grey', '#464342:200'},
{'Green', 'green', '#739251:200'},
{'Grey', 'grey', '#85817e:200'},
{'Magenta', 'magenta', '#b74679:200'},
{'Orange', 'orange', '#bb6c3e:200'},
{'Pink', 'pink', '#a2655f:200'},
{'Red', 'red', '#893734:200'},
{'Violet', 'violet', '#5e3170:200'},
{'White', 'white', '#bcb2a6:200'},
{'Yellow', 'yellow', '#c0a342:200'},
}

for i in pairs(curtain_colors) do
		local name = curtain_colors[i][1]
		local color = curtain_colors[i][2]
		local hex = curtain_colors[i][3]

		minetest.register_node("curtains:curtain_"..color, {
		description = color:gsub("^%l", string.upper).." Curtain",
		walkable = false,
		tiles = {"homedecor_curtain.png^[colorize:"..hex},
		inventory_image = "homedecor_curtain.png^[colorize:"..hex,
		wield_image = "homedecor_curtain.png^[colorize:"..hex,
		drawtype = "signlike",
		paramtype2 = "colorwallmounted",
		groups = {dig_immediate=3, flammable=3},
		selection_box = {type="wallmounted"},
		on_rightclick = function(pos, node, _, itemstack)
			minetest.set_node(pos, {name="curtains:curtain_open_"..color, param2=node.param2})
			return itemstack
		end
	})

	minetest.register_node("curtains:curtain_open_"..color, {
		tiles = {"homedecor_curtain_open.png^[colorize:"..hex,},
		drawtype = "signlike",
		paramtype2 = "colorwallmounted",
		walkable = false,
		groups = {dig_immediate=3, flammable=3, not_in_creative_inventory=1},
		selection_box = {type="wallmounted"},
		drop = "curtains:curtain_"..color,
		on_rightclick = function(pos, node, _, itemstack)
			minetest.set_node(pos, {name="curtains:curtain_"..color, param2=node.param2})
			return itemstack
		end
	})

	minetest.register_craft({
		output = "curtains:curtain_"..color.." 1",
		recipe = {
			{"", "wool:"..color, ""}
		}
	})
end


code:
name = curtains
description = Adds curtains.
title = Curtains
author = Jozet
depends = default, wool


code:
# Death Compass

![](./textures/death_compass_16_0.png) ![](./textures/death_compass_16_1.png) ![](./textures/death_compass_16_2.png) ![](./textures/death_compass_16_4.png) ![](./textures/death_compass_16_5.png) ![](./textures/death_compass_16_6.png) ![](./textures/death_compass_16_7.png) ![](./textures/death_compass_16_8.png) ![](./textures/death_compass_16_9.png) ![](./textures/death_compass_16_10.png) ![](./textures/death_compass_16_11.png) ![](./textures/death_compass_16_12.png) ![](./textures/death_compass_16_13.png) ![](./textures/death_compass_16_14.png) ![](./textures/death_compass_16_15.png) ![](./textures/death_compass_16_0.png)

Have you ever died and been frustrated with being unable to find your way back to your corpse? Carry a death compass with you. When you die, the compass will respawn in your inventory and be magically bound to point its bony finger to the location of your demise.

But hurry! The server administrator may have limited the duration that the compass will function for.

Crafting recipe: four bones and a mese fragment.

Alternately, there is a configuration setting (false by default) that automatically gives a player a death compass whenever they die. This automatic compass will disappear back to wherever it came from once its grim task is complete.


code:
-- compass configuration interface - adjustable from other mods or minetest.conf settings
death_compass = {}

local S = minetest.get_translator("death_compass")

 -- how many seconds does the death compass work for? 0 for indefinite
local duration = tonumber(minetest.settings:get("death_compass_duration")) or 30
local automatic = minetest.settings:get_bool("death_compass_automatic", false)

local range_to_inactivate = 5

local hud_position = {
	x= tonumber(minetest.settings:get("death_compass_hud_x")) or 0.5,
	y= tonumber(minetest.settings:get("death_compass_hud_y")) or 0.9,
}
local hud_color = tonumber("0x" .. (minetest.settings:get("death_compass_hud_color") or "FFFF00")) or 0xFFFF00

-- If round is true the return string will only have the two largest-scale values
local function clock_string(seconds, round)
	seconds = math.floor(seconds)
	local days = math.floor(seconds/86400)
	seconds = seconds - days*86400
	local hours = math.floor(seconds/3600)
	seconds = seconds - hours*3600
	local minutes = math.floor(seconds/60)
	seconds = seconds - minutes*60

	local ret = {}
	if days == 1 then
		table.insert(ret, S("1 day"))
	elseif days > 1 then
		table.insert(ret, S("@1 days", days))
	end
	if hours == 1 then
		table.insert(ret, S("1 hour"))
	elseif hours > 1 then
		table.insert(ret, S("@1 hours", hours))
	end
	if minutes == 1 then
		table.insert(ret, S("1 minute"))
	elseif minutes > 1 then
		table.insert(ret, S("@1 minutes", minutes))
	end
	if seconds == 1 then
		table.insert(ret, S("1 second"))
	elseif seconds > 1 then
		table.insert(ret, S("@1 seconds", seconds))
	end

	if #ret == 0 then
		return S("@1 seconds", 0)
	end
	if #ret == 1 then
		return ret[1]
	end
	if round or #ret == 2 then
		return S("@1 and @2", ret[1], ret[2])
	end

	return table.concat(ret, S(", "))
end

local documentation = S("This does nothing in its current inert state. If you have this in your inventory when you die, however, it will follow you into your next life's inventory and point toward the location of your previous life's end.")
local durationdesc
if duration > 0 then
	durationdesc = S("The Death Compass' guidance will only last for @1 after death.", clock_string(duration, false))
else
	durationdesc = S("The Death Compass will point toward your corpse until you find it.")
end

-- set a position to the compass stack
local function set_target(stack, pos, name)
	local meta=stack:get_meta()
	meta:set_string("target_pos", minetest.pos_to_string(pos))
	meta:set_string("target_corpse", name)
	meta:set_int("time_of_death", minetest.get_gametime())
end

-- Get compass target
local function get_destination(player, stack)
	local posstring = stack:get_meta():get_string("target_pos")
	if posstring ~= "" then
		return minetest.string_to_pos(posstring)
	end
end

-- looped ticking sound if there's a duration on this
local player_ticking = {}
local function start_ticking(player_name)
	if not player_ticking[player_name] then
		player_ticking[player_name] = minetest.sound_play("death_compass_tick_tock",
			{to_player = player_name, gain = 0.125, loop = true})
	end
end
local function stop_ticking(player_name)
	local tick_tock_handle = player_ticking[player_name]
	if tick_tock_handle then
		minetest.sound_stop(tick_tock_handle)
		player_ticking[player_name] = nil
	end
end

local player_huds = {}
local function hide_hud(player, player_name)
	local id = player_huds[player_name]
	if id then
		player:hud_remove(id)
		player_huds[player_name] = nil
	end
end
local function update_hud(player, player_name, compass)
	local metadata = compass:get_meta()

	local target_pos = minetest.string_to_pos(metadata:get_string("target_pos"))
	local player_pos = player:get_pos()
	local distance = vector.distance(player_pos, target_pos)
	if not target_pos then
		return
	end

	local time_of_death = metadata:get_int("time_of_death")
	local target_name = metadata:get_string("target_corpse")

	local description
	if duration > 0 then
		local remaining = time_of_death + duration - minetest.get_gametime()
		if remaining < 0 then
			return
		end
		description = S("@1m to @2's corpse, @3 remaining", math.floor(distance),
			target_name, clock_string(remaining, true))
	else
		description = S("@1m to @2's corpse, died @3 ago", math.floor(distance),
			target_name, clock_string(minetest.get_gametime() - time_of_death, true))
	end

	local id = player_huds[player_name]
	if not id then
		id = player:hud_add({
			hud_elem_type = "text",
			position = hud_position,
			text = description,
			number = hud_color,
			scale = 20,
		})
		player_huds[player_name] = id
	else
		player:hud_change(id, "text", description)
	end
end

-- get right image number for players compass
local function get_compass_stack(player, stack)
	local target = get_destination(player, stack)
	local inactive_return
	if automatic then
		inactive_return = ItemStack("")
	else
		inactive_return = ItemStack("death_compass:inactive")
	end

	if not target then
		return inactive_return
	end
	local pos = player:get_pos()
	local distance = vector.distance(pos, target)
	local player_name = player:get_player_name()

	if distance < range_to_inactivate then
		stop_ticking(player_name)
		minetest.sound_play("death_compass_bone_crunch", {to_player=player_name, gain = 1.0})
		return inactive_return
	end

	local dir = player:get_look_horizontal()
	local angle_north = math.deg(math.atan2(target.x - pos.x, target.z - pos.z))
	if angle_north < 0 then
		angle_north = angle_north + 360
	end
	local angle_dir = math.deg(dir)
	local angle_relative = (angle_north + angle_dir) % 360
	local compass_image = math.floor((angle_relative/22.5) + 0.5)%16

	-- create new stack with metadata copied
	local metadata = stack:get_meta():to_table()
	local meta_fields = metadata.fields
	local time_of_death = tonumber(meta_fields.time_of_death)

	if duration > 0 then
		local remaining = time_of_death + duration - minetest.get_gametime()
		if remaining < 0 then
			stop_ticking(player_name)
			minetest.sound_play("death_compass_bone_crunch", {to_player=player_name, gain = 1.0})
			return inactive_return
		end
		start_ticking(player_name)
	end

	local newstack = ItemStack("death_compass:dir"..compass_image)
	if metadata then
		newstack:get_meta():from_table(metadata)
	end
	return newstack
end

-- update inventory and hud
minetest.register_globalstep(function(dtime)
	for i, player in ipairs(minetest.get_connected_players()) do
		local player_name = player:get_player_name()
		local compass_in_quickbar
		local inv = player:get_inventory()
		if inv then
			for i, stack in ipairs(inv:get_list("main")) do
				if i > 8 then
					break
				end
				if string.sub(stack:get_name(), 0, 17) == "death_compass:dir" then
					player:get_inventory():set_stack("main", i, get_compass_stack(player, stack))
					compass_in_quickbar = true
				end
			end
			if compass_in_quickbar then
				local wielded = player:get_wielded_item()
				if string.sub(wielded:get_name(), 0, 17) == "death_compass:dir" then
					update_hud(player, player_name, wielded)
				else
					hide_hud(player, player_name)
				end
			end
		end
		if not compass_in_quickbar then
			stop_ticking(player_name)
			hide_hud(player, player_name)
		end
	end
end)

-- register items
for i = 0, 15 do
	local image = "death_compass_16_"..i..".png"
	minetest.register_craftitem("death_compass:dir"..i, {
		description = S("Death Compass"),
		inventory_image = image,
		wield_image = image,
		stack_max = 1,
		groups = {death_compass = 1, not_in_creative_inventory = 1},
	})
end

if not automatic then
	local display_doc = function(itemstack, user)
		local player_name = user:get_player_name()
		minetest.chat_send_player(player_name, documentation .. "\n" .. durationdesc)
	end

	minetest.register_craftitem("death_compass:inactive", {
		description = S("Death Compass"),
		_doc_items_longdesc = documentation,
		_doc_items_usagehelp = durationdesc,
		inventory_image = "death_compass_inactive.png",
		wield_image = "death_compass_inactive.png",
		stack_max = 1,
        on_place = display_doc,
        on_secondary_use = display_doc,
	})

	minetest.register_craft({
		output = 'death_compass:inactive',
		recipe = {
			{'', 'mesecraft_bones:bones', ''},
			{'mesecraft_bones:bones', 'default:mese_crystal_fragment', 'mesecraft_bones:bones'},
			{'', 'mesecraft_bones:bones', ''}
		}
	})

	-- Allow a player to deliberately deactivate a death compass
	minetest.register_craft({
		output = 'death_compass:inactive',
        type = "shapeless",
        recipe = {
            'group:death_compass',
		}
	})

end

local player_death_location = {}
minetest.register_on_dieplayer(function(player, reason)
	local player_name = player:get_player_name()
	local inv = minetest.get_inventory({type="player", name=player:get_player_name()})
	local list = inv:get_list("main")
	local count = 0
	if automatic then
		count = 1
	else
		for i, itemstack in pairs(list) do
			if itemstack:get_name() == "death_compass:inactive" then
				count = count + itemstack:get_count()
				list[i] = ItemStack("")
			end
		end
	end
	if count > 0 then
		inv:set_list("main", list)
		player_death_location[player_name] = {count=count,pos=player:get_pos()}
	end

end)
-- Called when a player dies
-- `reason`: a PlayerHPChangeReason table, see register_on_player_hpchange

minetest.register_on_respawnplayer(function(player)
	local player_name = player:get_player_name()
	local compasses = player_death_location[player_name]
	if compasses then
		local inv = minetest.get_inventory({type="player", name=player_name})

		-- Remove any death compasses they might still have for some reason
		local current = inv:get_list("main")
		for i, item in pairs(current) do
			if item:get_name() == "death_compass:inactive" then
				current[i] = ItemStack("")
			end
		end
		inv:set_list("main", current)

		-- give them new compasses pointing to their place of death
		for i = 1, compasses.count do
			local compass = ItemStack("death_compass:dir0")
			set_target(compass, compasses.pos, player_name)
			inv:add_item("main", compass)
		end
	end
	return false
end)
--    * Called when player is to be respawned
--    * Called _before_ repositioning of player occurs
--    * return true in func to disable regular player placement


code:
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-02-02 19:34-0700\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=CHARSET\n"
"Content-Transfer-Encoding: 8bit\n"

#: death_compass\init.lua:24
msgid "1 day"
msgstr ""

#: death_compass\init.lua:26
msgid "@1 days"
msgstr ""

#: death_compass\init.lua:29
msgid "1 hour"
msgstr ""

#: death_compass\init.lua:31
msgid "@1 hours"
msgstr ""

#: death_compass\init.lua:34
msgid "1 minute"
msgstr ""

#: death_compass\init.lua:36
msgid "@1 minutes"
msgstr ""

#: death_compass\init.lua:39
msgid "1 second"
msgstr ""

#: death_compass\init.lua:41
#: death_compass\init.lua:45
msgid "@1 seconds"
msgstr ""

#: death_compass\init.lua:51
msgid "@1 and @2"
msgstr ""

#: death_compass\init.lua:54
msgid ", "
msgstr ""

#: death_compass\init.lua:57
msgid ""
"This does nothing in its current inert state. If you have this in your "
"inventory when you die, however, it will follow you into your next life's "
"inventory and point toward the location of your previous life's end."
msgstr ""

#: death_compass\init.lua:60
msgid "The Death Compass' guidance will only last for @1 after death."
msgstr ""

#: death_compass\init.lua:62
msgid ""
"The Death Compass will point toward your previous corpse until you find it."
msgstr ""

#: death_compass\init.lua:141
msgid "@1m to @2's corpse, @3 remaining"
msgstr ""

#: death_compass\init.lua:144
msgid "@1m to @2's corpse, died @3 ago"
msgstr ""

#: death_compass\init.lua:181
#: death_compass\init.lua:196
msgid "Death Compass"
msgstr ""


code:
@echo off
setlocal ENABLEEXTENSIONS ENABLEDELAYEDEXPANSION
cd ..
set LIST=
for /r %%X in (*.lua) do set LIST=!LIST! %%X
..\intllib\tools\xgettext.bat %LIST%


code:
name = death_compass
description = A compass that points to the last place you died


code:
# The number of seconds the death compass will remain active for.
# Set this to 0 to let the death compass continue to be active indefinitely.
death_compass_duration (Death compass duration) int 0

# When a player dies they'll be given a death compass automatically, and it will
# disappear when its duration expires or the player reaches the death location
death_compass_automatic (Death compass given to players automatically) bool false

[HUD properties]
death_compass_hud_x (Relative X coordinate of HUD) float 0.5
death_compass_hud_y (Relative Y coordinate of HUD) float 0.9
death_compass_hud_color (Color string for HUD) string FFFF00


code:
minetest.register_craft({
	output = 'decoblocks:scarecrow',
	recipe = {
		{'', 'farming:pumpkin', ''},
		{'default:stick', 'farming:straw', 'default:stick'},
		{'', 'group:wood', ''},
	}
})

minetest.register_craft({
	output = 'decoblocks:paper_lantern',
	recipe = {
		{'group:wood', 'default:paper', 'group:wood'},
		{'default:paper', 'morelights:bulb', 'default:paper'},
		{'group:wood', 'default:paper', 'group:wood'},
	}
})

minetest.register_craft({
        output = 'decoblocks:spikes 4',
        recipe = {
                {'', '', ''},
                {'', 'default:steel_ingot', ''},
                {'', 'default:steel_ingot', ''},
        }
})

--These recipes depend on Ethereal, but are more logical.

minetest.register_craft({
        output = 'decoblocks:bamboo_fence',
        recipe = {
                {'', '', ''},
                {'ethereal:bamboo', 'default:stick', 'ethereal:bamboo'},
                {'ethereal:bamboo', 'default:stick', 'ethereal:bamboo'},
        }
})


code:
local path = minetest.get_modpath("decoblocks")

dofile(path.."/nodes.lua")
dofile(path.."/crafts.lua")


code:
name = decoblocks
description = misc decorations will be merged eventually.
depends = default, xpanes
optional_depends =
author = D00Med
title = DecoBlocks


code:
read_globals = {
	"vector",
	"screwdriver",
	"minetest",
	"default",
	"pipeworks",
	"dump",
	"VoxelArea",
}

globals = {
	"digilines",
}