sorcery  Check-in [82178e0a16]

Overview
Comment:changes, merges, additions galore
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: 82178e0a166ffc2a1e7237d685f25111c113116b3b9a3aaea0b593607c500c55
User & Date: lexi on 2020-08-16 02:05:40
Other Links: manifest | tags
Context
2020-08-17
13:35
fix bugs, finish grinder, add conduits and condensers to extract and transmit energy from leylines check-in: 9278734b41 user: lexi tags: trunk
2020-08-16
02:05
changes, merges, additions galore check-in: 82178e0a16 user: lexi tags: trunk
2020-08-14
06:17
fix(color): Actually get hsl brightening working Reimplemented based on the algorithms in Computer Graphics: Principles and Practice. Only lightens based on luminosity right now, which makes beautifully saturated, but not pastel colors, so some tweaking might be recommended. Leaf check-in: 794d5b023a user: glowpelt tags: glowpelt/hsl
2020-08-11
21:39
initial commit check-in: 956134c50b user: lexi tags: trunk
Changes

Modified altar.lua from [3de8edac81] to [1cf2c5fc2a].

   120    120   							x = range(-0.6, 0.6);
   121    121   							z = range(-0.6, 0.6);
   122    122   							y = range(-0.6, 0.6);
   123    123   						};
   124    124   						acceleration = {
   125    125   							x = 0; y = range(0,1); z = 0;
   126    126   						};
   127         -						texture = 'sorcery_sparkle.png' ..
   128         -							'^[transform' .. (math.random(8) - 1) ..
   129         -							'^[multiply:' .. color:brighten(1.7):hex();
          127  +						texture = sorcery.lib.image('sorcery_sparkle.png'):
          128  +							transform(math.random(8) - 1):
          129  +							multiply(color:brighten(1.7)):
          130  +								render();
   130    131   						glow = 14;
   131    132   					}
   132    133   				end
   133    134   				for i=0,48 do
   134    135   					minetest.add_particle{
   135    136   						pos = {
   136    137   							x = altar.x + range(-0.3,0.3);

Modified coins.lua from [cdf0329a68] to [e5af87fe4c].

   101    101   	on_construct = function(pos)
   102    102   		local meta = minetest.get_meta(pos)
   103    103   		local inv = meta:get_inventory()
   104    104   		inv:set_size('ingot',1)
   105    105   		inv:set_size('gem',1)
   106    106   		inv:set_size('output',1)
   107    107   		meta:set_string('formspec', [[
   108         -			formspec_version[3] real_coordinates[true]
   109    108   			size[8,6]
   110    109   			list[context;ingot;2,0.5;1,1;]
   111    110   				image[2,0.5;1,1;sorcery_ingot_outline.png]
   112    111   			list[context;gem;3,0.5;1,1;]
   113    112   				image[3,0.5;1,1;sorcery_diamond_outline.png]
   114    113   			list[context;output;5,0.5;1,1;]
   115    114   			list[current_player;main;0,2;8,4;]

Modified data/enchants.lua from [ac299c4e97] to [b3a388d8b4].

            1  +-- an optional 'enchant' function can be defined, and will
            2  +-- be called when the enchantment is placed on the object
            3  +local allgroups = {
            4  +	'sword'; 'pick'; 'pickaxe';
            5  +	'sickle'; 'scythe'; 'shovel';
            6  +	'hoe'; 'helmet'; 'leggings';
            7  +	'chestplate'; 'boots';
            8  +} 
     1      9   return {
     2     10   	endure = { -- withstand more blows
     3     11   		name = 'Endure';
           12  +		cost = 1;
     4     13   		tone = {232,102,255};
     5     14   		desc = 'durability magnified';
     6     15   		affinity = 'counterpraxic';
     7         -		apply = function(stack,power)
           16  +		groups = allgroups;
           17  +		recipe = {
           18  +			{lens = 'convex',    gem = 'amethyst', dmg = 2};
           19  +			{lens = 'rectifier', gem = 'emerald',  dmg = 4};
           20  +			{lens = 'convex',    gem = 'emerald',  dmg = 2};
           21  +		};
           22  +		apply = function(stack,power,base)
           23  +			local caps = table.copy(stack:get_definition().tool_capabilities)
           24  +			for g,v in pairs(caps.groupcaps) do
           25  +				local unit = base.groupcaps[g].uses * 0.6
           26  +				caps.groupcaps[g].uses = v.uses + unit*power
           27  +			end
           28  +			stack:get_meta():set_tool_capabilities(caps)
           29  +			return stack
     8     30   		end;
     9     31   	};
    10         -	drain = {}; -- health vampirism
           32  +	drain = {
           33  +		groups = {'sword'};
           34  +		cost = 4;
           35  +	}; -- health vampirism
    11     36   	harvest = { -- kills or digging ore replenish durability
    12     37   		name = 'Harvest';
           38  +		cost = 0; -- energy is only depleted when repair takes place
    13     39   		tone = {255,84,187};
    14     40   		affinity = 'syncretic';
           41  +		groups = {
           42  +			'pick'; 'pickaxe'; 'sword';
           43  +		};
           44  +		recipe = {
           45  +			{lens = 'amplifier', gem = 'ruby',     dmg = 5};
           46  +			{lens = 'concave',   gem = 'mese',     dmg = 1};
           47  +			{lens = 'concave',   gem = 'sapphire', dmg = 1};
           48  +		};
    15     49   		desc = 'some damage is repaired when used to mine ore or kill an attacker';
    16         -		apply = function(stack,power)
           50  +		on_dig = function(ctx)
           51  +			local orepfx = "stone_with_" -- }:<
           52  +			-- local oredrop = ' lump'
           53  +			local dug = minetest.get_node(ctx.target.under)
           54  +			local barename = string.sub(dug.name, string.find(dug.name, ':') + 1)
           55  +			print('is ore? ',dug.name,barename)
           56  +			if minetest.get_item_group(dug.name, ore) ~= 0 or
           57  +			   string.sub(barename,1,string.len(orepfx)) == orepfx 
           58  +			then
           59  +				print('is ore!')
           60  +				ctx.tool:add_wear(-(sorcery.enchant.strength(ctx.tool,'harvest') * 2000))
           61  +				ctx.cost = 3
           62  +			end
    17     63   		end;
    18     64   	};
    19     65   	conserve = { -- use less magical energy
    20     66   		name = 'Conserve';
    21     67   		tone = {84,255,144};
           68  +		cost = 0;
    22     69   		desc = 'enchantments last longer before running out of power to sustain them.';
           70  +		groups = allgroups;
    23     71   		affinity = 'syncretic';
    24         -		apply = function(stack,power)
    25         -		end;
           72  +		recipe = {
           73  +			{lens = 'rectifier', gem = 'mese',     dmg = 7};
           74  +			{lens = 'rectifier', gem = 'sapphire', dmg = 2};
           75  +			{lens = 'rectifier', gem = 'amethyst', dmg = 2};
           76  +		};
           77  +		-- implemented in sorcery/enchanter.lua:register_on_dig
    26     78   	};
    27     79   	dowse = { -- send up flare when valuable ores are nearby
    28     80   		name = 'Dowse';
    29     81   		tone = {241,251,113};
           82  +		cost = 1;
    30     83   		desc = 'strike colored sparks when used to dig near valuable ore.';
           84  +		groups = {'pick','pickaxe'};
    31     85   		affinity = 'cognic';
    32         -		apply = function(stack,power)
           86  +		recipe = {
           87  +			{lens = 'concave', gem = 'ruby',     dmg = 3};
           88  +			{lens = 'concave', gem = 'emerald',  dmg = 3};
           89  +			{lens = 'concave', gem = 'sapphire', dmg = 3};
           90  +		};
           91  +		on_dig = function(ctx)
           92  +			local range = 4*sorcery.enchant.strength(ctx.tool,'dowse')
           93  +			local colors = {
           94  +				['default:stone_with_gold'    ] = {255,234,182};
           95  +				['default:stone_with_mese'    ] = {231,255,151};
           96  +				['default:stone_with_diamond' ] = {180,253,255};
           97  +				['sorcery:stone_with_iridium' ] = {243,180,255};
           98  +				['sorcery:stone_with_tungsten'] = {119,234,196};
           99  +			}
          100  +			local search = {} for k in pairs(colors)
          101  +				do search[#search+1] = k end
          102  +			local nodes = minetest.find_nodes_in_area(
          103  +				vector.subtract(ctx.pos,range),
          104  +				vector.add(ctx.pos,range), search)
          105  +			for _,n in pairs(nodes) do
          106  +				-- we're going to use some ugly math tricks
          107  +				-- to avoid having to do a square root, since
          108  +				-- that's an expensive operation and we don't
          109  +				-- need that level of precision; we can
          110  +				-- approximate the distance without it
          111  +				local delta = vector.subtract(n,ctx.pos)
          112  +				local dstsq = (delta.x^2) + (delta.y^2) + (delta.z^2)
          113  +				if dstsq < range^2 then
          114  +					local dstfac = 1 - (dstsq / range^2)
          115  +					ctx.sparks[#ctx.sparks+1] = {
          116  +						color = sorcery.lib.color(colors[minetest.get_node(n).name]);
          117  +						count = 100 * dstfac;
          118  +					}
          119  +				end
          120  +			end
    33    121   		end;
    34    122   	};
    35    123   	pierce = { -- faster mining speed
    36    124   		name = 'Pierce';
          125  +		cost = 3;
    37    126   		tone = {113,240,251};
    38         -		desc = 'rip through solid stone like a hot knife through butter';
          127  +		groups = {
          128  +			'pick';'pickaxe';'axe';'shovel';'sickle';
          129  +		};
          130  +		desc = 'rip through solid stone or wood like a hot knife through butter';
          131  +		recipe = {
          132  +			{lens = 'amplifier', gem = 'diamond',  dmg = 4};
          133  +			{lens = 'amplifier', gem = 'ruby',     dmg = 4};
          134  +			{lens = 'rectifier', gem = 'diamond',  dmg = 2};
          135  +		};
    39    136   		affinity = 'praxic';
    40         -		apply = function(stack,power)
          137  +		apply = function(stack,power,base)
          138  +			local caps = table.copy(stack:get_definition().tool_capabilities)
          139  +			for g,v in pairs(caps.groupcaps) do
          140  +				for i,t in pairs(v.times) do
          141  +					local unit = base.groupcaps[g].times[i] * 0.15
          142  +					caps.groupcaps[g].times[i] = math.max(0.01, t - unit*power)
          143  +				end
          144  +			end
          145  +			stack:get_meta():set_tool_capabilities(caps)
          146  +			return stack
    41    147   		end;
    42    148   	};
    43    149   	rend = { -- more damage / mine higher level blocks
    44    150   		name = 'Rend';
    45    151   		affinity = 'praxic';
    46    152   		tone = {251,203,113};
          153  +		groups = {'sword';'pick';'pickaxe';};
          154  +		recipe = {
          155  +			{lens = 'convex',    gem = 'mese',    dmg = 3};
          156  +			{lens = 'amplifier', gem = 'emerald', dmg = 7};
          157  +			{lens = 'amplifier', gem = 'diamond', dmg = 7};
          158  +		};
          159  +		cost = 5;
    47    160   		desc = 'cleave through sturdy ores and tear mortal flesh with fearsome ease';
    48         -		apply = function(stack,power)
          161  +		apply = function(stack,power,base)
          162  +			local caps = table.copy(stack:get_definition().tool_capabilities)
          163  +			for g,v in pairs(caps.groupcaps) do
          164  +				local unit = 2
          165  +				caps.groupcaps[g].maxlevel = caps[g].maxlevel + math.floor(unit*power)
          166  +			end
          167  +			stack:get_meta():set_tool_capabilities(caps)
          168  +			return stack
    49    169   		end;
    50    170   	};
    51    171   }

Modified data/gems.lua from [608081f8a9] to [6638dda90c].

    18     18   
    19     19   return {
    20     20   	diamond = {
    21     21   		foreign = 'default:diamond';
    22     22   		tone = {137,240,255};
    23     23   		items = default_items('diamond');
    24     24   		tools = true, armor = true;
           25  +		maxenergy = 2000;
    25     26   		slots = {
    26     27   			{affinity = {'praxic','counterpraxic'}, confluence = 1};
    27     28   			{affinity = {'praxic','syncretic'}, confluence = 0.6};
    28     29   			{affinity = {'counterpraxic', 'entropic'}, confluence = 0.7};
    29     30   		};
    30     31   	};
    31     32   	mese = {
    32     33   		foreign = 'default:mese_crystal';
    33     34   		foreign_shard = 'default:mese_crystal_fragment';
    34     35   		tone = {255,253,94};
           36  +		energysource = 5;
           37  +		maxenergy = 600;
    35     38   		items = default_items('mese');
    36     39   		tools = true, armor = true;
    37     40   		slots = {
    38     41   			{affinity = {'praxic'}, confluence = 1};
    39     42   			{affinity = {'praxic'}, confluence = 0.5};
    40     43   			{affinity = {'cognic'}, confluence = 1};
    41     44   			{affinity = {'syncretic'}, confluence = 0.9};

Modified data/gods.lua from [e99e28f75f] to [ae9e7f6603].

    26     26   				"flowerpot:flowers_tulip";
    27     27   				"flowerpot:flowers_viola";
    28     28   				"flowerpot:flowers_geranium";
    29     29   				"flowerpot:flowers_chrysanthemum_green";
    30     30   				"flowerpot:flowers_dandelion_white";
    31     31   				"flowerpot:flowers_dandelion_yellow";
    32     32   			}};
    33         -			["sorcery:dagger"] = {4, "sorcery:dagger_consecrated"};
           33  +			["sorcery:dagger"] = {8, "sorcery:dagger_consecrated"};
    34     34   			["sorcery:oil_mystic"] = {6, "sorcery:oil_purifying"};
    35     35   			["sorcery:potion_water"] = {2, "sorcery:holy_water"};
    36     36   			-- ["default:steel_ingot"] = {15, "sorcery:holy_token_harvest"};
    37     37   		};
    38     38   		sacrifice = {
    39     39   			-- beattitudes
    40     40   			["farming:straw"           ] = 1;
................................................................................
    88     88   			["default:dirt"] = -5;
    89     89   			["moreblocks:tar"] = -9;
    90     90   			["default:bucket_lava"] = -12;
    91     91   			["sorcery:blood"] = -15;
    92     92   			["default:bones"] = -35;
    93     93   		};
    94     94   		gifts = {
    95         -			-- gift specs = {favor, chance} where favor is the likelihood of a god bestowing the gift
    96         -			["default:blueberry_bush_sapling"] = {35,15};
    97         -			["farming:coffee_beans"] = {31, 9};
    98         -			["farming:rhubarb"] = {29, 8};
    99         -			["farming:corn"] = {27, 5};
   100         -			["farming:grapes"] = {26, 6};
   101         -			["farming:seed_hemp"] = {26, 8};
   102         -			["farming:raspberries"] = {25, 5};
   103         -			["flowers:mushroom_red"] = {25, 2};
   104         -			["farming:garlic_clove"] = {25, 7};
   105         -			["farming:seed_mint"] = {25, 7};
   106         -			["farming:seed_barley"] = {24, 6};
   107         -			["farming:beans"] = {24, 6};
           95  +			-- gift specs = {favor, chance} where chance is the likelihood of a god bestowing the gift
           96  +			["default:blueberry_bush_sapling"] = {120,15};
           97  +			["default:blueberries"] = {60, 4};
           98  +			["farming:coffee_beans"] = {58, 9};
           99  +			["farming:seed_hemp"] = {55, 8};
          100  +			["farming:garlic_clove"] = {50, 7};
          101  +			["farming:seed_mint"] = {50, 7};
          102  +			["flowers:mushroom_red"] = {45, 2};
          103  +			["farming:grapes"] = {43, 6};
          104  +			["farming:seed_barley"] = {40, 6};
          105  +			["farming:rhubarb"] = {38, 8};
          106  +			["farming:beans"] = {35, 6};
          107  +			["farming:raspberries"] = {30, 5};
          108  +			["farming:corn"] = {27, 2};
          109  +			["farming:sugar"] = {24, 4};
          110  +			["farming:salt"]  = {24, 3};
   108    111   			["farming:onion"] = {20, 7};
   109         -			["farming:carrot"] = {19, 7};
   110         -			["farming:sugar"] = {17, 3};
   111         -			["farming:salt"]  = {17, 3};
   112         -			["default:apple"] = {16, 2};
   113         -			["default:blueberries"] = {15, 4};
          112  +			["farming:carrot"] = {20, 7};
          113  +			["default:apple"] = {18, 2};
   114    114   			["farming:wheat"] = {14, 2};
   115    115   		};
   116    116   	};
   117    117   }

Modified data/metals.lua from [b325d7e9c3] to [f4099f1a65].

    42     42   	};
    43     43   	bronze = {
    44     44   		ingot = 'default:bronze_ingot';
    45     45   		block = 'default:bronzeblock';
    46     46   		artificial = true;
    47     47   		tone = {229,115,52};
    48     48   		items = default_items('bronze');
           49  +		maxenergy = 150;
    49     50   		slots = {
    50         -			{affinity = {'counterpraxic'}; confluence = 0.7;};
           51  +			{affinity = {'counterpraxic'}; confluence = 0.7};
    51     52   		};
    52     53   		mix = {
    53     54   			metals = {
    54     55   				copper = 4;
    55     56   				tin = 1;
    56     57   			};
    57     58   		};
    58     59   	};
    59     60   	steel = {
    60     61   		ingot = 'default:steel_ingot';
    61     62   		block = 'default:steelblock';
    62     63   		tone = {240,240,240};
    63     64   		items = default_items('steel');
           65  +		maxenergy = 200;
    64     66   		slots = {
    65         -			{affinity = {'praxic'}; confluence = 0.5;};
           67  +			{affinity = {'praxic'}; confluence = 0.5};
    66     68   		};
    67     69   	};
    68     70   	aluminum = {
    69     71   		tone = {196,64,32}, alpha = 128;
    70     72   		meltpoint = 1;
    71     73   		rarity = 12; depth = 158;
    72     74   		power = 3; speed = 2.4;
    73     75   		durability = 700; cooktime = 25;
    74     76   		armor_weight = 0.3;
           77  +		maxenergy = 400;
    75     78   		slots = {
    76         -			{affinity = {'syncretic'}; confluence = 0.7;};
    77         -			{affinity = {'praxic'}; confluence = 0.4;};
           79  +			{affinity = {'syncretic'}; confluence = 0.7};
           80  +			{affinity = {'praxic'}; confluence = 0.4};
    78     81   		};
    79     82   	};
    80     83   	levitanium = {
    81     84   		tone = {17,255,191}, alpha = 40;
    82     85   		meltpoint = 4;
    83     86   		rarity = 17; depth = 870;
    84     87   		power = 1; durability = 50; cooktime = 70;
    85     88   		armor_weight = -2.2; armor_protection = 1;
           89  +		maxenergy = 5000;
    86     90   		no_tools = true;
    87     91   	};
    88     92   	platinum = {
    89     93   		tone = {255,233,118}, alpha = 50;
    90     94   		meltpoint = 1;
    91     95   		rarity = 15; depth = 580;
    92     96   		power = 4; speed = 3;
    93     97   		durability = 1400; cooktime = 40;
    94     98   		armor_weight = 0.7;
           99  +		maxenergy = 1000;
    95    100   		slots = {
    96    101   			{affinity = {'praxic','counterpraxic'}; confluence = 0.3};
    97    102   			{affinity = {'counterpraxic'}; confluence = 0.8};
    98    103   		}
    99    104   	};
   100    105   	gold = {
   101    106   		ingot = 'default:gold_ingot';
   102    107   		block = 'default:goldblock';
   103    108   		tone = {255,225,47};
          109  +		maxenergy = 3000;
   104    110   		slots = {
   105    111   			{affinity = {'praxic','counterpraxic'}; confluence = 1.4};
   106    112   			{affinity = {'praxic','counterpraxic'}; confluence = 1.2};
   107    113   		}
   108    114   	};
   109    115   	silver = {
   110    116   		tone = {218,255,246};
   111    117   
          118  +		maxenergy = 2000;
   112    119   		depth = 380; rarity = 13.5;
   113    120   		no_armor = true; no_tools = true;
   114    121   		power = 1; cooktime = 8; hardness = 1;
   115    122   	};
   116    123   	electrum = {
   117    124   		tone = {212, 255, 0}, alpha = 80;
   118    125   		artificial = true;
................................................................................
   123    130   			};
   124    131   		};
   125    132   		no_tools = true;
   126    133   	};
   127    134   	tungsten = {
   128    135   		tone = {0,255,163}, alpha = 70;
   129    136   		rarity = 14;
   130         -		speed = 2.5;
          137  +		speed = 2.8;
   131    138   		power = 4;
   132    139   		meltpoint = 4;
   133    140   		cooktime = 100;
   134         -		durability = 2500;
          141  +		durability = 2700;
          142  +		maxenergy = 1500;
   135    143   		slots = {
   136    144   			{affinity = {'counterpraxic'}, confluence = 0.6};
   137         -			{affinity = {'praxic','counterpraxic'}, confluence = 0.8};
          145  +			{affinity = {'praxic','counterpraxic'}, confluence = 1};
   138    146   			{affinity = {'praxic'}, confluence = 0.5};
   139    147   		};
   140    148   	};
   141    149   	cobalt = {
   142    150   		tone = {48,101,255}, alpha = 90;
   143         -		rarity = 15;
   144         -		durabilty = 650;
          151  +		rarity = 17;
          152  +		durabilty = 900;
   145    153   		power = 3;
   146    154   		speed = 3.5;
   147    155   		cooktime = 30;
          156  +		maxenergy = 3500;
          157  +		slots = {
          158  +			{
          159  +				affinity = {'counterpraxic'};
          160  +				confluence = 0.65;
          161  +				interference = {speed = 1};
          162  +			};
          163  +		}
   148    164   	};
   149    165   	lithium = {
   150    166   		tone = {255,252,93}, alpha = 80;
   151         -		rarity = 12.5;
          167  +		rarity = 13;
   152    168   		no_tools = true;
   153    169   		no_armor = true;
   154    170   	};
   155    171   	iridium = {
   156    172   		tone = {209,88,241}, alpha = 80;
   157         -		rarity = 17;
          173  +		rarity = 18;
   158    174   		meltpoint = 3;
   159    175   		cooktime = 340;
   160         -		durability = 1700;
          176  +		maxenergy = 1800;
          177  +		durability = 1900;
   161    178   		speed = 3.2;
   162    179   		img = {
   163    180   			-- ingot = 'sorcery_iridium_ingot.png';
   164    181   			-- block = 'sorcery_iridium_block.png';
   165    182   		};
   166    183   		slots = {
   167    184   			{affinity={'counterpraxic','syncretic'}, confluence = 1.1};
   168    185   			{affinity={'cognic','entropic'}, confluence = 0.8};
   169    186   		};
   170    187   	};
   171         -	duranium = {
          188  +	duridium = {
   172    189   		tone = {255,64,175}, alpha = 70;
   173    190   		cooktime = 120;
   174    191   		artificial = true;
   175    192   		durability = 3400;
   176    193   		speed = 3.1;
   177    194   		power = 5;
   178    195   		mix = {
................................................................................
   181    198   				aluminum = 4;
   182    199   				tin = 1;
   183    200   			};
   184    201   		};
   185    202   		img = {
   186    203   			-- ingot = 'sorcery_duranium_ingot.png';
   187    204   		};
          205  +		maxenergy = 1300;
   188    206   		slots = {
   189    207   			{affinity={'counterpraxic'}, confluence = 0.6};
   190    208   			{affinity={'counterpraxic'}, confluence = 0.4};
   191    209   		};
   192    210   	};
   193    211   	impervium = {
   194    212   		tone = {226,255,107}, alpha = 90;
   195    213   		cooktime = 260;
   196    214   		meltpoint = 5;
   197    215   		artificial = true;
   198    216   		speed = 2.1;
   199    217   		durability = 5300;
          218  +		maxenergy = 2300;
   200    219   		watercool = true;
   201    220   		mix = {
   202    221   			metals = {
   203    222   				duranium = 4;
   204    223   				iridium = 2;
   205    224   				levitanium = 1;
   206    225   			};
   207    226   		};
   208    227   		slots = {
   209         -			{affinity={'praxic'}, confluence = 1.2};
          228  +			{affinity={'praxic'}, confluence = 1.2, interference={durability=2}};
   210    229   			{affinity={'praxic','syncretic'}, confluence = 0.8};
   211    230   			{affinity={'cognic'}, confluence = 0.9};
   212    231   		};
   213    232   	};
   214    233   	eternium = {
   215    234   		tone = {156,82,222}, alpha = 100;
   216    235   		cooktime = 500;
   217    236   		meltpoint = 6;
   218    237   		artificial = true;
   219    238   		speed = 2;
          239  +		maxenergy = 1200;
   220    240   		durability = 8100;
   221    241   		watercool = true;
   222    242   		mix = {
   223    243   			metals = {
   224    244   				iridium = 2;
   225    245   				tungsten = 2;
   226    246   				lithium = 1;
................................................................................
   232    252   		}
   233    253   	};
   234    254   	unobtanium = {
   235    255   		tone = {114,255,214}, alpha = 120;
   236    256   		meltpoint = 3;
   237    257   		cooktime = 330;
   238    258   		artificial = true;
          259  +		maxenergy = 4000;
   239    260   		durability = 3300;
   240    261   		speed = 3.4;
   241    262   		slots = {
   242    263   			{affinity={'praxic'}, confluence = 0.7};
   243    264   			{affinity={'counterpraxic'}, confluence = 1.2};
   244    265   			{affinity={'cognic'}, confluence = 1.1};
   245    266   		};

Modified data/oils.lua from [268d4500ec] to [f8f2204848].

     5      5   		-- recipes are described by two fields, the 'core', which consists of items which are included in the output, and 'mix', items with are incidental to the output. items listed in mix can leave leftovers in the crafting grid; core items will never leave leftovers (as it is assumed that they are being reused). the mortar and pestle is always implied. if core is empty, a bowl is automatically added to the recipe.
     6      6   		core = {};
     7      7   		mix = {
     8      8   			'farming:sugar';
     9      9   			'group:food_blueberries';
    10     10   			'farming:raspberries';
    11     11   			'sorcery:extract_wheat';
    12         -			'sorcery:extract_wheat';
           12  +			'xdecor:honey';
    13     13   		};
    14     14   	};
    15     15   	berry = {
    16     16   		color = {151,67,183};
    17     17   		core = {};
    18     18   		mix = {
    19     19   			'group:food_raspberries';

Modified data/philters.lua from [db8db88125] to [6fff6fb29a].

     1      1   return {
     2      2   	dark = {
     3      3   		color = {86,16,42};
     4      4   		infusion = "default:obsidian_shard";
     5      5   	};
     6      6   	shimmering = {
     7         -		color = {112,255,170};
            7  +		color = {224,255,155};
     8      8   		infusion = "default:mese_crystal_fragment";
     9      9   	};
    10     10   	silent = {
    11     11   		color = {249,193,42};
    12     12   		infusion = "sorcery:extract_cotton";
    13     13   	};
    14     14   	verdant = {
    15         -		infusion = "default:apple_sapling";
           15  +		infusion = "default:sapling";
    16     16   		color = {78,222,113};
    17     17   	};
    18     18   	blazing = {
    19     19   		infusion = "sorcery:oil_flame";
    20     20   		color = {229,32,53};
    21     21   	};
    22     22   }

Modified data/potions.lua from [a62297059e] to [c61093f3aa].

    15     15   		color = {186,241,233};
    16     16   		infusion = 'sorcery:grease_fog';
    17     17   	};
    18     18   	Luminous = {
    19     19   		color = {255,237,160};
    20     20   		style = 'dull';
    21     21   		glow = 12;
    22         -		infusion = 'xdecor:honey';
           22  +		infusion = 'sorcery:gem_luxite';
    23     23   	};
    24     24   	Soft = {
    25     25   		color = {174,210,85};
    26     26   		style = 'sparkle';
    27     27   		infusion = 'sorcery:extract_cotton';
    28     28   	};
    29     29   	Viscous = {
    30     30   		color = {119,51,111};
    31     31   		infusion = 'sorcery:oil_bleak';
    32     32   	};
    33     33   }

Modified data/register.lua from [b568fdc66a] to [d2f1e722e5].

     1      1   return {
     2      2   	infusion = function(r)
     3      3   		sorcery.data.infusions[#sorcery.data.infusions + 1] = r
     4      4   	end;
     5      5   	alloy = function(mix)
     6      6   		sorcery.data.alloys[#sorcery.data.alloys + 1] = mix
     7      7   	end;
            8  +	kiln_recipe = function(mix)
            9  +		sorcery.data.kilnrecs[#sorcery.data.kilnrecs + 1] = mix
           10  +	end;
     8     11   	infusion_leftover = function(orig, leave)
     9     12   		sorcery.data.infusion_leftovers[orig] = leave
    10     13   	end;
    11     14   }

Modified data/spells.lua from [196ad8b009] to [3dd4d17be3].

    41     41   			-- the item material and look up the slots offered by this
    42     42   			-- material. we iterate through the slot definitions to pick 
    43     43   			-- out a candidate slot, criteria being that the slot
    44     44   			-- possesses the proper affinity, and that the corresponding
    45     45   			-- slot on the item enchantment record is empty, then pick
    46     46   			-- the final slot at random from this table if #candidates>0
    47     47   			-- (otherwise, we fail unceremoniously).
    48         -			--
           48  +
           49  +			local current_enchant = sorcery.enchant.get(subj)
           50  +			local material, eligible_spells = sorcery.enchant.getsubj(subj)
           51  +			if     eligible_spells == nil
           52  +			   or #eligible_spells == 0
           53  +			   or  material == nil
           54  +			   or  material.data.slots == nil
           55  +			   or #material.data.slots == 0
           56  +				   then return false end
           57  +
           58  +			-- determine the properties the enchantment will have
           59  +			local power = 10
           60  +			local energy = material.data.maxenergy
           61  +			local reliability = 100
           62  +			if ctx.base.gem == 'sapphire' then power = power + 5
           63  +			elseif ctx.base.gem == 'amethyst' then
           64  +				energy = energy * (math.random() * 0.7)
           65  +			elseif ctx.base.gem == 'diamond' then
           66  +				if math.random(5) == 1 then
           67  +					power = power * 2
           68  +					reliability = reliability - (reliability / 4)
           69  +				else
           70  +					power = power + 5
           71  +					reliability = reliability - 10
           72  +				end
           73  +			end
           74  +
           75  +			local viable_slots = {}
           76  +			for i,slot in pairs(material.data.slots) do
           77  +				if sorcery.lib.tbl.has(slot.affinity,aff) then
           78  +					for _,spell in pairs(current_enchant.spells) do
           79  +						if spell.slot == i then goto nonviable end
           80  +					end
           81  +					viable_slots[#viable_slots + 1] = i
           82  +				end
           83  +			::nonviable::end
           84  +			if #viable_slots == 0 then return false end
           85  +
           86  +			local target_slot = viable_slots[math.random(#viable_slots)]
    49     87   			-- once we have selected an appropriate slot, we iterate
    50     88   			-- through the list of known enchantments to check the
    51     89   			-- affinity of each against `aff`. if the affinity matches
    52     90   			-- the wand, we then check whether the 'recipe' matches.
    53     91   			-- if so, we've found the enchantment -- apply it to the
    54     92   			-- object and update its enchantment data. otherwise, we
    55     93   			-- move on to the next. if no matching recipe is found,
    56         -			-- we give up and bail, returning 'nil' to signify that
           94  +			-- we give up and bail, returning 'false' to signify that
    57     95   			-- a spell was not cast.
    58     96   
    59         -			enchantment_sparkle(ctx,affcolor)
           97  +			local focus_match = function(spec,stack)
           98  +				local default_mode
           99  +				if spec.lens then
          100  +					default_mode = 'dmg'
          101  +					if minetest.get_item_group(stack:get_name(), 'sorcery_enchanting_lens') == 0
          102  +						then return false end
          103  +					local proto = stack:get_definition()._proto
          104  +					if proto.kind ~= spec.lens or proto.gem ~= spec.gem
          105  +						then return false end
          106  +				elseif spec.item then
          107  +					default_mode = 'consume'
          108  +					if stack:get_name() ~= spec.item then
          109  +						return false end
          110  +				else
          111  +					return false
          112  +				end
          113  +
          114  +				local mode
          115  +				if spec.dmg then mode = 'dmg'
          116  +				elseif spec.consume then mode = 'consume'
          117  +				else mode = default_mode end
          118  +
          119  +				if mode == 'dmg' then
          120  +					stack:add_wear((spec.dmg or 1) * 1000)
          121  +					return stack
          122  +				elseif mode == 'consume' then
          123  +					stack:take_item(spec.consume or 1)
          124  +					return stack
          125  +				end
          126  +			end
          127  +			for ench,data in pairs(sorcery.data.enchants) do
          128  +				if data.affinity ~= aff or data.recipe == nil then goto skip end
          129  +				local newinv = {}
          130  +				for s,v in pairs(data.recipe) do
          131  +					newinv[s] = focus_match(v,inv:get_stack('foci',s))
          132  +					if newinv[s] == false then goto skip end
          133  +				end
          134  +				-- is the tool compatible with the chosen spell? we check its groups to
          135  +				-- see if any of them grant it eligibility; if not, we skip to the next
          136  +				-- spell. NOTE: this means the same recipe could mean different things
          137  +				-- for different kinds of tools, if one were so inclined.
          138  +				for g,v in pairs(subj:get_definition().groups) do
          139  +					if v ~= 0 and sorcery.lib.tbl.has(data.groups, g)
          140  +						then goto enchant end
          141  +				end
          142  +				goto skip
          143  +				-- spell matches!
          144  +				::enchant:: current_enchant.spells[#current_enchant.spells + 1] = {
          145  +					id = ench;
          146  +					slot = target_slot;
          147  +					boost = power;
          148  +					reliability = reliability;
          149  +				}
          150  +				current_enchant.energy = math.max(current_enchant.energy, energy)
          151  +
          152  +				sorcery.enchant.set(subj, current_enchant)
          153  +				inv:set_stack('item',1,subj)
          154  +				for i,v in pairs(newinv) do inv:set_stack('foci',i,v) end
          155  +				sorcery.enchant.update_enchanter(ctx.target.under)
          156  +				enchantment_sparkle(ctx,affcolor)
          157  +				do return nil end
          158  +			::skip::end
          159  +			return false
    60    160   		end
    61    161   	};
    62    162   end
    63    163   -- note: this was written before terminology was standardized,
    64    164   -- and "leytype" corresponds to what is otherwise known as an
    65    165   -- "affinity"; "affinity" after this comment is widely misused
    66    166   return {
................................................................................
    93    193   		leytype = 'imperic';
    94    194   		affinity = {'pine','dark'};
    95    195   		cast = function(ctx)
    96    196   			if ctx.target == nil or ctx.target.type ~= 'node' then return false end
    97    197   			local meta = minetest.get_meta(ctx.target.under)
    98    198   			-- first we need to check if the wand has an identifying 'key' yet,
    99    199   			-- and set one if not.
   100         -			local wandmode = ctx.base.gem == 'amethyst'
          200  +			local wandmode = ctx.base.gem == 'sapphire'
   101    201   			local keycode
   102    202   			if ctx.meta:contains('sorcery_wand_key') then
   103    203   				keycode = ctx.meta:get_string('sorcery_wand_key')
   104    204   			else
   105    205   				keycode = sorcery.lib.str.rand(32)
   106    206   				ctx.meta:set_string('sorcery_wand_key', keycode)
          207  +				-- ctx.meta:mark_as_private('sorcery_wand_key')
   107    208   			end
   108    209   			if meta:contains('owner') then
   109    210   				-- owner is already set -- can we break the enchantment?
   110    211   				if meta:get_string('sorcery_wand_key') == keycode then
   111    212   					meta:set_string('owner','')
   112    213   					meta:set_string('sorcery_wand_key','')
   113    214   					meta:set_string('sorcery_seal_mode','')
   114    215   					enchantment_sparkle(ctx,sorcery.lib.color(101,255,142))
   115    216   				else return false end
   116    217   			else
   117    218   				meta:set_string('sorcery_wand_key',keycode)
          219  +				meta:mark_as_private('sorcery_wand_key')
   118    220   				meta:set_string('owner',ctx.caster:get_player_name())
   119    221   				if wandmode then
   120    222   					meta:set_string('sorcery_seal_mode','wand')
   121    223   				end
   122    224   				enchantment_sparkle(ctx,sorcery.lib.color(255,201,27))
   123    225   			end
   124    226   		end;
   125    227   	};
   126    228   	leyspark = {
   127    229   		name = 'Leyspark';
   128    230   		leytype = 'cognic';
          231  +		color = {255,246,142}; -- bright yellow
   129    232   		affinity = {'apple','silent'};
   130         -		uses = 64;
          233  +		uses = 128;
   131    234   		desc = 'Reveal the strength and affinities of the local leyline';
   132    235   		cast = function(ctx)
   133    236   			local color = ctx.base.gem == 'sapphire';
          237  +			local duration = (ctx.base.gem == 'amethyst' and 4) or 2;
   134    238   			local ley = sorcery.ley.estimate(ctx.caster:get_pos())
   135    239   			local emit = function(color,strength)
   136    240   				minetest.add_particlespawner {
   137    241   					amount = 70 * strength;
   138         -					time = 2 * strength;
          242  +					time = duration * strength;
   139    243   					attached = ctx.caster;
   140    244   					texture = sorcery.lib.image('sorcery_spark.png'):
   141         -						multiply(color):render();
   142         -					minpos = { x =  0.5, z =  0.0, y =  1.5}; 
   143         -					maxpos = { x =  0.5, z =  0.0, y =  1.5}; 
          245  +						multiply(color:brighten(1.3)):render();
          246  +					minpos = { x = -0.1, z =  0.5, y =  1.2}; 
          247  +					maxpos = { x =  0.1, z =  0.3, y =  1.6}; 
   144    248   					minvel = { x = -0.5, z = -0.5, y = -0.5};
   145    249   					maxvel = { x =  0.5, z =  0.5, y =  0.5};
   146    250   					minacc = { x =  0.0, z =  0.0, y =  0.5};
   147         -					minacc = { x =  0.0, z =  0.0, y =  0.5};
          251  +					maxacc = { x =  0.0, z =  0.0, y =  0.5};
   148    252   					minsize = 0.4, maxsize = 0.8;
   149    253   					minexptime = 1, maxexptime = 1;
   150    254   					glow = 14;
   151    255   					animation = {
   152    256   						type = 'vertical_frames';
   153    257   						aspect_w = 16;
   154    258   						aspect_h = 16;
................................................................................
   168    272   			end
   169    273   
   170    274   		end;
   171    275   	};
   172    276   	dowse = {
   173    277   		name = 'dowsing';
   174    278   		leytype = 'cognic';
          279  +		color = {65,116,255};
   175    280   		affinity = {'acacia','dark','silent'};
   176         -		uses = 128;
          281  +		uses = 176;
   177    282   		desc = 'Send up sparks of radia to indicate nearness or absence of attuned blocks';
   178    283   	};
   179    284   	verdant = {
   180    285   		name = 'verdant';
   181    286   		color = {16,29,255};
   182    287   		uses = 48;
   183    288   		leytype = 'imperic';
................................................................................
   188    293   	counterpraxic = anchorwand('counterpraxic',23, {'pine','shimmering','silent'});
   189    294   	entropic      = anchorwand('entropic',      8, {'jungle','dark'});
   190    295   	syncretic     = anchorwand('syncretic',    12, {'aspen','verdant','shimmering','blazing'});
   191    296   	cognic        = anchorwand('cognic',       36, {'acacia','verdant','dark'});
   192    297   	shape = {
   193    298   		name = 'shaping';
   194    299   		uses = 24;
          300  +		color = {255,114,65};
          301  +		leytype = 'praxic';
   195    302   		affinity = {'apple','blazing'};
   196    303   		desc = 'With an enchanter, physically alter the mundane qualities of an object';
   197    304   	};
   198    305   	attune = {
   199    306   		name = 'attunement';
   200    307   		uses = 38;
          308  +		color = {255,65,207};
   201    309   		leytype = 'syncretic';
   202    310   		affinity = {'pine','verdant','dark'};
   203    311   		desc = 'Establish a connection between mystic mechanisms, like connecting two sides of a portal or impressing targets onto a dowsing wand in an enchanter';
   204    312   	};
   205    313   	meld = {
   206    314   		name = 'melding';
   207    315   		uses = 48;
   208    316   		leytype = 'syncretic';
          317  +		color = {172,65,255};
   209    318   		affinity = {'apple','verdant'};
   210    319   		desc = 'Meld the properties of three balanced items on an enchanter to create a new one with special properties, but destroying the old ones and losing two thirds of the mass in the process. The precise outcome is not always predictable.';
   211    320   	};
   212    321   	divide = {
   213    322   		name = 'division';
   214    323   		uses = 19;
   215    324   		leytype = 'syncretic';
          325  +		color = {255,65,121};
   216    326   		affinity = {'apple','shimmering'};
   217    327   		desc = 'Shatter an item on an enchanter, dividing its essence equally into three parts and precipitating it into new items embodying various properties of the destroyed item. The outcome is not always predictable.';
   218    328   	};
   219    329   	obliterate = {
   220    330   		name = 'obliteration';
   221    331   		uses = 129;
          332  +		color = {175,6,212};
   222    333   		affinity = {'aspen','dark'};
   223    334   		leytype = 'occlutic';
   224    335   		desc = 'Totally and irreversibly obliterate all items on an enchanter.';
   225    336   	};
   226    337   	sacrifice = {
   227    338   		name = 'sacrifice';
   228    339   		uses = 58;
          340  +		color = {212,6,63};
   229    341   		affinity = {'aspen','blazing'};
   230    342   		leytype = 'syncretic';
   231    343   		desc = 'Transform the matter of one to three items on an enchanter into energy and empower the item on the center of the enchanter with it. Useful to recharge wands in areas with weak leylines.';
   232    344   	};
   233    345   	transfer = {
   234    346   		name = 'transfer';
   235    347   		uses = 65;
          348  +		color = {6,212,121};
   236    349   		leytype = 'syncretic';
   237    350   		affinity = {'aspen','shimmering','silent'};
   238    351   		desc = 'Transfer ley-current from items on an enchanter into the item in the center, but at a 50% loss if they are of mismatched affinities. One third of maximum current is transferred, and when used on items with little power may destroy them or their enchantments';
   239    352   	};
          353  +	transmute = {
          354  +		name = 'transmutation';
          355  +		uses = 7;
          356  +		color = {255,90,18};
          357  +		leytype = 'imperic';
          358  +		affinity = {'aspen','shimmering','dark','blazing'};
          359  +		desc = 'Transmute three ingots into one of a different metal, determined by chance and influenced by configuration of the wand';
          360  +	};
   240    361   	disjoin = {
   241    362   		name = 'disjunction';
   242    363   		uses = 32;
          364  +		color = {17,6,212};
   243    365   		leytype = 'occlutic';
   244    366   		affinity = {'jungle','silent'};
   245    367   		desc = 'With an enchanter, disjoin the anchor holding a spell into an object so a new spell can instead be bound in';
          368  +	};
          369  +	luminate = {
          370  +		name = 'lumination';
          371  +		desc = 'Banish darkness all about you for a few moments';
          372  +		uses = 40;
          373  +		color = {244,255,157};
          374  +		affinity = {'acacia','shimmering','blazing'};
          375  +		leytype = 'cognic';
          376  +		cast = function(ctx)
          377  +			local center = ctx.heading.pos
          378  +			local maxpower = 20
          379  +			local power = (ctx.base.gem == 'sapphire' and maxpower) or maxpower/2
          380  +			local range = (ctx.base.gem == 'emerald' and 10) or 5
          381  +			local duration = (ctx.base.gem == 'amethyst' and 60) or 30
          382  +			if ctx.base.gem == 'diamond' then
          383  +				power = power * (math.random()*2)
          384  +				range = range * (math.random()*2)
          385  +				duration = duration * (math.random()*2)
          386  +			end
          387  +			local lum = math.ceil((power/maxpower) * minetest.LIGHT_MAX)
          388  +			print('setting lum',lum)
          389  +			for i=1,power do
          390  +				local pos = vector.add(center, {
          391  +					x = math.random(-range,range);
          392  +					z = math.random(-range,range);
          393  +					y = math.random(0,range/2);
          394  +				})
          395  +				local delta = vector.subtract(pos,center)
          396  +				local near = 1 - (delta.x^2 + delta.y^2 + delta.z^2)/(range^2)
          397  +				if minetest.get_node(pos).name == 'air' then
          398  +					minetest.set_node(pos,{name='sorcery:air_glimmer_' .. tostring(lum)})
          399  +					do local lm = minetest.get_meta(pos)
          400  +						lm:set_float('duration',duration)
          401  +						lm:set_float('timeleft',duration)
          402  +						lm:set_float('power',lum * near)
          403  +					end
          404  +				end
          405  +			end
          406  +		end;
   246    407   	};
   247    408   }

Modified enchanter.lua from [f08588c5d9] to [2062a1f6be].

     2      2   	type = 'fixed';
     3      3   	fixed = {
     4      4   		-0.5, -0.5, -0.5;
     5      5   		0.5, 0.1, 0.5;
     6      6   	};
     7      7   }
     8      8   
     9         -local enchantable_tools = {
    10         -	pickaxe = {}, pick = {};
    11         -	axe = {};
    12         -	sword = {};
    13         -	sickle = {};
    14         -	shovel = {};
    15         -	hoe = {};
    16         -};
    17         -
    18         -sorcery.enchant = {} do
    19         -	local m = sorcery.lib.marshal
    20         -	local ench_t = m.g.struct {
    21         -		id = m.t.str;
    22         -		slot = m.t.u8;
    23         -		boost = m.t.u8; -- every enchantment has an intrinsic force
    24         -		-- separate from the confluence of the slot, which is
    25         -		-- determined by the composition of the wand used to generate
    26         -		-- it (for instance, a gold-wired wand at low wear, or a wand
    27         -		-- with specific gemstones, may have a boost level above 0)
    28         -	}
    29         -	local pack, unpack = m.transcoder {
    30         -		spells = m.g.array(8, ench_t);
    31         -		energy = m.t.u16;
    32         -	}
    33         -	local key = 'sorcery_enchantment_recs'
    34         -	sorcery.enchant.set = function(stack, data)
    35         -		local meta = stack:get_meta()
    36         -		meta:set_string(key, pack(data))
    37         -	end
    38         -	sorcery.enchant.get = function(stack)
    39         -		local meta = stack:get_meta()
    40         -		if meta:contains(key) then
    41         -			local data = meta:get_string(key)
    42         -			return unpack(data)
    43         -		else
    44         -			return {
    45         -				spells = {};
    46         -				energy = 0;
    47         -			}
    48         -		end
    49         -	end
    50         -	sorcery.enchant.strength = function(stack,id)
    51         -		-- this functions should be used whenever you need to
    52         -		-- determine the power of a particular enchantment on
    53         -		-- an enchanted item.
    54         -		local e = sorcery.enchant.get(stack)
    55         -		local p = 0.0
    56         -		local slots = sorcery.matreg.lookup[stack:get_name()].data.slots
    57         -		-- TODO handle strength-boosting spells!
    58         -		for _,s in pairs(e.spells) do
    59         -			if s.id == id then p = p + slots[s.slot] end
    60         -		end
    61         -		return p
    62         -	end
    63         -	sorcery.enchant.stackup = function(stack)
    64         -		-- stack update function. this should be called whenever
    65         -		-- the enchantment status of a stack changes; it will
    66         -		-- alter/reset tool capabilities and tooltip as necessary
    67         -		local e = sorcery.enchant.get(stack)
    68         -		local meta = stack:get_meta()
    69         -		local def = stack:get_definition()
    70         -		meta:set_string('tool_capabilities','')
    71         -		local done = {}
    72         -		local props = {}
    73         -		for _,s in pairs(e.spells) do
    74         -			if done[s.id] then goto skip end
    75         -			done[s.id] = true
    76         -			local pwr = sorcery.enchant.strength(stack,s.id)
    77         -				-- somewhat wasteful…
    78         -			local name, color, desc = sorcery.data.enchants[s.id].apply(stack,pwr)
    79         -			props[#props+1] = {
    80         -				title = name;
    81         -				desc = desc;
    82         -				color = sorcery.lib.color(desc);
    83         -			}
    84         -		::skip::end
    85         -		if #e.spells > 0 then
    86         -			meta:set_string('description', sorcery.lib.ui.tooltip {
    87         -				title = 'Enchanted ' .. def.description;
    88         -				props = props;
    89         -			})
    90         -		else
    91         -			meta:set_string('description','')
    92         -		end
    93         -	end
    94         -end
    95         -
    96         -local enchanter_getsubj = function(item)
    97         -	if not item:is_empty() then
    98         -		for group, spells in pairs(enchantable_tools) do
    99         -			if minetest.get_item_group(item:get_name(), group) ~= 0 then
   100         -				return group, sorcery.matreg.lookup[item:get_name()]
   101         -			end
   102         -		end
   103         -	end
   104         -	return false
   105         -end
   106      9   local enchanter_update = function(pos)
   107     10   	local meta = minetest.get_meta(pos)
   108     11   	local inv = meta:get_inventory()
   109     12   	local item = inv:get_stack('item',1)
   110     13   	local slots = ''
   111         -	local itype, imat = enchanter_getsubj(item)
   112         -	if itype and imat and imat.data.slots then
           14  +	local imat = sorcery.enchant.getsubj(item)
           15  +	if imat and imat.data.slots then
   113     16   		local n = #imat.data.slots
   114     17   		local sw, sh = 2.1, 2.1;
   115     18   		local w = sw * n;
   116         -		local spells = sorcery.enchant.get(item).spells
           19  +		local item_enchantment = sorcery.enchant.get(item)
           20  +		local spells = item_enchantment.spells
   117     21   		for i=1,n do
   118     22   			local slot=imat.data.slots[i]
   119     23   			local x = (4 - (w/2) + (sw * (i-1))) + 0.2
   120     24   			local offtbl = {
   121     25   				[1] = {0};
   122     26   				[2] = {0.3, 0.3};
   123     27   				[3] = {0.3,   0, 0.3};
................................................................................
   139     43   					color = sorcery.lib.color(sorcery.data.affinities[aff].color);
   140     44   					desc = sorcery.data.affinities[aff].desc;
   141     45   				}
   142     46   			end
   143     47   			local hovertitle = 'Empty spell slot';
   144     48   			local conf = tostring(math.floor(slot.confluence*100)) .. '%'
   145     49   			local hoverdesc = 'An enchantment of one the following affinities can be anchored into this slot at ' .. conf .. ' confluence';
           50  +			local hovercolor = nil
   146     51   			for _,sp in pairs(spells) do
           52  +				local spdata = sorcery.data.enchants[sp.id]
   147     53   				if sp.slot == i then
           54  +					hovercolor = sorcery.lib.color(spdata.tone):readable()
   148     55   					hovertitle = sorcery.lib.str.capitalize(sp.id)
   149         -					hoverdesc = 'An enchantment is anchored in this slot at ' .. conf .. ' confluence'
           56  +					hoverdesc = sorcery.lib.str.capitalize(spdata.desc) .. '. Anchored in this slot at ' .. conf .. ' confluence'
           57  +					if sp.boost > 10 then
           58  +						hoverdesc = hoverdesc .. ' and boosted by ' .. tostring((sp.boost - 10) * 10) .. '%'
           59  +					elseif sp.boost < 10 then
           60  +						hoverdesc = hoverdesc .. ' but weakened by ' .. tostring((10 - sp.boost) * 10) .. '%'
           61  +					end
           62  +					hoverdesc = hoverdesc .. '.'
           63  +					local addrune = function(tex)
           64  +						pwr = pwr .. string.format([[
           65  +							image[%f,%f;%f,%f;%s]
           66  +						]], x+0.43,y+0.6,sw/2.7,sh/2.7, tex)
           67  +					end
           68  +					local rune = 'sorcery_enchant_' .. sp.id .. '.png'
           69  +					local energy = item_enchantment.energy
           70  +					local fullenergy = imat.data.maxenergy
           71  +					if energy <= fullenergy then
           72  +						addrune(rune .. '^[opacity:110^[lowpart:' .. tostring(math.floor((energy/fullenergy) * 100)) .. '%:' .. rune)
           73  +					elseif energy == fullenergy then addrune(rune)
           74  +					elseif energy >= fullenergy then
           75  +						addrune(rune .. '^[colorize:#ffffff:' ..
           76  +							tostring(255 * math.min(1,(energy / fullenergy * 2))))
           77  +					end
   150     78   					break
   151     79   				end
   152     80   			end
   153     81   			slots = slots .. string.format([[
   154     82   				image[%f,%f;%f,%f;sorcery_pentacle.png]
   155     83   				tooltip[%f,%f;%f,%f;%s;%s;%s]
   156     84   			]],
   157     85   				x,y, sw,sh,
   158     86   				x+0.20,y+0.16, sw-0.84,sh-0.76,
   159     87   				minetest.formspec_escape(sorcery.lib.ui.tooltip {
   160     88   					title = hovertitle;
   161     89   					desc = hoverdesc;
           90  +					color = hovercolor;
   162     91   					props = ap;
   163     92   				}),
   164     93   				'#37002C','#FFC8F5'
   165     94   			) .. pwr
   166     95   		end
   167     96   	end
   168     97   
................................................................................
   175    104   		list[context;foci;2.5,2;1,1;1]
   176    105   		list[context;foci;4.5,2;1,1;2]
   177    106   		list[current_player;main;0,4.7;8,4;]
   178    107   		listring[current_player;main]
   179    108   		listring[context;item]
   180    109   	]] .. slots)
   181    110   end
          111  +
          112  +sorcery.enchant = {} do
          113  +	sorcery.enchant.update_enchanter = enchanter_update
          114  +	local m = sorcery.lib.marshal
          115  +	local ench_t = m.g.struct {
          116  +		id = m.t.str;
          117  +		slot = m.t.u8;
          118  +		boost = m.t.u8; -- every enchantment has an intrinsic force
          119  +		-- separate from the confluence of the slot, which is
          120  +		-- determined by the composition of the wand used to generate
          121  +		-- it (for instance, a gold-wired wand at low wear, or a wand
          122  +		-- with specific gemstones, may have a boost level above 10)
          123  +		-- boost is divided by 10 to yield a float
          124  +		reliability = m.t.u8;
          125  +		-- reliability is a value between 0 and 100, where 0 means the
          126  +		-- spell fails every time and 100 means it never fails. not
          127  +		-- relevant for every spell
          128  +	}
          129  +	local pack, unpack = m.transcoder {
          130  +		spells = m.g.array(8, ench_t);
          131  +		energy = m.t.u16;
          132  +	}
          133  +	sorcery.enchant.getsubj = function(item)
          134  +		if not item:is_empty() then
          135  +			local eligible = {}
          136  +			for name, spell in pairs(sorcery.data.enchants) do
          137  +				for g,v in pairs(item:get_definition().groups) do
          138  +					if v~= 0 and sorcery.lib.tbl.has(spell.groups,g) then
          139  +						eligible[#eligible+1] = name
          140  +						goto skip
          141  +					end
          142  +				end
          143  +			::skip::end
          144  +			return sorcery.matreg.lookup[item:get_name()], eligible
          145  +		else return nil end
          146  +	end
          147  +	local key = 'sorcery_enchantment_recs'
          148  +	sorcery.enchant.set = function(stack, data, noup)
          149  +		local meta = stack:get_meta()
          150  +		meta:set_string(key, sorcery.lib.str.meta_armor(pack(data),true))
          151  +		if not noup then stack=sorcery.enchant.stackup(stack) end
          152  +	end
          153  +	sorcery.enchant.get = function(stack)
          154  +		local meta = stack:get_meta()
          155  +		if meta:contains(key) then
          156  +			local data = sorcery.lib.str.meta_dearmor(meta:get_string(key),true)
          157  +			return unpack(data)
          158  +		else
          159  +			return {
          160  +				spells = {};
          161  +				energy = 0;
          162  +			}
          163  +		end
          164  +	end
          165  +	sorcery.enchant.strength = function(stack,id)
          166  +		-- this functions should be used whenever you need to
          167  +		-- determine the power of a particular enchantment on
          168  +		-- an enchanted item.
          169  +		local e = sorcery.enchant.get(stack)
          170  +		local p = 0.0
          171  +		local slots = sorcery.matreg.lookup[stack:get_name()].data.slots
          172  +		-- TODO handle strength-boosting spells!
          173  +		for _,s in pairs(e.spells) do
          174  +			print(dump(s))
          175  +			if s.id == id then p = p + ((s.boost * slots[s.slot].confluence)/10) end
          176  +		end
          177  +		return p
          178  +	end
          179  +	sorcery.enchant.stackup = function(stack)
          180  +		-- stack update function. this should be called whenever
          181  +		-- the enchantment status of a stack changes; it will
          182  +		-- alter/reset tool capabilities and tooltip as necessary
          183  +		local e = sorcery.enchant.get(stack)
          184  +		local meta = stack:get_meta()
          185  +		local def = stack:get_definition()
          186  +		local mat = sorcery.enchant.getsubj(stack)
          187  +		local done = {}
          188  +		local props = {}
          189  +		local interference = {}
          190  +		-- meta:set_string('tool_capabilities','')
          191  +		meta:set_tool_capabilities(nil); -- TODO this probably only works
          192  +		-- in >5.3; maybe bring in the old JSON mechanism so it works in
          193  +		-- older versions as well?
          194  +		local basecaps = def.tool_capabilities
          195  +		for _,s in pairs(e.spells) do
          196  +			if done[s.id] then goto skip end
          197  +			done[s.id] = true
          198  +			local pwr = sorcery.enchant.strength(stack,s.id)
          199  +				-- somewhat wasteful…
          200  +			local e = sorcery.data.enchants[s.id]
          201  +			if e.apply then stack = e.apply(stack,pwr,basecaps) end
          202  +			props[#props+1] = {
          203  +				title = e.name;
          204  +				desc = e.desc;
          205  +				color = sorcery.lib.color(e.tone);
          206  +			}
          207  +			local inf = mat.data.slots[s.slot].interference
          208  +			if inf then for k,v in pairs(inf) do
          209  +				interference[k] = interference[k] + v
          210  +			end end
          211  +		::skip::end
          212  +		if #interference > 0 then
          213  +			if interference.speed then stack = sorcery.data.enchants.pierce.apply(stack,-interference.speed,basecaps) end
          214  +			if interference.durability then stack = sorcery.data.enchants.endure.apply(stack,-interference.durability,basecaps) end
          215  +		end
          216  +		meta = stack:get_meta() -- necessary? unclear
          217  +		if #e.spells > 0 then
          218  +			meta:set_string('description', sorcery.lib.ui.tooltip {
          219  +				title = 'Enchanted ' .. def.description;
          220  +				props = props;
          221  +			})
          222  +		else
          223  +			meta:set_string('description',def.description)
          224  +		end
          225  +		return stack
          226  +	end
          227  +end
   182    228   
   183    229   minetest.register_node('sorcery:enchanter', {
   184    230   	description = 'Enchanter';
   185    231   	drawtype = 'mesh';
   186    232   	mesh = 'sorcery-enchanter.obj';
   187    233   	paramtype = 'light';
   188    234   	paramtype2 = 'facedir';
................................................................................
   196    242   		"default_bronze_block.png";
   197    243   		"default_junglewood.png";
   198    244   		"default_gold_block.png";
   199    245   	};
   200    246   	on_construct = function(pos)
   201    247   		local meta = minetest.get_meta(pos)
   202    248   		local inv = meta:get_inventory()
          249  +		meta:set_string('infotext','Enchanter')
   203    250   		inv:set_size('item', 1)
   204    251   		inv:set_size('foci', 3)
   205    252   		enchanter_update(pos)
   206    253   	end;
   207    254   	on_metadata_inventory_put  = enchanter_update;
   208    255   	on_metadata_inventory_move = enchanter_update;
   209    256   	on_metadata_inventory_take = enchanter_update;
   210    257   })
   211    258   
          259  +minetest.register_craftitem('sorcery:enchanter_channeler',{
          260  +	inventory_image = 'sorcery_enchanter_channeler.png';
          261  +	description = 'Channeler';
          262  +})
          263  +minetest.register_craftitem('sorcery:enchanter_pedestal',{
          264  +	inventory_image = 'sorcery_enchanter_pedestal.png';
          265  +	description = 'Pedestal';
          266  +})
          267  +minetest.register_craft {
          268  +	output = 'sorcery:enchanter_channeler';
          269  +	recipe = {
          270  +		{'','default:bronze_ingot',''};
          271  +		{'basic_materials:gold_wire','basic_materials:steel_strip','basic_materials:gold_wire'};
          272  +		{'','sorcery:electrum_ingot',''};
          273  +	};
          274  +	replacements = {
          275  +		{'basic_materials:gold_wire','basic_materials:empty_spool'};
          276  +		{'basic_materials:gold_wire','basic_materials:empty_spool'};
          277  +	};
          278  +}
          279  +minetest.register_craft {
          280  +	output = 'sorcery:enchanter_pedestal';
          281  +	recipe = {
          282  +		{'basic_materials:copper_strip','group:wood','basic_materials:copper_strip'};
          283  +		{'','default:bronze_ingot',''};
          284  +		{'','group:wood',''};
          285  +	};
          286  +}
          287  +minetest.register_craft {
          288  +	output = 'sorcery:enchanter';
          289  +	recipe = {
          290  +		{'','sorcery:enchanter_channeler',''};
          291  +		{'sorcery:enchanter_channeler','sorcery:enchanter_pedestal','sorcery:enchanter_channeler'};
          292  +		{'group:wood','group:wood','group:wood'};
          293  +	};
          294  +}
   212    295   for i=1,10 do
   213    296   	minetest.register_node('sorcery:air_flash_' .. i, {
   214    297   		drawtype = 'airlike';
   215    298   		pointable = false; walkable = false;
   216    299   		buildable_to = true;
   217    300   		sunlight_propagates = true;
   218    301   		light_source = i + 4;
................................................................................
   226    309   			end
   227    310   		end
   228    311   	});
   229    312   end
   230    313   
   231    314   minetest.register_on_dignode(function(pos, node, puncher)
   232    315   	if puncher == nil then return end -- i don't know why
   233         -	-- this is necessary but you get rare crashed without it
          316  +	-- this is necessary but you get rare crashes without it
   234    317   
   235    318   	-- we're goint to do something VERY evil here and
   236    319   	-- replace the air with a "glow-air" that removes
   237    320   	-- itself after a short period of time, to create
   238    321   	-- a flash of light when an enchanted tool's used
   239    322   	-- to dig out a node
   240    323   	local tool = puncher:get_wielded_item()
   241         -	local meta = tool:get_meta()
          324  +	local ench = sorcery.enchant.get(tool)
          325  +	if #ench.spells == 0 then return end
          326  +	print('enchanted!')
   242    327   	local sparks = {}
   243         -	local spark = function(name,color)
   244         -		if meta:contains('enchant_' .. name) then
   245         -			sparks[#sparks + 1] = {
   246         -				color = color;
   247         -				count = meta:get_int('enchant_' .. name);
   248         -			}
   249         -		end
          328  +	-- local spark = function(name,color)
          329  +	local material = sorcery.enchant.getsubj(tool)
          330  +	local totalcost = 0
          331  +	do local done = {} for _,sp in pairs(ench.spells) do
          332  +		if done[sp.id] then goto skip end
          333  +		done[sp.id] = true
          334  +
          335  +		local data = sorcery.data.enchants[sp.id]
          336  +		local strength = sorcery.enchant.strength(tool,sp.id)
          337  +		local ch = math.random(1,100)
          338  +		local props = {
          339  +			fail = ch > sp.reliability;
          340  +			user = puncher;
          341  +			pos = pos;
          342  +			node = node;
          343  +			tool = tool;
          344  +			material = material.data;
          345  +			enchantment = ench;
          346  +			power = strength;
          347  +			spell = sp;
          348  +			sparks = sparks;
          349  +			cost = data.cost;
          350  +		} 
          351  +		if data.on_dig then data.on_dig(props) end
          352  +		if props.cost ~= 0 then totalcost = totalcost + math.max(1,math.floor(props.cost * strength)) end
          353  +
          354  +		if ch > sp.reliability then goto skip end
          355  +		sparks[#sparks + 1] = {
          356  +			color = sorcery.lib.color(data.tone):brighten(1.1);
          357  +			count = strength * 7;
          358  +		}
          359  +	::skip::end end
          360  +	if totalcost > 0 then
          361  +		local conservation = sorcery.enchant.strength(tool,'conserve') * 0.5
          362  +		totalcost = totalcost - (totalcost * conservation)
          363  +		ench.energy = math.max(0,ench.energy - (totalcost - (material.data.energysource or 0)))
   250    364   	end
   251         -	spark('durable',sorcery.lib.color(0,89,245))
   252         -	spark('fast',sorcery.lib.color(245,147,89))
   253    365   	if #sparks == 0 then return end
   254    366   	if math.random(5) == 1 then
   255    367   		minetest.set_node(pos, {name='sorcery:air_flash_' .. tostring(math.random(10))})
   256    368   	end
   257    369   	local range = function(min, max)
   258    370   		local span = max - min
   259    371   		local val = math.random() * span
   260    372   		return val + min
   261    373   	end
   262    374   	for _,s in pairs(sparks) do
   263         -		for i=0,math.floor(s.count * range(1,3))  do
          375  +		for i=1,math.floor(s.count * range(1,3))  do
   264    376   			local life = range(0.3,1);
   265    377   			minetest.add_particle {
   266    378   				pos = {
   267    379   					x = pos.x + range(-0.5,0.5);
   268    380   					z = pos.z + range(-0.5,0.5);
   269    381   					y = pos.y + range(-0.5,0.5);
   270    382   				};
................................................................................
   276    388   				velocity = {
   277    389   					x = range(-1.3,1.3);
   278    390   					z = range(-1.3,1.3);
   279    391   					y = range( 0.3,0.9);
   280    392   				};
   281    393   				expirationtime = life;
   282    394   				size = range(0.5,1.5);
   283         -				vertical = true;
   284         -				texture = sorcery.lib.image('sorcery_spark.png'):multiply(s.color:brighten(1.2)):render();
          395  +				texture = sorcery.lib.image('sorcery_spark.png'):multiply(s.color):render();
   285    396   				glow = 14;
   286    397   				animation = {
   287    398   					type = "vertical_frames";
   288    399   					aspect_w = 16;
   289    400   					aspect_h = 16;
   290         -					length = life * 1.1;
          401  +					length = life + 0.1;
   291    402   				};
   292    403   			}
   293    404   		end
   294    405   	end
          406  +
          407  +	-- destroy spells that can no longer be sustained
          408  +	if ench.energy == 0 then
          409  +		ench.spells = {}
          410  +		sorcery.enchant.set(tool,ench)
          411  +	else
          412  +		sorcery.enchant.set(tool,ench,true)
          413  +	end
          414  +	puncher:set_wielded_item(tool)
   295    415   end)
          416  +
          417  +minetest.register_chatcommand('enchants', {
          418  +	description = 'Log information about the currently held object\'s enchantment';
          419  +	privs = { server = true };
          420  +	func = function(caller,params)
          421  +		local tool = minetest.get_player_by_name(caller):get_wielded_item()
          422  +		minetest.chat_send_player(caller, dump(sorcery.enchant.get(tool)))
          423  +		local binary = tool:get_meta():get_string('sorcery_enchantment_recs')
          424  +		local dumpout = ''
          425  +		for i=1,string.len(binary) do
          426  +			dumpout = dumpout .. string.format('%x%s',string.byte(binary,i),(i%16==0 and '\n') or ' ') 
          427  +		end
          428  +		print(dumpout)
          429  +	end;
          430  +})

Modified gems.lua from [79f4b638de] to [461d75b6c5].

     1      1   --- gemstones
     2      2   
     3      3   sorcery.register_gem = function(name,gem)
     4      4   	local itemname = gem.foreign or 'sorcery:gem_' .. name
     5      5   	local shardname = gem.foreign_shard or 'sorcery:gem_' .. name .. '_shard'
            6  +	local amuletname = gem.foreign_amulet or 'sorcery:gem_' .. name .. '_amulet'
     6      7   
     7      8   	local tools, armors = sorcery.matreg.tools, sorcery.matreg.armors
     8      9   	if gem.tools then for _,t in pairs(tools) do
     9     10   		sorcery.matreg.lookup[(gem.items and gem.items[t]) or ('sorcery:' .. t .. '_' .. name)] = {
    10     11   			gem = true;
    11     12   			id = name; data = gem;
    12     13   		}
    13     14   	end end
    14     15   	if gem.armor then for _,a in pairs(armors) do
    15         -		sorcery.matreg.lookup[(gem.items and gem.items[t]) or ('sorcery:' .. a .. '_' .. name)] = {
           16  +		sorcery.matreg.lookup[(gem.items and gem.items[a]) or ('sorcery:' .. a .. '_' .. name)] = {
    16     17   			gem = true;
    17     18   			id = name; data = gem;
    18     19   		}
    19     20   	end end
    20     21   
    21     22   	if gem.foreign_shard then
    22     23   		minetest.clear_craft {output=shardname}
................................................................................
    23     24   	else
    24     25   		minetest.register_craftitem(shardname, {
    25     26   			description = sorcery.lib.str.capitalize(name) .. ' shard';
    26     27   			inventory_image = 'sorcery_gem_' .. name .. '_shard.png';
    27     28   			groups = { sorcery_shard = 1; };
    28     29   			_proto = gem;
    29     30   		})
           31  +	end
           32  +	if not gem.foreign_amulet then
           33  +		minetest.register_craftitem(amuletname, {
           34  +			description = sorcery.lib.str.capitalize(name) .. ' amulet';
           35  +			inventory_image = sorcery.lib.image('sorcery_amulet.png'):multiply(sorcery.lib.color(gem.tone)):render();
           36  +			_proto = {
           37  +				id = name;
           38  +				data = gem;
           39  +			};
           40  +		}) 
    30     41   	end
    31     42   	minetest.register_craft {
    32     43   		type = 'shapeless';
    33     44   		recipe = (minetest.get_modpath('xdecor') and {
    34     45   			'xdecor:hammer', itemname;
    35     46   		}) or { itemname };
    36     47   		output = shardname .. ' 9';
................................................................................
    43     54   		recipe = {
    44     55   			shardname, shardname, shardname;
    45     56   			shardname, shardname, shardname;
    46     57   			shardname, shardname, shardname;
    47     58   		};
    48     59   		output = itemname;
    49     60   	};
           61  +	minetest.register_craft {
           62  +		recipe = {
           63  +			{shardname,itemname,shardname};
           64  +			{itemname,itemname,itemname};
           65  +			{shardname,itemname,shardname};
           66  +		};
           67  +		output = amuletname;
           68  +	};
    50     69   
    51     70   	-- generate lenses and crafting recipes
    52     71   	for _, kind in pairs { 'amplifier','rectifier','concave','convex' } do
    53     72   		local id = 'sorcery:lens_' .. kind .. '_' .. name
    54     73   		minetest.register_tool(id, {
    55     74   			inventory_image = sorcery.lib.image('sorcery_lens_overlay_gold.png'):
    56     75   				blit(sorcery.lib.image('sorcery_lens_' .. kind .. '.png'):
    57     76   					multiply(sorcery.lib.color(gem.tone):brighten(1.1))):
    58         -			render();
           77  +				render();
    59     78   			description = sorcery.lib.str.capitalize(name) .. ' ' .. kind .. ' lens';
    60         -			group = { sorcery_enchanting_lens = 1 };
           79  +			groups = { sorcery_enchanting_lens = 1 };
           80  +			_proto = {
           81  +				gem = name;
           82  +				kind = kind;
           83  +			};
    61     84   		})
    62     85   	end
    63     86   	do local casing = 'sorcery:fragment_gold'
    64     87   		minetest.register_craft {
    65     88   			output = 'sorcery:lens_convex_' .. name;
    66     89   			recipe = {
    67     90   				{'',       casing, ''};

Modified harvester.lua from [26814ac7ca] to [8384a43ac0].

    36     36   		local meta = minetest.get_meta(pos)
    37     37   		local inv = meta:get_inventory()
    38     38   		if inv:is_empty('charge') then return false end
    39     39   		
    40     40   		local ley = sorcery.ley.estimate(pos)
    41     41   		local charged = false
    42     42   		for i=1,inv:get_size('charge') do
    43         -			local repair_per_tick = (65536 / 64) * (15 * ley.force)
           43  +			local curve = function(x) return ((x*10)^2)/10 end
    44     44   			local item = inv:get_stack('charge',i)
    45     45   			if minetest.get_item_group(item:get_name(), 'sorcery_wand') ~= 0 then
           46  +				local mese = 1
           47  +				if sorcery.wands.util.getproto(item).gem == 'mese' then
           48  +					mese = 1.5 end
           49  +				local repair_per_tick = (65536 / 80) * curve(ley.force) * mese
    46     50   				local spell = item:get_meta():get_string('sorcery_wand_spell')
    47     51   				if spell == '' then goto skip end
    48     52   				local aff = sorcery.data.spells[spell].leytype
    49     53   				if aff == ley.aff[1] or aff == ley.aff[2] then
    50     54   					repair_per_tick = repair_per_tick * 2 end
    51         -			else goto skip end -- item is not magical
    52         -			print('repair cycle! repairing item'..item:get_name()..' by',repair_per_tick * (elapse / 60))
    53         -			item:set_wear(math.max(0,item:get_wear() - repair_per_tick * (elapse / 60)))
           55  +				item:set_wear(math.max(0,item:get_wear() - repair_per_tick * (elapse / 60)))
           56  +			else 
           57  +				local e = sorcery.enchant.get(item)
           58  +				if #e.spells == 0 then goto skip end -- item is not magical
           59  +				local mat = sorcery.enchant.getsubj(item)
           60  +				if e.energy < mat.data.maxenergy then
           61  +					local energy_per_tick = (100 * curve(ley.force)) + ((mat.data.energysource or 0)*2)
           62  +					e.energy = math.min(e.energy + energy_per_tick, mat.data.maxenergy)
           63  +					sorcery.enchant.set(item,e,true)
           64  +				else goto skip end -- already fully charged
           65  +			end
           66  +			-- print('repair cycle! repairing item'..item:get_name()..' by',repair_per_tick * (elapse / 60))
    54     67   			inv:set_stack('charge',i,item)
    55     68   			charged = true
    56     69   		::skip::end
    57     70   
    58     71   		return charged
    59     72   	end;
    60     73   
    61     74   	on_construct = function(pos)
    62     75   		local meta = minetest.get_meta(pos)
    63     76   		local inv = meta:get_inventory()
    64     77   		inv:set_size('charge', 3)
           78  +		meta:set_string('infotext','Harvester')
    65     79   		meta:set_string('formspec', [[
    66     80   			size[8,5]
    67     81   			list[context;charge;2.5,0;3,1;]
    68     82   			list[current_player;main;0,1.3;8,4;]
    69     83   			listring[]
    70     84   		]])
    71     85   	end;

Added hotmetallurgy.lua version [b05dd85eee].

            1  +-- alloying furnace
            2  +--
            3  +-- there are several kinds of alloy furnace, with varying
            4  +-- capabilities. the simplest can alloy two simple metals;
            5  +-- the most complex can alloy four high-grade metals such
            6  +-- as titanium, platinum, iridium, or levitanium.
            7  +--
            8  +-- furnace recipes follow a pattern: the number of crucibles
            9  +-- determines the input slots, the type of crucible determines
           10  +-- how hot the furnace can get, and various other components
           11  +-- (like a coolant circulator) can be added to allow the
           12  +-- creation of exotic metals.
           13  +--
           14  +-- alloy furnaces produce ingots that can later be melted down
           15  +-- again and cast into a mold to produce items of particular
           16  +-- shapes.
           17  +
           18  +-- there are five kinds of crucibles: clay, aluminum, platinum,
           19  +-- duridium, and impervium. clay crucibles are made by molding
           20  +-- clay into the proper shape and then firing it in a furnace.
           21  +-- others are made by casting.
           22  +
           23  +local fragments_per_ingot = 4 
           24  +
           25  +for _, c in pairs { 'clay', 'aluminum', 'platinum', 'duranium' } do
           26  +	minetest.register_craftitem('sorcery:crucible_' .. c, {
           27  +		description = sorcery.lib.str.capitalize(c .. ' crucible');
           28  +		inventory_image = 'sorcery_crucible_' .. c .. '.png';
           29  +	})
           30  +end
           31  +
           32  +minetest.register_craftitem('sorcery:crucible_clay_molding', {
           33  +	description = sorcery.lib.str.capitalize('Crucible molding');
           34  +	inventory_image = 'sorcery_crucible_clay_molding.png';
           35  +})
           36  +
           37  +minetest.register_craft {
           38  +	recipe = {
           39  +		{ 'default:clay_lump', '', 'default:clay_lump'};
           40  +		{ 'default:clay_lump', '', 'default:clay_lump'};
           41  +		{ 'default:clay_lump', 'default:clay_lump', 'default:clay_lump'};
           42  +	};
           43  +	output = 'sorcery:crucible_clay_molding';
           44  +}
           45  +minetest.register_craft {
           46  +	type = 'shapeless';
           47  +	recipe = { 'sorcery:crucible_clay_molding' };
           48  +	output = 'default:clay_lump 7';
           49  +}
           50  +minetest.register_craft {
           51  +	type = 'cooking';
           52  +	recipe = 'sorcery:crucible_clay_molding';
           53  +	cooktime = 40;
           54  +	output = 'sorcery:crucible_clay';
           55  +}
           56  +
           57  +local burn_layout = function(v)
           58  +	local layouts = {
           59  +		[1] = {w = 1, h = 1}; [2] = {w = 2, h = 1}; [3] = {w = 3, h = 1};
           60  +		[4] = {w = 2, h = 2}; [5] = {w = 3, h = 2}; [6] = {w = 3, h = 2};
           61  +		[7] = {w = 4, h = 2}; [8] = {w = 4, h = 2}; [9] = {w = 3, h = 3};
           62  +	}
           63  +	local inpos  = { x = 2.8, y = 1 }
           64  +	local insize = layouts[v.in]
           65  +	local outpos = { x = 5.2, y = 2.4 }
           66  +	local outsize = layouts[v.out]
           67  +	local fuelpos = { x = 2.8, y = 3.4 }
           68  +	local fuelsize = layouts[v.fuel]
           69  +	return string.format([[
           70  +		list[context;input;%f,%f;%f,%f;]
           71  +		list[context;output;%f,%f;%f,%f;]
           72  +		list[context;fuel;%f,%f;%f,%f;]
           73  +		
           74  +		image[2.3,1.9;1,1;default_furnace_fire_bg.png^[lowpart:%u%%:default_furnace_fire_fg.png]
           75  +		image[3.2,1.9;1,1;gui_furnace_arrow_bg.png^[lowpart:%u%%:gui_furnace_arrow_fg.png^[transformR270]
           76  +
           77  +		listring[context;output] listring[current_player;main]
           78  +		listring[context;input]  listring[current_player;main]
           79  +		listring[context;fuel]   listring[current_player;main]
           80  +	]], 
           81  +
           82  +	--input
           83  +		inpos.x - insize.w/2, -- pos
           84  +		inpos.y - insize.h/2, 
           85  +		insize.w, insize.h, -- size
           86  +	--output
           87  +		outpos.x - outsize.w/2, -- pos
           88  +		outpos.y - outsize.h/2, 
           89  +		outsize.w, outsize.h, -- size
           90  +	--fuel
           91  +		fuelpos.x - fuelsize.w/2, -- pos
           92  +		fuelpos.y - fuelsize.h/2, 
           93  +		fuelsize.w, fuelsize.h, -- size
           94  +
           95  +		v.burn, v.prog
           96  +	)
           97  +end
           98  +
           99  +local kiln_formspec = function(kind, fuel_progress, cook_progress)
          100  +	return [[
          101  +		size[8,8]
          102  +		list[current_player;main;0,4.2;8,4]
          103  +		list[context;wax;0,2;1,1]
          104  +	]] .. burn_layout{
          105  +		in = kind.size^2;
          106  +		out = kind.outsize;
          107  +		fuel = kind.fuelsize;
          108  +		burn = fuel_progress;
          109  +		prog = cook_progress;
          110  +	}
          111  +end
          112  +
          113  +local smelter_formspec = function(kind, fuel_progress, smelt_progress)
          114  +	return [[
          115  +		size[8,8]
          116  +		list[current_player;main;0,4.2;8,4]
          117  +	]] .. burn_layout {
          118  +		in = kind.size;
          119  +		out = kind.outsize;
          120  +		fuel = kind.fuelsize;
          121  +		burn = fuel_progress;
          122  +		prog = smelt_progress;
          123  +	}
          124  +end
          125  +
          126  +local find_recipe = function(inv)
          127  +	local mix = {}
          128  +	local count = 0
          129  +	for i=1,inv:get_size('input') do
          130  +		local m = inv:get_stack('input',i)
          131  +		if m:is_empty() then goto skip end
          132  +		local l = sorcery.data.metallookup[m:get_name()]
          133  +		if not l then return false end
          134  +		mix[l.id] = (mix[l.id] or 0) + l.value
          135  +		count = count + l.value
          136  +	::skip::end
          137  +	-- everything is metal, we've finished summing it up.
          138  +	-- let's see if the assembled items match the ratio
          139  +	-- specified in any of the smelting recipes.
          140  +	local matches = 0
          141  +	for _,rec in pairs(sorcery.data.alloys) do
          142  +		local fac = nil
          143  +		local meltpoint = 1
          144  +		if rec.metals == nil then goto skip_recipe end
          145  +		for metal, ratio in pairs(rec.metals) do
          146  +			if mix[metal] and mix[metal] % ratio == 0 then
          147  +				if fac then
          148  +					if mix[metal] / ratio ~= fac then goto skip_recipe end
          149  +				else fac = math.floor(mix[metal] / ratio) end
          150  +				local m = sorcery.data.metals[metal]
          151  +				if m.meltpoint then
          152  +					meltpoint = math.max(meltpoint, m.meltpoint) end
          153  +			else goto skip_recipe end
          154  +		end
          155  +		do return rec, count, fac, meltpoint end
          156  +	::skip_recipe::end
          157  +	return false
          158  +end
          159  +
          160  +local update_smelter = function(pos)
          161  +	local meta = minetest.get_meta(pos)
          162  +	local inv = meta:get_inventory()
          163  +	local proto = minetest.registered_nodes[minetest.get_node(pos).name]._proto
          164  +	local recipe, count, factor, meltpoint = find_recipe(inv)
          165  +	if recipe and proto.temp >= meltpoint then
          166  +		minetest.get_node_timer(pos):start(1)
          167  +	else
          168  +		meta:set_float('burntime',0)
          169  +	end
          170  +end
          171  +
          172  +local smelter_step = function(kind,active,pos,delta)
          173  +	local meta = minetest.get_meta(pos)
          174  +	local inv = meta:get_inventory()
          175  +	local recipe, count, factor = find_recipe(inv)
          176  +	local cooktime
          177  +	local elapsed = meta:get_float('burntime') + delta
          178  +	meta:set_float('burnleft',meta:get_float('burnleft') - delta)
          179  +	if (not active) and (not recipe) then return false end
          180  +	if meta:get_float('burnleft') <= 0 or not active then
          181  +		if recipe then
          182  +			local burn, frep = minetest.get_craft_result {
          183  +				method = 'fuel', width = 1;
          184  +				items = { inv:get_stack('fuel',1) };
          185  +			}
          186  +			if burn.time == 0 then goto nofuel end
          187  +			inv:set_stack('fuel', 1, frep.items[1])
          188  +			meta:set_float('burnleft',burn.time)
          189  +			meta:set_float('burnmax',burn.time)
          190  +			if not active then
          191  +				minetest.swap_node(pos,
          192  +					sorcery.lib.tbl.merge(minetest.get_node(pos), {
          193  +						name = kind.id .. '_active'
          194  +					}))
          195  +				active = true
          196  +			end
          197  +		else goto nofuel end
          198  +	end
          199  +
          200  +	if not recipe then goto update end
          201  +	
          202  +	cooktime = ((recipe.cooktime / fragments_per_ingot) * count) / factor
          203  +	if elapsed >= cooktime then
          204  +		elapsed = 0
          205  +		-- remove used items
          206  +		for i=1,inv:get_size('input') do
          207  +			local s = inv:get_stack('input',i)
          208  +			if s:is_empty() then goto skip end
          209  +			s:take_item(1) inv:set_stack('input',i,s)
          210  +		::skip::end
          211  +
          212  +		local outstack
          213  +		if count % fragments_per_ingot == 0 then
          214  +			outstack = ItemStack {
          215  +				name = sorcery.data.metals[recipe.output].ingot or 'sorcery:' .. recipe.output .. '_ingot';
          216  +				count = count / fragments_per_ingot;
          217  +			}
          218  +		else
          219  +			outstack = ItemStack {
          220  +				name = 'sorcery:fragment_' .. recipe.output;
          221  +				count = count;
          222  +			}
          223  +		end
          224  +
          225  +		local leftover = inv:add_item('output',outstack)
          226  +		if not leftover:is_empty() then
          227  +			minetest.add_item(pos, leftover)
          228  +		end
          229  +	end
          230  +
          231  +	::update::
          232  +		meta:set_float('burntime',elapsed)
          233  +		meta:set_string('formspec', smelter_formspec(kind,
          234  +			math.min(1, meta:get_float('burnleft') /
          235  +						meta:get_float('burnmax')
          236  +					) * 100, -- fuel
          237  +			(cooktime and math.min(1, elapsed / cooktime) * 100) or 0 -- smelt
          238  +		))
          239  +		do return active end
          240  +
          241  +	::nofuel::
          242  +		if active then
          243  +			minetest.swap_node(pos,
          244  +				sorcery.lib.tbl.merge(minetest.get_node(pos), { name = kind.id }))
          245  +		end
          246  +		meta:set_float('burnleft',0) -- just in case
          247  +	::noburn::
          248  +		meta:set_float('burntime',0) -- just in case
          249  +		meta:set_string('formspec', smelter_formspec(kind, 0, 0))
          250  +		return false
          251  +end
          252  +local register_kiln = function(kind)
          253  +	local box = {
          254  +		open = {
          255  +			type = 'fixed';
          256  +			fixed = {
          257  +				-0.5,-0.5,-0.5,
          258  +				 0.5, 1.6, 0.5
          259  +			};
          260  +		};
          261  +		closed = {
          262  +			type = 'fixed';
          263  +			fixed = {
          264  +				-0.5,-0.5,-0.5,
          265  +				 0.5, 0.7, 0.5
          266  +			};
          267  +		};
          268  +	}
          269  +	local id = 'sorcery:kiln_' .. kind.material
          270  +	local metal = sorcery.data.metals[kind.material]
          271  +	-- use some iffy heuristics to guess the texture
          272  +	local metaltex
          273  +	if kind.material == 'clay' then
          274  +		metaltex = 'default_brick.png';
          275  +	else
          276  +		metaltex = (metal.img and metal.img.block) or
          277  +			(metal.ingot and 'default_' .. kind.material .. '_block.png') or
          278  +			sorcery.lib.image('default_steel_block.png'):multiply(sorcery.lib.color(metal.tone)):render();
          279  +	end
          280  +	local tex = {
          281  +		open = {
          282  +			'default_furnace_front.png';
          283  +			metaltex;
          284  +			'default_copper_block.png';
          285  +			'default_stone.png';
          286  +		};
          287  +		closed = {
          288  +			'default_furnace_front_active.png';
          289  +			'default_copper_block.png';
          290  +			metaltex;
          291  +			'default_stone.png';
          292  +		};
          293  +	};
          294  +	for _,state in pairs{'open','closed'} do
          295  +		local id_closed = id .. '_closed'
          296  +		local id_current = (state == 'closed' and id_closed) or id
          297  +		local desc = sorcery.lib.str.capitalize(kind.temp_name) .. ' kiln';
          298  +		minetest.register_node(id_current, {
          299  +			_active = (state == 'closed');
          300  +			_proto = kind;
          301  +			description = desc;
          302  +			drawtype = "mesh";
          303  +			mesh = 'sorcery-kiln-' .. state .. '.obj';
          304  +			drop = id;
          305  +			sunlight_propagates = true;
          306  +			paramtype1 = 'light';
          307  +			paramtype2 = 'facedir';
          308  +			selection_box = box[state];
          309  +			collision_box = box[state];
          310  +			tiles = tex[state];
          311  +			light_source = (state == 'closed' and 7) or 0;
          312  +			on_construct = function(pos)
          313  +				local meta = minetest.get_meta(pos)
          314  +				local inv = meta:get_inventory()
          315  +				inv:set_size('input', kind.size^2)
          316  +				inv:set_size('output', kind.outsize)
          317  +				inv:set_size('fuel', kind.fuelsize)
          318  +				inv:set_size('wax', 1)
          319  +
          320  +				meta:set_float('burnleft',0)
          321  +				meta:set_float('burnmax',0)
          322  +				meta:set_float('burntime',0)
          323  +
          324  +				meta:set_string('infotext',desc)
          325  +				meta:set_string('formspec',kiln_formspec(kind,0,0))
          326  +			end;
          327  +			on_timer = function(pos,delta)
          328  +			end;
          329  +
          330  +			on_metadata_inventory_put = function(pos)
          331  +				minetest.get_node_timer(pos):start(1) end;
          332  +			on_metadata_inventory_move = function(pos)
          333  +				minetest.get_node_timer(pos):start(1) end;
          334  +			on_metadata_inventory_take = function(pos)
          335  +				minetest.get_node_timer(pos):start(1) end;
          336  +		})
          337  +	end
          338  +	local ingot
          339  +	if kind.material == 'clay' then
          340  +		ingot = 'default:clay_brick';
          341  +	else
          342  +		ingot = metal.ingot or 'sorcery:' .. kind.material .. '_ingot'
          343  +	end
          344  +	minetest.register_craft = {
          345  +		output = id_open;
          346  +		recipe = {
          347  +			{'default:copper_ingot', 'default:copper_ingot', 'default:copper_ingot'};
          348  +			{ingot,'',ingot};
          349  +			{ingot,'default:furnace',ingot};
          350  +		};
          351  +	}
          352  +end
          353  +
          354  +local register_smelter = function(kind)
          355  +	local recipe = {{},{};
          356  +		{'default:stone','default:furnace','default:stone'};
          357  +	} do
          358  +		local on = kind.crucible
          359  +		local ti = 'default:tin_ingot'
          360  +		local cu = 'default:copper_ingot'
          361  +		local crucmap = {
          362  +			[2] = { {cu,cu,cu}, {on,ti,on} };
          363  +			[3] = { {cu,on,cu}, {on,ti,on} };
          364  +			[4] = { {on,cu,on}, {on,ti,on} };
          365  +			[5] = { {on,cu,on}, {on,on,on} };
          366  +			[6] = { {on,on,on}, {on,on,on} };
          367  +		};
          368  +		for y=1,2 do recipe[y] = crucmap[kind.size][y] end
          369  +	end
          370  +	
          371  +	local desc = 'smelter';
          372  +	if kind.temp_name then desc = kind.temp_name .. ' ' .. desc end
          373  +	if kind.size_name then desc = kind.size_name .. ' ' .. desc end
          374  +	desc = sorcery.lib.str.capitalize(desc);
          375  +	local id = 'sorcery:smelter_' .. kind.material .. kind.size_name
          376  +	kind.id = id
          377  +	for _, active in pairs {false, true} do
          378  +		minetest.register_node((active and id .. '_active') or id, {
          379  +			_proto = kind;
          380  +			description = desc;
          381  +			drop = id;
          382  +			groups = { cracky = 2; };
          383  +			paramtype2 = 'facedir';
          384  +			light_source = (active and 9) or 0;
          385  +			on_construct = function(pos)
          386  +				local meta = minetest.get_meta(pos)
          387  +				local inv = meta:get_inventory()
          388  +				inv:set_size('input',kind.size)
          389  +				inv:set_size('output',kind.outsize)
          390  +				inv:set_size('fuel',kind.fuelsize)
          391  +				meta:set_string('infotext', desc)
          392  +				meta:set_string('formspec', smelter_formspec(kind, 0, 0))
          393  +				meta:set_float('burnleft',0)
          394  +				meta:set_float('burnmax',0)
          395  +				meta:set_float('burntime',0)
          396  +			end;
          397  +			on_metadata_inventory_put = update_smelter;
          398  +			on_metadata_inventory_move = update_smelter;
          399  +			on_metadata_inventory_take = update_smelter;
          400  +			on_timer = function(pos,delta) return smelter_step(kind, active, pos, delta) end;
          401  +			-- allow_metadata_inventory_put = function(pos, listname, index, stack, player)
          402  +			-- end;
          403  +			tiles = {
          404  +				'sorcery_smelter_top_' .. tostring(kind.size) .. '.png';
          405  +				'sorcery_smelter_bottom.png';
          406  +				'sorcery_smelter_side.png';
          407  +				'sorcery_smelter_side.png';
          408  +				'sorcery_smelter_side.png';
          409  +				'sorcery_smelter_front' .. ((active and '_hot') or '') .. '.png';
          410  +			};
          411  +		})
          412  +	end
          413  +	minetest.register_craft {
          414  +		recipe = recipe;
          415  +		output = id;
          416  +	}
          417  +end
          418  +
          419  +for _, t in pairs {
          420  +	{1, nil, 'clay'};
          421  +	{2, 'hot','aluminum'};
          422  +	{3, 'volcanic','platinum'};
          423  +	{4, 'stellar','duridium'};
          424  +	{5, 'nova','impervium'};
          425  +} do register_kiln {
          426  +		temp = t[1], temp_name = t[2];
          427  +		material = t[3];
          428  +		size = 3, outsize = 4, fuelsize = 1; -- future-proofing
          429  +	}
          430  +
          431  +	for _, s in pairs {
          432  +		{2, 'small'};
          433  +		{3, 'large'};
          434  +		{4, 'grand'};
          435  +	} do register_smelter {
          436  +		size = s[1], size_name = s[2];
          437  +		temp = t[1], temp_name = t[2];
          438  +		material = t[3];
          439  +		crucible = 'sorcery:crucible_' .. t[3];
          440  +		outsize = 4, fuelsize = 1; -- future-proofing
          441  +	} end
          442  +end

Modified init.lua from [c38fa912b9] to [5c9fa3f240].

    49     49   	'register';
    50     50   }
    51     51   
    52     52   for _,u in pairs {
    53     53   	'leylines'; 'ores'; 'gems';
    54     54   	'potions'; 'infuser'; 'altar'; 'wands';
    55     55   	'tools'; 'enchanter'; 'harvester';
    56         -	'smelter'; 'entities'; 'recipes'; 'coins';
    57         -	'interop';
           56  +	'metallurgy-hot'; 'entities'; 'recipes';
           57  +	'coins'; 'interop'; 'tnodes';
    58     58   } do sorcery.load(u) end

Modified lib/color.lua from [16b55419da] to [e07c3f3979].

    43     43   	end;
    44     44   	
    45     45   	cast = {
    46     46   		number = function(n) return {
    47     47   			red = n; green = n; blue = n;
    48     48   		} end;
    49     49   		table = function(t) return {
    50         -			red = t[1]; green = t[2]; blue = t[3];
           50  +			red = t[1]; green = t[2]; blue = t[3]; alpha = t[4];
    51     51   		} end;
    52     52   	};
    53     53   
    54     54   	construct = function(r,g,b,a)
    55     55   		local clip = function(v)
    56     56   			return math.max(0,math.min(255,v))
           57  +		end;
           58  +		local from_hsl = function(hsl, alpha)
           59  +			-- Based on the algorithm in Computer Graphics: Principles and Practice, by
           60  +			-- James D. Foley et. al., 2nd ed., p. 596
           61  +			-- Degree version, though radian is more natural, I don't want to translate it yet
           62  +			local h = hsl.hue
           63  +			local s = hsl.saturation
           64  +			local l = hsl.luminosity
           65  +			local value = function(n1, n2, hue)
           66  +				if hue > 360 then
           67  +					hue = hue - 360
           68  +				elseif hue < 0 then
           69  +					hue = hue + 360
           70  +				end
           71  +				if hue < 60 then
           72  +					return n1 + (n2 - n1) * hue/60
           73  +				elseif hue < 180 then
           74  +					return n2
           75  +				elseif hue < 240 then
           76  +					return n1 + (n2 - n1) * (240 - hue)/60
           77  +				else
           78  +					return n1
           79  +				end
           80  +			end
           81  +			local m2
           82  +			if l < 0.5 then
           83  +				m2 = l * (1 + s)
           84  +			else
           85  +				m2 = l + s - l * s
           86  +			end
           87  +			local m1 = 2 * l - m2
           88  +			if s == 0 then
           89  +				-- Achromatic, there is no hue
           90  +				-- In book this errors if hue is not undefined, but we set hue to 0 in this case, not nil or something, so
           91  +				return color(l, l, l, alpha)
           92  +			else
           93  +				-- Chromatic case, so there is a hue
           94  +				return color(
           95  +					clip(value(m1, m2, h + 120)*255),
           96  +					clip(value(m1, m2, h)*255),
           97  +					clip(value(m1, m2, h - 120)*255),
           98  +					alpha
           99  +				)
          100  +			end
    57    101   		end;
    58    102   		local warp = function(f)
    59    103   			return function(self, ...)
    60    104   				local n = color(self)
    61    105   				f(n, ...)
    62    106   				return n
    63    107   			end;
................................................................................
    71    115   				minetest.colorize(self:hex(), text)
    72    116   			end;
    73    117   
    74    118   			luminosity = function(self) return
    75    119   				(self.red + self.green + self.blue) / 3
    76    120   			end;
    77    121   
    78         -			readable = function(self, target)
    79         -				target = target or 200
    80         -				local lum = self:luminosity()
    81         -				if lum < target then
    82         -					local f = 1.0 + (target - lum) / 255
    83         -					local nc = self:brighten(f * 1.5)
    84         -					-- i don't know why the *1.5 is necessary. it's
    85         -					-- an ugly hack to work around broken math,
    86         -					-- because i'm too much of a mathtard to actually
    87         -					-- find what's wrong
    88         -					return nc
          122  +			to_hsl = function(self)
          123  +				-- Based on the algorithm in Computer Graphics: Principles and Practice, by
          124  +				-- James D. Foley et. al., 2nd ed., p. 595
          125  +				-- We need the rgb between 0 and 1
          126  +				local r = self.red/255
          127  +				local g = self.green/255
          128  +				local b = self.blue/255
          129  +				local max = math.max(r, g, b)
          130  +				local min = math.min(r, g, b)
          131  +				local luminosity = (max + min)/2
          132  +				local hue = 0
          133  +				local saturation = 0
          134  +				if max == min then
          135  +					-- Achromatic case, because r=g=b
          136  +					saturation = 0
          137  +					hue = 0 -- Undefined, so just replace w/ 0 for usability
    89    138   				else
    90         -					return self
          139  +					-- Chromatic case
          140  +					if luminosity <= 0.5 then
          141  +						saturation = (max - min)/(max + min)
          142  +					else
          143  +						saturation = (max - min)/(2 - max - min)
          144  +					end
          145  +					-- Next calculate the hue
          146  +					local delta = max - min
          147  +					if r == max then
          148  +						hue = (g - b)/delta
          149  +					elseif g == max then
          150  +						hue = 2 + (b - r)/delta
          151  +					else -- blue must be max, so no point in checking
          152  +						hue = 4 + (r - g)/delta
          153  +					end
          154  +					hue = hue * 60 -- degrees
          155  +					--hue = hue * (math.pi / 3) -- for hue in radians instead of degrees
          156  +					if hue < 0 then
          157  +						hue = hue + 2 * math.pi
          158  +					end
    91    159   				end
          160  +				-- print("r"..self.red.."g"..self.green.."b"..self.blue.." is h"..hue.."s"..saturation.."l"..luminosity)
          161  +				local temp = from_hsl({hue=hue,saturation=saturation,luminosity=luminosity},self.alpha)
          162  +				-- print("back is r"..temp.red.."g"..temp.green.."b"..temp.blue)
          163  +				return { hue = hue, saturation = saturation, luminosity = luminosity }
          164  +			end;
          165  +
          166  +			readable = function(self, target)
          167  +				target = target or 0.5
          168  +				local hsl = self:to_hsl()
          169  +				hsl.luminosity = target
          170  +				return from_hsl(hsl, self.alpha)
    92    171   			end;
    93    172   
    94    173   			bg = function(self, text) return
    95    174   				text .. minetest.get_background_escape_sequence(self:hex())
    96    175   			end;
    97    176   
    98    177   			fade = warp(function(new, fac)
    99    178   				new.alpha = math.min(255, (new.alpha or 255) * fac)
   100    179   			end);
   101    180   
   102         -			brighten = warp(function(new, fac)
   103         -				local lum = new:luminosity()
   104         -				local newlum = lum * fac
   105         -				local delta = (newlum - lum)
   106         -				new.red   = clip(new.red   + delta)
   107         -				new.blue  = clip(new.blue  + delta)
   108         -				new.green = clip(new.green + delta)
   109         -			end);
          181  +			brighten = function(self, fac)
          182  +				-- Use HSL to brighten
          183  +				-- To HSL
          184  +				local hsl = self:to_hsl()
          185  +				-- Do the calculation, clamp to 0-1 instead of the clamp fn
          186  +				hsl.luminosity = math.min(math.max(hsl.luminosity * fac, 0), 1)
          187  +				-- Turn back into RGB color
          188  +				local t = from_hsl(hsl, self.alpha)
          189  +				-- print("brighten is r"..t.red.."g"..t.green.."b"..t.blue)
          190  +				return from_hsl(hsl, self.alpha)
          191  +			end;
   110    192   
   111    193   			darken = warp(function(new, fac)
          194  +				-- TODO: is there any point to this being different than brighten? Probably especially not now.
   112    195   				new.red = clip(new.red - (new.red * fac))
   113    196   				new.blue = clip(new.blue - (new.blue * fac))
   114    197   				new.green = clip(new.green - (new.green * fac))
   115    198   			end);
   116    199   		}
   117    200   		if g == nil then
   118    201   			if type(r) == 'string' then

Modified lib/marshal.lua from [9b9bc45df4] to [9fd6c4419a].

    61     61   			end
    62     62   			return str
    63     63   		end;
    64     64   		dec = function(str)
    65     65   			local val = 0
    66     66   			for i = 0, bytes-1 do
    67     67   				local b = string.byte(str,bytes - i)
    68         -				val = (val * 0x100) + b
           68  +				val = (val * 0x100) + (b or 0)
    69     69   			end
    70     70   			if signed then
    71     71   				if val > spoint then val = 0 - (val - spoint) end
    72     72   			end
    73     73   			return val, string.sub(str, 1 + bytes)
    74     74   		end;
    75     75   	}

Modified lib/str.lua from [4468d8dfeb] to [e883d78392].

            1  +local sanitable = {
            2  +	['\xfe'] = '\xf0';
            3  +	['\1'] = '\xf1';
            4  +	['\2'] = '\xf2';
            5  +	['\3'] = '\xf3';
            6  +
            7  +	['\xf0'] = '\xfe';
            8  +	['\xf1'] = '\1';
            9  +	['\xf2'] = '\2';
           10  +	['\xf3'] = '\3';
           11  +}
     1     12   return {
     2     13   	capitalize = function(str)
     3     14   		return string.upper(string.sub(str, 1,1)) .. string.sub(str, 2)
     4     15   	end;
     5     16   
     6     17   	rand = function(min,max)
     7     18   		if not min then min = 16  end
................................................................................
    32     43   			str = string.sub(str, 2)
    33     44   		end
    34     45   		if string.sub(str, #str,#str) == ' ' then
    35     46   			str = string.sub(str, 1, #str - 1)
    36     47   		end
    37     48   		return str
    38     49   	end;
           50  +
           51  +	meta_armor = function(str,mark_struct)
           52  +		-- binary values stored in metadata need to be sanitized so
           53  +		-- they don't contain values that will disrupt parsing of the
           54  +		-- KV store, as minetest (stupidly) uses in-band signalling
           55  +		local sanitized = string.gsub(str, '[\xfe\1\2\3]', function(char)
           56  +			return '\xfe' .. sanitable[char]
           57  +		end)
           58  +		if sanitized ~= str and mark_struct then
           59  +			-- use different type code to mark struct headers for
           60  +			-- back-compat
           61  +			return string.gsub(sanitized,'^\xfe\xf0\x99','\xfe\x98')
           62  +		else return sanitized end
           63  +	end;
           64  +	meta_dearmor = function(str,cond)
           65  +		local dearmor = function(s)
           66  +			return string.gsub(s, '\xfe([\xf0\xf1\xf2\xf3])', function(char)
           67  +				return sanitable[char]
           68  +			end)
           69  +		end
           70  +		if cond then
           71  +			if string.sub(str,1,2) == '\xfe\x98' then
           72  +				return dearmor(string.gsub(str,'^\xfe\x98','\xfe\xf0\x99'))
           73  +			else return str end
           74  +		else return dearmor(str) end
           75  +	end;
    39     76   }

Modified lib/ui.lua from [6423ac3b4f] to [a001c55363].

    20     20   				else
    21     21   					c = l.color(dui.colors.neutral)
    22     22   				end
    23     23   
    24     24   				str = str .. '\n ' .. c:fmt('* ')
    25     25   
    26     26   				if prop.title then
    27         -					str = str .. c:brighten(1.5):fmt(prop.title) .. ': '
           27  +					str = str .. c:brighten(1.3):fmt(prop.title) .. ': '
    28     28   				end
    29     29   
    30     30   				local lines = minetest.wrap_text(prop.desc, 50, true)
    31     31   				str = str .. c:fmt(lines[1])
    32     32   				for i=2,#lines do
    33     33   					str = str .. '\n' .. string.rep(' ',5) .. c:fmt(lines[i])
    34     34   				end
    35     35   			end
    36     36   		end
    37     37   		return str
    38     38   	end;
    39     39   }

Added metallurgy-hot.lua version [3c9b60f14d].

            1  +-- alloying furnace
            2  +--
            3  +-- there are several kinds of alloy furnace, with varying
            4  +-- capabilities. the simplest can alloy two simple metals;
            5  +-- the most complex can alloy four high-grade metals such
            6  +-- as titanium, platinum, iridium, or levitanium.
            7  +--
            8  +-- furnace recipes follow a pattern: the number of crucibles
            9  +-- determines the input slots, the type of crucible determines
           10  +-- how hot the furnace can get, and various other components
           11  +-- (like a coolant circulator) can be added to allow the
           12  +-- creation of exotic metals.
           13  +--
           14  +-- alloy furnaces produce ingots that can later be melted down
           15  +-- again and cast into a mold to produce items of particular
           16  +-- shapes.
           17  +
           18  +-- there are four kinds of crucibles: clay, aluminum, platinum,
           19  +-- and duranium.  clay crucibles are made by molding clay into
           20  +-- the proper shape and then firing it in a furnace. others
           21  +-- are made by casting.
           22  +
           23  +local fragments_per_ingot = 4
           24  +
           25  +for _, c in pairs { 'clay', 'aluminum', 'platinum', 'duranium' } do
           26  +	minetest.register_craftitem('sorcery:crucible_' .. c, {
           27  +		description = sorcery.lib.str.capitalize(c .. ' crucible');
           28  +		inventory_image = 'sorcery_crucible_' .. c .. '.png';
           29  +	})
           30  +end
           31  +
           32  +minetest.register_craftitem('sorcery:crucible_clay_molding', {
           33  +	description = sorcery.lib.str.capitalize('Crucible molding');
           34  +	inventory_image = 'sorcery_crucible_clay_molding.png';
           35  +})
           36  +
           37  +minetest.register_craft {
           38  +	recipe = {
           39  +		{ 'default:clay_lump', '', 'default:clay_lump'};
           40  +		{ 'default:clay_lump', '', 'default:clay_lump'};
           41  +		{ 'default:clay_lump', 'default:clay_lump', 'default:clay_lump'};
           42  +	};
           43  +	output = 'sorcery:crucible_clay_molding';
           44  +}
           45  +minetest.register_craft {
           46  +	type = 'shapeless';
           47  +	recipe = { 'sorcery:crucible_clay_molding' };
           48  +	output = 'default:clay_lump 7';
           49  +}
           50  +minetest.register_craft {
           51  +	type = 'cooking';
           52  +	recipe = 'sorcery:crucible_clay_molding';
           53  +	cooktime = 40;
           54  +	output = 'sorcery:crucible_clay';
           55  +}
           56  +
           57  +local burn_layout = function(v)
           58  +	local layouts = {
           59  +		[1] = {w = 1, h = 1}; [2] = {w = 2, h = 1}; [3] = {w = 3, h = 1};
           60  +		[4] = {w = 2, h = 2}; [5] = {w = 3, h = 2}; [6] = {w = 3, h = 2};
           61  +		[7] = {w = 4, h = 2}; [8] = {w = 4, h = 2}; [9] = {w = 3, h = 3};
           62  +	}
           63  +	local inpos  = { x = 2.8, y = 1 }
           64  +	local insize = layouts[v.inp]
           65  +	local outpos = { x = 5.2, y = 2.4 }
           66  +	local outsize = layouts[v.out]
           67  +	local fuelpos = { x = 2.8, y = 3.4 }
           68  +	local fuelsize = layouts[v.fuel]
           69  +	return string.format([[
           70  +		list[context;input;%f,%f;%f,%f;]
           71  +		list[context;output;%f,%f;%f,%f;]
           72  +		list[context;fuel;%f,%f;%f,%f;]
           73  +		
           74  +		image[2.3,1.9;1,1;default_furnace_fire_bg.png^[lowpart:%u%%:default_furnace_fire_fg.png]
           75  +		image[3.2,1.9;1,1;gui_furnace_arrow_bg.png^[lowpart:%u%%:gui_furnace_arrow_fg.png^[transformR270]
           76  +
           77  +		listring[context;output] listring[current_player;main]
           78  +		listring[context;input]  listring[current_player;main]
           79  +		listring[context;fuel]   listring[current_player;main]
           80  +	]], 
           81  +
           82  +	--input
           83  +		inpos.x - insize.w/2, -- pos
           84  +		inpos.y - insize.h/2, 
           85  +		insize.w, insize.h, -- size
           86  +	--output
           87  +		outpos.x - outsize.w/2, -- pos
           88  +		outpos.y - outsize.h/2, 
           89  +		outsize.w, outsize.h, -- size
           90  +	--fuel
           91  +		fuelpos.x - fuelsize.w/2, -- pos
           92  +		fuelpos.y - fuelsize.h/2, 
           93  +		fuelsize.w, fuelsize.h, -- size
           94  +
           95  +		v.burn, v.prog
           96  +	)
           97  +end
           98  +
           99  +local kiln_formspec = function(kind, fuel_progress, cook_progress)
          100  +	return [[
          101  +		size[8,8]
          102  +		list[current_player;main;0,4.2;8,4]
          103  +		list[context;wax;0,2;1,1]
          104  +	]] .. burn_layout{
          105  +		inp = kind.size^2;
          106  +		out = kind.outsize;
          107  +		fuel = kind.fuelsize;
          108  +		burn = fuel_progress;
          109  +		prog = cook_progress;
          110  +	}
          111  +end
          112  +
          113  +local smelter_formspec = function(kind, fuel_progress, smelt_progress)
          114  +	return [[
          115  +		size[8,8]
          116  +		list[current_player;main;0,4.2;8,4]
          117  +	]] .. burn_layout {
          118  +		inp  = kind.size;
          119  +		out = kind.outsize;
          120  +		fuel = kind.fuelsize;
          121  +		burn = fuel_progress;
          122  +		prog = smelt_progress;
          123  +	}
          124  +end
          125  +
          126  +local find_recipe = function(inv)
          127  +	local mix = {}
          128  +	local count = 0
          129  +	for i=1,inv:get_size('input') do
          130  +		local m = inv:get_stack('input',i)
          131  +		if m:is_empty() then goto skip end
          132  +		local l = sorcery.data.metallookup[m:get_name()]
          133  +		if not l then return false end
          134  +		mix[l.id] = (mix[l.id] or 0) + l.value
          135  +		count = count + l.value
          136  +	::skip::end
          137  +	-- everything is metal, we've finished summing it up.
          138  +	-- let's see if the assembled items match the ratio
          139  +	-- specified in any of the smelting recipes.
          140  +	local matches = 0
          141  +	for _,rec in pairs(sorcery.data.alloys) do
          142  +		local fac = nil
          143  +		local meltpoint = 1
          144  +		if rec.metals == nil then goto skip_recipe end
          145  +		for metal, ratio in pairs(rec.metals) do
          146  +			if mix[metal] and mix[metal] % ratio == 0 then
          147  +				if fac then
          148  +					if mix[metal] / ratio ~= fac then goto skip_recipe end
          149  +				else fac = math.floor(mix[metal] / ratio) end
          150  +				local m = sorcery.data.metals[metal]
          151  +				if m.meltpoint then
          152  +					meltpoint = math.max(meltpoint, m.meltpoint) end
          153  +			else goto skip_recipe end
          154  +		end
          155  +		do return rec, count, fac, meltpoint end
          156  +	::skip_recipe::end
          157  +	return false
          158  +end
          159  +
          160  +local update_smelter = function(pos)
          161  +	local meta = minetest.get_meta(pos)
          162  +	local inv = meta:get_inventory()
          163  +	local proto = minetest.registered_nodes[minetest.get_node(pos).name]._proto
          164  +	local recipe, count, factor, meltpoint = find_recipe(inv)
          165  +	if recipe and proto.temp >= meltpoint then
          166  +		minetest.get_node_timer(pos):start(1)
          167  +	else
          168  +		meta:set_float('burntime',0)
          169  +	end
          170  +end
          171  +
          172  +local smelter_step = function(kind,active,pos,delta)
          173  +	local meta = minetest.get_meta(pos)
          174  +	local inv = meta:get_inventory()
          175  +	local recipe, count, factor = find_recipe(inv)
          176  +	local cooktime
          177  +	local elapsed = meta:get_float('burntime') + delta
          178  +	meta:set_float('burnleft',meta:get_float('burnleft') - delta)
          179  +	if (not active) and (not recipe) then return false end
          180  +	if meta:get_float('burnleft') <= 0 or not active then
          181  +		if recipe then
          182  +			local burn, frep = minetest.get_craft_result {
          183  +				method = 'fuel', width = 1;
          184  +				items = { inv:get_stack('fuel',1) };
          185  +			}
          186  +			if burn.time == 0 then goto nofuel end
          187  +			inv:set_stack('fuel', 1, frep.items[1])
          188  +			meta:set_float('burnleft',burn.time)
          189  +			meta:set_float('burnmax',burn.time)
          190  +			if not active then
          191  +				minetest.swap_node(pos,
          192  +					sorcery.lib.tbl.merge(minetest.get_node(pos), {
          193  +						name = kind.id .. '_active'
          194  +					}))
          195  +				active = true
          196  +			end
          197  +		else goto nofuel end
          198  +	end
          199  +
          200  +	if not recipe then goto update end
          201  +	
          202  +	cooktime = ((recipe.cooktime / fragments_per_ingot) * count) / factor
          203  +	if elapsed >= cooktime then
          204  +		elapsed = 0
          205  +		-- remove used items
          206  +		for i=1,inv:get_size('input') do
          207  +			local s = inv:get_stack('input',i)
          208  +			if s:is_empty() then goto skip end
          209  +			s:take_item(1) inv:set_stack('input',i,s)
          210  +		::skip::end
          211  +
          212  +		local outstack
          213  +		if count % fragments_per_ingot == 0 then
          214  +			outstack = ItemStack {
          215  +				name = sorcery.data.metals[recipe.output].ingot or 'sorcery:' .. recipe.output .. '_ingot';
          216  +				count = count / fragments_per_ingot;
          217  +			}
          218  +		else
          219  +			outstack = ItemStack {
          220  +				name = 'sorcery:fragment_' .. recipe.output;
          221  +				count = count;
          222  +			}
          223  +		end
          224  +
          225  +		local leftover = inv:add_item('output',outstack)
          226  +		if not leftover:is_empty() then
          227  +			minetest.add_item(pos, leftover)
          228  +		end
          229  +	end
          230  +
          231  +	::update::
          232  +		meta:set_float('burntime',elapsed)
          233  +		meta:set_string('formspec', smelter_formspec(kind,
          234  +			math.min(1, meta:get_float('burnleft') /
          235  +						meta:get_float('burnmax')
          236  +					) * 100, -- fuel
          237  +			(cooktime and math.min(1, elapsed / cooktime) * 100) or 0 -- smelt
          238  +		))
          239  +		do return active end
          240  +
          241  +	::nofuel::
          242  +		if active then
          243  +			minetest.swap_node(pos,
          244  +				sorcery.lib.tbl.merge(minetest.get_node(pos), { name = kind.id }))
          245  +		end
          246  +		meta:set_float('burnleft',0) -- just in case
          247  +	::noburn::
          248  +		meta:set_float('burntime',0) -- just in case
          249  +		meta:set_string('formspec', smelter_formspec(kind, 0, 0))
          250  +		return false
          251  +end
          252  +local register_kiln = function(kind)
          253  +	local box = {
          254  +		open = {
          255  +			type = 'fixed';
          256  +			fixed = {
          257  +				-0.5,-0.5,-0.5,
          258  +				 0.5, 1.6, 0.5
          259  +			};
          260  +		};
          261  +		closed = {
          262  +			type = 'fixed';
          263  +			fixed = {
          264  +				-0.5,-0.5,-0.5,
          265  +				 0.5, 0.7, 0.5
          266  +			};
          267  +		};
          268  +	}
          269  +	local id = 'sorcery:kiln_' .. kind.material
          270  +	local metal = sorcery.data.metals[kind.material]
          271  +	-- use some iffy heuristics to guess the texture
          272  +	local metaltex
          273  +	if kind.material == 'clay' then
          274  +		metaltex = 'default_brick.png';
          275  +	else
          276  +		metaltex = (metal.img and metal.img.block) or
          277  +			(metal.ingot and 'default_' .. kind.material .. '_block.png') or
          278  +			sorcery.lib.image('default_steel_block.png'):multiply(sorcery.lib.color(metal.tone)):render();
          279  +	end
          280  +	local tex = {
          281  +		open = {
          282  +			'default_furnace_front.png';
          283  +			metaltex;
          284  +			'default_copper_block.png';
          285  +			'default_stone.png';
          286  +		};
          287  +		closed = {
          288  +			'default_furnace_front_active.png';
          289  +			'default_copper_block.png';
          290  +			metaltex;
          291  +			'default_stone.png';
          292  +		};
          293  +	};
          294  +	for _,state in pairs{'open','closed'} do
          295  +		local id_closed = id .. '_closed'
          296  +		local id_current = (state == 'closed' and id_closed) or id
          297  +		local desc = (kind.temp_name and sorcery.lib.str.capitalize(kind.temp_name) .. ' kiln') or 'Kiln'
          298  +		minetest.register_node(id_current, {
          299  +			description = desc;
          300  +			drawtype = "mesh";
          301  +			mesh = 'sorcery-kiln-' .. state .. '.obj';
          302  +			drop = id;
          303  +			groups = {
          304  +				cracky = (state == 'open' and 2) or nil;
          305  +			};
          306  +			sunlight_propagates = true;
          307  +			paramtype1 = 'light';
          308  +			paramtype2 = 'facedir';
          309  +			selection_box = box[state];
          310  +			collision_box = box[state];
          311  +			tiles = tex[state];
          312  +			light_source = (state == 'closed' and 7) or 0;
          313  +			on_construct = function(pos)
          314  +				local meta = minetest.get_meta(pos)
          315  +				local inv = meta:get_inventory()
          316  +				inv:set_size('input', kind.size^2)
          317  +				inv:set_size('output', kind.outsize)
          318  +				inv:set_size('fuel', kind.fuelsize)
          319  +				inv:set_size('wax', 1)
          320  +
          321  +				meta:set_float('burnleft',0)
          322  +				meta:set_float('burnmax',0)
          323  +				meta:set_float('burntime',0)
          324  +
          325  +				meta:set_string('infotext',desc)
          326  +				meta:set_string('formspec',kiln_formspec(kind,0,0))
          327  +			end;
          328  +			on_timer = function(pos,delta)
          329  +			end;
          330  +
          331  +			on_metadata_inventory_put = function(pos)
          332  +				minetest.get_node_timer(pos):start(1) end;
          333  +			on_metadata_inventory_move = function(pos)
          334  +				minetest.get_node_timer(pos):start(1) end;
          335  +			on_metadata_inventory_take = function(pos)
          336  +				minetest.get_node_timer(pos):start(1) end;
          337  +		})
          338  +	end
          339  +	local ingot
          340  +	if kind.material == 'clay' then
          341  +		ingot = 'default:clay_brick';
          342  +	else
          343  +		ingot = metal.ingot or 'sorcery:' .. kind.material .. '_ingot'
          344  +	end
          345  +	minetest.register_craft {
          346  +		output = id;
          347  +		recipe = {
          348  +			{'default:copper_ingot', 'default:copper_ingot', 'default:copper_ingot'};
          349  +			{ingot,'',ingot};
          350  +			{ingot,'default:furnace',ingot};
          351  +		};
          352  +	}
          353  +end
          354  +
          355  +local register_smelter = function(kind)
          356  +	local recipe = {{},{};
          357  +		{'default:stone','default:furnace','default:stone'};
          358  +	} do
          359  +		local on = kind.crucible
          360  +		local ti = 'default:tin_ingot'
          361  +		local cu = 'default:copper_ingot'
          362  +		local crucmap = {
          363  +			[2] = { {cu,cu,cu}, {on,ti,on} };
          364  +			[3] = { {cu,on,cu}, {on,ti,on} };
          365  +			[4] = { {on,cu,on}, {on,ti,on} };
          366  +			[5] = { {on,cu,on}, {on,on,on} };
          367  +			[6] = { {on,on,on}, {on,on,on} };
          368  +		};
          369  +		for y=1,2 do recipe[y] = crucmap[kind.size][y] end
          370  +	end
          371  +	
          372  +	local desc = 'smelter';
          373  +	if kind.temp_name then desc = kind.temp_name .. ' ' .. desc end
          374  +	if kind.size_name then desc = kind.size_name .. ' ' .. desc end
          375  +	desc = sorcery.lib.str.capitalize(desc);
          376  +	local id = 'sorcery:smelter_' .. kind.material .. kind.size_name
          377  +	kind.id = id
          378  +	for _, active in pairs {false, true} do
          379  +		minetest.register_node((active and id .. '_active') or id, {
          380  +			_proto = kind;
          381  +			description = desc;
          382  +			drop = id;
          383  +			groups = {
          384  +				cracky = (active and 2) or nil;
          385  +			};
          386  +			paramtype2 = 'facedir';
          387  +			light_source = (active and 9) or 0;
          388  +			on_construct = function(pos)
          389  +				local meta = minetest.get_meta(pos)
          390  +				local inv = meta:get_inventory()
          391  +				inv:set_size('input',kind.size)
          392  +				inv:set_size('output',kind.outsize)
          393  +				inv:set_size('fuel',kind.fuelsize)
          394  +				meta:set_string('infotext', desc)
          395  +				meta:set_string('formspec', smelter_formspec(kind, 0, 0))
          396  +				meta:set_float('burnleft',0)
          397  +				meta:set_float('burnmax',0)
          398  +				meta:set_float('burntime',0)
          399  +			end;
          400  +			on_metadata_inventory_put = update_smelter;
          401  +			on_metadata_inventory_move = update_smelter;
          402  +			on_metadata_inventory_take = update_smelter;
          403  +			on_timer = function(pos,delta) return smelter_step(kind, active, pos, delta) end;
          404  +			-- allow_metadata_inventory_put = function(pos, listname, index, stack, player)
          405  +			-- end;
          406  +			tiles = {
          407  +				'sorcery_smelter_top_' .. tostring(kind.size) .. '.png';
          408  +				'sorcery_smelter_bottom.png';
          409  +				'sorcery_smelter_side.png';
          410  +				'sorcery_smelter_side.png';
          411  +				'sorcery_smelter_side.png';
          412  +				'sorcery_smelter_front' .. ((active and '_hot') or '') .. '.png';
          413  +			};
          414  +		})
          415  +	end
          416  +	minetest.register_craft {
          417  +		recipe = recipe;
          418  +		output = id;
          419  +	}
          420  +end
          421  +
          422  +for _, t in pairs {
          423  +	{1, nil, 'clay'};
          424  +	{2, 'hot','aluminum'};
          425  +	{3, 'volcanic','platinum'};
          426  +	{4, 'stellar','duridium'};
          427  +	{5, 'nova','impervium'};
          428  +} do register_kiln {
          429  +		temp = t[1], temp_name = t[2];
          430  +		material = t[3];
          431  +		size = 3, outsize = 4, fuelsize = 1; -- future-proofing
          432  +	}
          433  +
          434  +	for _, s in pairs {
          435  +		{2, 'small'};
          436  +		{3, 'large'};
          437  +		{4, 'grand'};
          438  +	} do register_smelter {
          439  +		size = s[1], size_name = s[2];
          440  +		temp = t[1], temp_name = t[2];
          441  +		material = t[3];
          442  +		crucible = 'sorcery:crucible_' .. t[3];
          443  +		outsize = 4, fuelsize = 1; -- future-proofing
          444  +	} end
          445  +end

Added models/sorcery-kiln-closed.obj version [fb9010b25d].

            1  +# Blender v2.82 (sub 7) OBJ File: 'kiln.blend'
            2  +# www.blender.org
            3  +mtllib sorcery-kiln-closed.mtl
            4  +o furnace-front_Cube.002
            5  +v -0.288540 -0.410913 -0.500000
            6  +v 0.288540 -0.410913 -0.500000
            7  +v 0.288540 0.152026 -0.500000
            8  +v -0.288540 0.152026 -0.500000
            9  +vt 0.000000 0.987748
           10  +vt 0.000000 0.012252
           11  +vt 1.000000 0.012252
           12  +vt 1.000000 0.987748
           13  +vn 0.0000 0.0000 1.0000
           14  +g furnace-front_Cube.002_furnace.001
           15  +usemtl furnace.001
           16  +s off
           17  +f 4/1/1 1/2/1 2/3/1 3/4/1
           18  +o bronze.001_bronze
           19  +v 0.500000 0.593635 -0.500000
           20  +v 0.500000 0.593635 0.500000
           21  +v -0.500000 0.593635 -0.500000
           22  +v -0.500000 0.593635 0.500000
           23  +v 0.500000 0.365016 -0.500000
           24  +v -0.500000 0.365016 0.500000
           25  +v 0.500000 0.365016 0.500000
           26  +v -0.500000 0.365016 -0.500000
           27  +vt 1.000000 0.385690
           28  +vt 1.000000 0.614310
           29  +vt 0.000000 0.614310
           30  +vt 0.000000 0.385690
           31  +vt 1.000000 0.614310
           32  +vt 1.000000 0.385690
           33  +vt 1.000000 0.385690
           34  +vt 1.000000 0.614310
           35  +vt 0.000000 0.614310
           36  +vt 0.000000 0.385690
           37  +vt 0.000000 0.385690
           38  +vt 0.000000 0.614310
           39  +vt 0.000000 1.000000
           40  +vt 1.000000 1.000000
           41  +vt 1.000000 0.000000
           42  +vt 0.000000 0.000000
           43  +vt 1.000000 0.000000
           44  +vt 1.000000 1.000000
           45  +vt 0.000000 1.000000
           46  +vt 0.000000 0.000000
           47  +vn 0.0000 0.0000 1.0000
           48  +vn -1.0000 0.0000 0.0000
           49  +vn 1.0000 0.0000 0.0000
           50  +vn 0.0000 0.0000 -1.0000
           51  +vn -0.0000 -1.0000 0.0000
           52  +vn 0.0000 1.0000 0.0000
           53  +g bronze.001_bronze_bronze
           54  +usemtl bronze
           55  +s off
           56  +f 11/5/2 6/6/2 8/7/2 10/8/2
           57  +f 10/8/3 8/7/3 7/9/3 12/10/3
           58  +f 9/11/4 5/12/4 6/13/4 11/14/4
           59  +f 12/15/5 7/16/5 5/12/5 9/11/5
           60  +f 12/17/6 9/18/6 11/19/6 10/20/6
           61  +f 6/21/7 5/22/7 7/23/7 8/24/7
           62  +o metal
           63  +v 0.500000 -0.500000 -0.500000
           64  +v 0.500000 -0.500000 0.500000
           65  +v -0.500000 -0.500000 -0.500000
           66  +v -0.500000 -0.500000 0.500000
           67  +v 0.500000 -0.410913 -0.500000
           68  +v -0.500000 -0.410913 0.500000
           69  +v 0.500000 -0.410913 0.500000
           70  +v -0.500000 -0.410913 -0.500000
           71  +v -0.288539 -0.410913 -0.452614
           72  +v 0.288539 -0.410913 -0.452614
           73  +v -0.288540 -0.410913 -0.500000
           74  +v 0.288540 -0.410913 -0.500000
           75  +v 0.500000 0.593635 -0.500000
           76  +v 0.500000 0.593635 0.500000
           77  +v -0.500000 0.593635 -0.500000
           78  +v -0.500000 0.593635 0.500000
           79  +v 0.500000 0.692600 -0.500000
           80  +v -0.500000 0.593635 0.500000
           81  +v 0.500000 0.593635 -0.500000
           82  +v -0.500000 0.593635 -0.500000
           83  +v 0.500000 0.692600 0.500000
           84  +v -0.500000 0.692600 0.500000
           85  +v -0.500000 0.692600 -0.500000
           86  +vt 0.000000 1.000000
           87  +vt 1.000000 1.000000
           88  +vt 1.000000 0.000000
           89  +vt 0.000000 0.000000
           90  +vt 0.000000 0.440608
           91  +vt 0.000000 0.529696
           92  +vt 0.333333 0.529696
           93  +vt 0.666667 0.529696
           94  +vt 1.000000 0.529696
           95  +vt 1.000000 0.440608
           96  +vt 0.000000 0.529696
           97  +vt 0.000000 0.440608
           98  +vt 0.000000 0.440608
           99  +vt 0.000000 0.529696
          100  +vt 1.000000 0.529696
          101  +vt 1.000000 0.440608
          102  +vt 1.000000 0.440608
          103  +vt 1.000000 0.529696
          104  +vt 1.000000 1.000000
          105  +vt 1.000000 0.000000
          106  +vt 0.000000 0.000000
          107  +vt 0.000000 1.000000
          108  +vt 1.000000 1.000000
          109  +vt 0.000000 1.000000
          110  +vt 0.000000 0.000000
          111  +vt 1.000000 0.000000
          112  +vt 1.000000 0.450518
          113  +vt 0.000000 0.450518
          114  +vt 0.000000 0.549482
          115  +vt 1.000000 0.549482
          116  +vt 0.000000 0.450518
          117  +vt 1.000000 0.450518
          118  +vt 1.000000 0.549482
          119  +vt 0.000000 0.549482
          120  +vt 0.000000 0.450518
          121  +vt 0.000000 0.549482
          122  +vt 1.000000 0.450518
          123  +vt 1.000000 0.549482
          124  +vn 0.0000 -1.0000 0.0000
          125  +vn 0.0000 0.0000 -1.0000
          126  +vn 1.0000 0.0000 0.0000
          127  +vn -1.0000 0.0000 0.0000
          128  +vn 0.0000 0.0000 1.0000
          129  +vn 0.0000 1.0000 -0.0000
          130  +g metal_metal_metal.001
          131  +usemtl metal.001
          132  +s off
          133  +f 15/25/8 13/26/8 14/27/8 16/28/8
          134  +f 15/29/9 20/30/9 23/31/9 24/32/9 17/33/9 13/34/9
          135  +f 13/34/10 17/33/10 19/35/10 14/36/10
          136  +f 16/37/11 18/38/11 20/39/11 15/40/11
          137  +f 14/41/12 19/42/12 18/38/12 16/37/12
          138  +f 17/33/13 20/39/13 18/38/13 19/42/13
          139  +f 31/43/8 26/44/8 30/45/8 32/46/8
          140  +f 29/47/13 35/48/13 34/49/13 33/50/13
          141  +f 31/51/9 32/52/9 35/53/9 29/54/9
          142  +f 30/55/12 26/56/12 33/57/12 34/58/12
          143  +f 26/59/10 31/51/10 29/54/10 33/60/10
          144  +f 32/61/11 30/55/11 34/58/11 35/62/11
          145  +l 21 22
          146  +l 28 32
          147  +l 27 25
          148  +o stone_Cube.008
          149  +v 0.452614 0.365016 -0.452614
          150  +v 0.452614 -0.410913 -0.452614
          151  +v -0.452614 -0.410913 0.452614
          152  +v -0.452614 0.365016 0.452614
          153  +v 0.452614 0.365016 0.452614
          154  +v 0.452614 -0.410913 0.452614
          155  +v -0.452614 0.365016 -0.452614
          156  +v -0.452614 -0.410913 -0.452614
          157  +v 0.452614 0.152026 -0.452614
          158  +v -0.452614 0.152026 -0.452614
          159  +v -0.288539 -0.410913 -0.452614
          160  +v 0.288539 -0.410913 -0.452614
          161  +v -0.288539 0.152026 -0.452614
          162  +v 0.288539 0.152026 -0.452614
          163  +v -0.288540 -0.410913 -0.500000
          164  +v 0.288540 -0.410913 -0.500000
          165  +v 0.288540 0.152026 -0.500000
          166  +v -0.288540 0.152026 -0.500000
          167  +vt 0.000000 0.718717
          168  +vt 0.000000 0.990645
          169  +vt 1.000000 0.990645
          170  +vt 1.000000 0.718717
          171  +vt 0.818747 0.718717
          172  +vt 0.181253 0.718717
          173  +vt 1.000000 -0.000000
          174  +vt 1.000000 0.723899
          175  +vt 1.000000 0.997788
          176  +vt 0.000000 0.997788
          177  +vt 0.000000 -0.000000
          178  +vt 0.000000 0.000000
          179  +vt 0.000000 0.995330
          180  +vt 1.000000 0.995330
          181  +vt 1.000000 0.722116
          182  +vt 1.000000 -0.000000
          183  +vt 1.000000 0.000000
          184  +vt 1.000000 0.987517
          185  +vt 0.000000 0.987518
          186  +vt 0.818747 0.000000
          187  +vt 0.000000 0.000000
          188  +vt 0.181253 0.000000
          189  +vt 0.458944 -0.150330
          190  +vt 0.541056 -0.150330
          191  +vt 0.541056 0.825165
          192  +vt 0.458944 0.825165
          193  +vt 0.000003 0.458944
          194  +vt 0.000000 0.541056
          195  +vt 1.000000 0.541056
          196  +vt 0.999997 0.458944
          197  +vt 0.458944 0.825165
          198  +vt 0.541056 0.825165
          199  +vt 0.541056 -0.150330
          200  +vt 0.458944 -0.150330
          201  +vn 0.0000 0.0000 -1.0000
          202  +vn 1.0000 0.0000 0.0000
          203  +vn -1.0000 0.0000 0.0000
          204  +vn -0.0000 0.0000 1.0000
          205  +vn 0.0000 -1.0000 -0.0000
          206  +vn 0.0000 1.0000 -0.0000
          207  +g stone_Cube.008_stone.001
          208  +usemtl stone.001
          209  +s off
          210  +f 45/63/14 42/64/14 36/65/14 44/66/14 49/67/14 48/68/14
          211  +f 37/69/15 44/70/15 36/71/15 40/72/15 41/73/15
          212  +f 38/74/16 39/75/16 42/76/16 45/77/16 43/78/16
          213  +f 41/79/17 40/80/17 39/81/17 38/74/17
          214  +f 47/82/14 49/67/14 44/66/14 37/69/14
          215  +f 43/83/14 45/63/14 48/68/14 46/84/14
          216  +f 46/84/14 48/68/14 49/67/14 47/82/14
          217  +f 46/85/15 50/86/15 53/87/15 48/88/15
          218  +f 48/89/18 53/90/18 52/91/18 49/92/18
          219  +f 49/93/16 52/94/16 51/95/16 47/96/16
          220  +f 47/82/18 37/69/18 41/79/18 38/74/18 43/83/18 46/84/18
          221  +f 42/76/19 39/75/19 40/80/19 36/71/19

Added models/sorcery-kiln-open.obj version [14e1653fbc].

            1  +# Blender v2.82 (sub 7) OBJ File: 'kiln.blend'
            2  +# www.blender.org
            3  +mtllib sorcery-kiln-open.mtl
            4  +o furnace
            5  +v -0.288540 -0.410913 -0.500000
            6  +v 0.288540 -0.410913 -0.500000
            7  +v 0.288540 0.152026 -0.500000
            8  +v -0.288540 0.152026 -0.500000
            9  +vt 0.000000 0.987748
           10  +vt 0.000000 0.012252
           11  +vt 1.000000 0.012252
           12  +vt 1.000000 0.987748
           13  +vn 0.0000 0.0000 1.0000
           14  +g furnace_furnace_furnace.002
           15  +usemtl furnace.002
           16  +s off
           17  +f 4/1/1 1/2/1 2/3/1 3/4/1
           18  +o metal.001
           19  +v 0.508325 1.576141 0.354696
           20  +v 0.508325 0.586871 0.500792
           21  +v -0.491675 1.576141 0.354696
           22  +v -0.491675 0.586871 0.500792
           23  +v 0.508325 1.590600 0.452599
           24  +v -0.491675 0.586871 0.500792
           25  +v 0.508325 1.576141 0.354696
           26  +v -0.491675 1.576141 0.354696
           27  +v 0.508325 0.601329 0.598694
           28  +v -0.491675 0.601329 0.598694
           29  +v -0.491675 1.590600 0.452599
           30  +v 0.508325 -0.506765 -0.499208
           31  +v 0.508325 -0.506765 0.500792
           32  +v -0.491675 -0.506765 -0.499208
           33  +v -0.491675 -0.506765 0.500792
           34  +v 0.508325 -0.417677 -0.499208
           35  +v -0.491675 -0.417677 0.500792
           36  +v 0.508325 -0.417677 0.500792
           37  +v -0.491675 -0.417677 -0.499208
           38  +v 0.460939 -0.417677 -0.451822
           39  +v -0.444289 -0.417677 0.453406
           40  +v 0.460939 -0.417677 0.453406
           41  +v -0.444289 -0.417677 -0.451822
           42  +v 0.344038 0.275759 0.336506
           43  +v -0.327389 0.275759 0.336506
           44  +v 0.344038 0.275759 -0.334922
           45  +v -0.327389 0.275759 -0.334922
           46  +v 0.344038 -0.302932 0.336506
           47  +v -0.327389 -0.302932 0.336506
           48  +v 0.344038 -0.302932 -0.334922
           49  +v -0.327389 -0.302932 -0.334922
           50  +v 0.344038 0.497017 0.336506
           51  +v 0.344038 0.441703 0.336506
           52  +v 0.344038 0.386388 0.336506
           53  +v 0.344038 0.331074 0.336506
           54  +v -0.327389 0.497017 -0.334922
           55  +v -0.327389 0.441703 -0.334922
           56  +v -0.327389 0.386388 -0.334921
           57  +v -0.327389 0.331074 -0.334922
           58  +v -0.327389 0.331074 0.336506
           59  +v -0.327389 0.386388 0.336506
           60  +v -0.327389 0.441703 0.336506
           61  +v -0.327389 0.497017 0.336506
           62  +v 0.344038 0.331074 -0.334922
           63  +v 0.344038 0.386388 -0.334921
           64  +v 0.344038 0.441703 -0.334922
           65  +v 0.344038 0.497017 -0.334922
           66  +v 0.381991 0.275759 0.374458
           67  +v -0.365341 0.275759 0.374458
           68  +v 0.381991 0.275759 -0.372874
           69  +v -0.365341 0.275759 -0.372874
           70  +v 0.381991 0.331074 0.374458
           71  +v -0.365341 0.331074 -0.372874
           72  +v -0.365341 0.497017 0.374458
           73  +v -0.365341 0.552332 0.374458
           74  +v 0.381991 0.497017 -0.372874
           75  +v 0.381991 0.552332 -0.372874
           76  +v -0.365341 0.552332 -0.372874
           77  +v 0.381991 0.552332 0.374458
           78  +v 0.381991 0.497017 0.374458
           79  +v 0.381991 0.441703 0.374458
           80  +v 0.381991 0.386388 0.374458
           81  +v -0.365341 0.497017 -0.372874
           82  +v -0.365341 0.441703 -0.372874
           83  +v -0.365341 0.386388 -0.372874
           84  +v -0.365341 0.331074 0.374458
           85  +v -0.365341 0.386388 0.374458
           86  +v -0.365341 0.441703 0.374458
           87  +v 0.381991 0.331074 -0.372874
           88  +v 0.381991 0.386388 -0.372874
           89  +v 0.381991 0.441703 -0.372874
           90  +v -0.280214 -0.417677 -0.451822
           91  +v 0.296863 -0.417677 -0.451822
           92  +v -0.280215 -0.417677 -0.499208
           93  +v 0.296865 -0.417677 -0.499208
           94  +v -0.378077 0.552332 0.387194
           95  +v 0.394726 0.552332 -0.385610
           96  +v -0.378077 0.552332 -0.385610
           97  +v 0.394726 0.552332 0.387194
           98  +v -0.378077 -0.323989 0.387194
           99  +v 0.394726 -0.323989 -0.385610
          100  +v -0.378077 -0.323989 -0.385610
          101  +v 0.394726 -0.323989 0.387194
          102  +vt 1.000000 1.000000
          103  +vt 1.000000 0.000000
          104  +vt 0.000000 0.000000
          105  +vt 0.000000 1.000000
          106  +vt 1.000000 1.000000
          107  +vt 0.000000 1.000000
          108  +vt 0.000000 0.000000
          109  +vt 1.000000 0.000000
          110  +vt 1.000000 0.450518
          111  +vt 0.000000 0.450518
          112  +vt 0.000000 0.549482
          113  +vt 1.000000 0.549482
          114  +vt 0.000000 0.450518
          115  +vt 1.000000 0.450518
          116  +vt 1.000000 0.549482
          117  +vt 0.000000 0.549482
          118  +vt 0.000000 0.450518
          119  +vt 0.000000 0.549482
          120  +vt 1.000000 0.450518
          121  +vt 1.000000 0.549482
          122  +vt 0.000000 1.000000
          123  +vt 1.000000 1.000000
          124  +vt 1.000000 0.000000
          125  +vt 0.000000 0.000000
          126  +vt 0.000000 0.440608
          127  +vt 0.000000 0.529696
          128  +vt 0.333333 0.529696
          129  +vt 0.666667 0.529696
          130  +vt 1.000000 0.529696
          131  +vt 1.000000 0.440608
          132  +vt 0.000000 0.529696
          133  +vt 0.000000 0.440608
          134  +vt 1.000000 1.000000
          135  +vt 0.666667 1.000000
          136  +vt 0.333333 1.000000
          137  +vt 0.000000 1.000000
          138  +vt 0.047386 0.952614
          139  +vt 0.349129 0.952614
          140  +vt 0.650871 0.952614
          141  +vt 0.952614 0.952614
          142  +vt 0.000000 0.440608
          143  +vt 0.000000 0.529696
          144  +vt 1.000000 0.529696
          145  +vt 1.000000 0.440608
          146  +vt 1.000000 0.000000
          147  +vt 0.952614 0.047386
          148  +vt 1.000000 0.440608
          149  +vt 1.000000 0.529696
          150  +vt 0.000000 0.000000
          151  +vt 0.047386 0.047386
          152  +vt 0.949216 0.949216
          153  +vt 0.050784 0.949216
          154  +vt 0.000000 1.000000
          155  +vt 1.000000 1.000000
          156  +vt 1.000000 1.000000
          157  +vt 0.000000 1.000000
          158  +vt 0.000000 0.000000
          159  +vt 1.000000 0.000000
          160  +vt 0.050784 0.050784
          161  +vt 0.000000 0.000000
          162  +vt 0.050784 0.949216
          163  +vt 0.949216 0.949216
          164  +vt 1.000000 1.000000
          165  +vt 0.000000 1.000000
          166  +vt -0.000000 0.000000
          167  +vt 1.000000 0.000000
          168  +vt 1.000000 1.000000
          169  +vt 0.000000 1.000000
          170  +vt 0.000000 1.000000
          171  +vt 0.000000 0.000000
          172  +vt 1.000000 1.000000
          173  +vt 0.000000 1.000000
          174  +vt 0.000000 0.000000
          175  +vt 1.000000 1.000000
          176  +vt 1.000000 0.000000
          177  +vt 0.949216 0.611024
          178  +vt 0.050784 0.611024
          179  +vt 0.050784 0.537008
          180  +vt 0.949216 0.537008
          181  +vt 0.050784 0.050784
          182  +vt 0.000000 0.000000
          183  +vt 0.949216 0.462992
          184  +vt 0.050784 0.462992
          185  +vt 0.050784 0.388976
          186  +vt 0.949216 0.388976
          187  +vt 0.949216 0.949216
          188  +vt 0.050784 0.949216
          189  +vt 0.000000 1.000000
          190  +vt 1.000000 1.000000
          191  +vt 0.050784 0.611024
          192  +vt 0.949216 0.611024
          193  +vt 0.949216 0.537008
          194  +vt 0.050784 0.537008
          195  +vt 0.050784 0.050784
          196  +vt 0.000000 0.000000
          197  +vt 0.050784 0.462992
          198  +vt 0.949216 0.462992
          199  +vt 0.949216 0.388976
          200  +vt 0.050784 0.388976
          201  +vt 0.050784 0.949216
          202  +vt 0.949216 0.949216
          203  +vt 1.000000 1.000000
          204  +vt 0.000000 1.000000
          205  +vt 0.050784 0.611024
          206  +vt 0.050784 0.537008
          207  +vt 0.949216 0.050784
          208  +vt 1.000000 0.000000
          209  +vt 0.050784 0.462992
          210  +vt 0.050784 0.388976
          211  +vt 0.050784 0.050784
          212  +vt 0.000000 0.000000
          213  +vt 0.949216 0.611024
          214  +vt 0.949216 0.537008
          215  +vt 0.050784 0.949216
          216  +vt 0.949216 0.949216
          217  +vt 1.000000 1.000000
          218  +vt 0.000000 1.000000
          219  +vt 0.949216 0.462992
          220  +vt 0.949216 0.388976
          221  +vt 0.000000 0.388976
          222  +vt 1.000000 0.388976
          223  +vt 1.000000 0.314960
          224  +vt 0.000000 0.314960
          225  +vt 1.000000 0.388976
          226  +vt 0.000000 0.388976
          227  +vt 0.000000 0.314960
          228  +vt 1.000000 0.314960
          229  +vt 0.000000 0.388976
          230  +vt 0.000000 0.314960
          231  +vt 1.000000 0.388976
          232  +vt 1.000000 0.314960
          233  +vt 1.000000 0.685040
          234  +vt 0.000000 0.685040
          235  +vt 0.000000 0.611024
          236  +vt 1.000000 0.611024
          237  +vt 1.000000 0.537008
          238  +vt 0.000000 0.537008
          239  +vt 0.000000 0.462992
          240  +vt 1.000000 0.462992
          241  +vt 0.000000 0.685040
          242  +vt 1.000000 0.685040
          243  +vt 1.000000 0.611024
          244  +vt 0.000000 0.611024
          245  +vt 0.000000 0.537008
          246  +vt 1.000000 0.537008
          247  +vt 1.000000 0.462992
          248  +vt 0.000000 0.462992
          249  +vt 0.000000 0.685040
          250  +vt 0.000000 0.611024
          251  +vt 0.000000 0.537008
          252  +vt 0.000000 0.462992
          253  +vt 1.000000 0.685040
          254  +vt 1.000000 0.611024
          255  +vt 1.000000 0.537008
          256  +vt 1.000000 0.462992
          257  +vt 0.949216 0.050784
          258  +vt 1.000000 0.000000
          259  +vt 0.949216 0.050784
          260  +vt 0.050784 0.050784
          261  +vt 0.000000 0.000000
          262  +vt 1.000000 0.000000
          263  +vt 0.949216 0.050784
          264  +vt 1.000000 0.000000
          265  +vt 0.949216 0.050784
          266  +vt 1.000000 0.000000
          267  +vt 1.000000 0.685040
          268  +vt 0.000000 0.685040
          269  +vt 1.000000 0.685040
          270  +vt 0.000000 0.685040
          271  +vt 1.000000 0.685040
          272  +vt 0.000000 0.685040
          273  +vt 1.000000 0.685040
          274  +vt 0.000000 0.685040
          275  +vt 1.000000 0.685040
          276  +vt 0.000000 0.685040
          277  +vt 1.000000 0.685040
          278  +vt 0.000000 0.685040
          279  +vn 0.0000 -0.1461 -0.9893
          280  +vn 0.0000 0.1461 0.9893
          281  +vn 0.0000 0.9893 -0.1461
          282  +vn 0.0000 -0.9893 0.1461
          283  +vn 1.0000 0.0000 0.0000
          284  +vn -1.0000 0.0000 0.0000
          285  +vn 0.0000 -1.0000 0.0000
          286  +vn 0.0000 0.0000 -1.0000
          287  +vn 0.0000 1.0000 0.0000
          288  +vn 0.0000 -0.0000 1.0000
          289  +g metal.001_metal.001_metal.002
          290  +usemtl metal.002
          291  +s off
          292  +f 11/5/2 6/6/2 10/7/2 12/8/2
          293  +f 9/9/3 15/10/3 14/11/3 13/12/3
          294  +f 11/13/4 12/14/4 15/15/4 9/16/4
          295  +f 10/17/5 6/18/5 13/19/5 14/20/5
          296  +f 6/21/6 11/13/6 9/16/6 13/22/6
          297  +f 12/23/7 10/17/7 14/20/7 15/24/7
          298  +f 18/25/8 16/26/8 17/27/8 19/28/8
          299  +f 18/29/9 23/30/9 78/31/9 79/32/9 20/33/9 16/34/9
          300  +f 16/34/6 20/33/6 22/35/6 17/36/6
          301  +f 20/37/10 79/38/10 78/39/10 23/40/10 27/41/10 76/42/10 77/43/10 24/44/10
          302  +f 19/45/7 21/46/7 23/47/7 18/48/7
          303  +f 22/49/10 20/37/10 24/44/10 26/50/10
          304  +f 17/51/11 22/52/11 21/46/11 19/45/11
          305  +f 23/40/10 21/53/10 25/54/10 27/41/10
          306  +f 21/53/10 22/49/10 26/50/10 25/54/10
          307  +f 48/55/8 43/56/8 57/57/8 73/58/8
          308  +f 30/59/11 31/60/11 35/61/11 34/62/11
          309  +f 43/56/8 44/63/8 70/64/8 57/57/8
          310  +f 42/65/10 49/66/10 74/67/10 69/68/10
          311  +f 34/69/10 35/70/10 33/71/10 32/72/10
          312  +f 28/73/7 30/59/7 34/62/7 32/74/7
          313  +f 31/75/6 29/76/6 33/77/6 35/70/6
          314  +f 29/76/9 28/78/9 32/79/9 33/77/9
          315  +f 40/80/6 47/81/6 46/82/6 41/83/6
          316  +f 45/84/10 42/65/10 69/68/10 71/85/10
          317  +f 42/86/6 45/87/6 44/88/6 43/89/6
          318  +f 50/90/8 41/91/8 68/92/8 75/93/8
          319  +f 36/94/7 51/95/7 50/96/7 37/97/7
          320  +f 41/91/8 46/98/8 72/99/8 68/92/8
          321  +f 38/100/7 49/101/7 48/102/7 39/103/7
          322  +f 40/104/10 51/105/10 60/106/10 67/107/10
          323  +f 51/95/11 40/108/11 41/109/11 50/96/11
          324  +f 44/63/8 39/110/8 56/111/8 70/64/8
          325  +f 49/101/11 42/112/11 43/113/11 48/102/11
          326  +f 47/114/10 40/104/10 67/107/10 58/115/10
          327  +f 47/81/9 36/116/9 37/117/9 46/82/9
          328  +f 31/118/10 30/119/10 54/120/10 55/121/10
          329  +f 45/87/9 38/122/9 39/123/9 44/88/9
          330  +f 70/124/9 56/125/9 52/126/9 53/127/9
          331  +f 73/128/11 57/129/11 55/130/11 54/131/11
          332  +f 56/132/7 73/128/7 54/131/7 52/133/7
          333  +f 57/134/6 70/124/6 53/127/6 55/135/6
          334  +f 62/136/6 59/137/6 58/138/6 67/139/6
          335  +f 68/140/6 72/141/6 71/142/6 69/143/6
          336  +f 63/144/7 61/145/7 60/146/7 64/147/7
          337  +f 65/148/7 75/149/7 74/150/7 66/151/7
          338  +f 61/145/11 62/152/11 67/153/11 60/146/11
          339  +f 75/149/11 68/154/11 69/155/11 74/150/11
          340  +f 59/137/9 63/156/9 64/157/9 58/138/9
          341  +f 72/141/9 65/158/9 66/159/9 71/142/9
          342  +f 51/105/10 36/160/10 64/161/10 60/106/10
          343  +f 28/162/10 29/163/10 53/164/10 52/165/10
          344  +f 36/160/10 47/114/10 58/115/10 64/161/10
          345  +f 37/166/8 50/90/8 75/93/8 65/167/8
          346  +f 30/119/10 28/162/10 52/165/10 54/120/10
          347  +f 46/98/8 37/166/8 65/167/8 72/99/8
          348  +f 49/66/10 38/168/10 66/169/10 74/67/10
          349  +f 29/163/10 31/118/10 55/121/10 53/164/10
          350  +f 38/168/10 45/84/10 71/85/10 66/169/10
          351  +f 39/110/8 48/55/8 73/58/8 56/111/8
          352  +f 62/152/10 61/145/10 81/170/10 82/171/10
          353  +f 59/137/10 62/136/10 82/172/10 80/173/10
          354  +f 63/156/10 59/137/10 80/173/10 83/174/10
          355  +f 61/145/10 63/144/10 83/175/10 81/170/10
          356  +f 82/171/9 81/170/9 85/176/9 86/177/9
          357  +f 80/173/7 82/172/7 86/178/7 84/179/7
          358  +f 83/174/11 80/173/11 84/179/11 87/180/11
          359  +f 81/170/6 83/175/6 87/181/6 85/176/6
          360  +f 84/179/8 86/177/8 85/176/8 87/180/8
          361  +l 8 12
          362  +l 7 5
          363  +o bronze_Cube.006
          364  +v 0.500000 0.593635 -0.500000
          365  +v 0.500000 0.593635 0.500000
          366  +v -0.500000 0.593635 -0.500000
          367  +v -0.500000 0.593635 0.500000
          368  +v 0.500000 0.365016 -0.500000
          369  +v -0.500000 0.365016 0.500000
          370  +v 0.500000 0.365016 0.500000
          371  +v -0.500000 0.365016 -0.500000
          372  +v 0.452614 0.365016 -0.452614
          373  +v -0.452614 0.365016 0.452614
          374  +v 0.452614 0.365016 0.452614
          375  +v -0.452614 0.365016 -0.452614
          376  +v 0.335714 0.593635 0.335714
          377  +v -0.335714 0.593635 0.335714
          378  +v 0.335714 0.593635 -0.335714
          379  +v -0.335714 0.593635 -0.335714
          380  +v 0.335714 0.559096 0.335714
          381  +v -0.335714 0.559096 0.335714
          382  +v -0.335714 0.559096 -0.335714
          383  +v 0.335714 0.559096 -0.335714
          384  +v -0.373666 0.559096 0.373666
          385  +v 0.373666 0.559096 -0.373666
          386  +v -0.373666 0.559096 -0.373666
          387  +v 0.373666 0.559096 0.373666
          388  +vt 0.000000 1.000000
          389  +vt 0.000000 0.000000
          390  +vt 0.164286 0.164286
          391  +vt 0.164286 0.835714
          392  +vt 1.000000 0.385690
          393  +vt 1.000000 0.614310
          394  +vt 0.000000 0.614310
          395  +vt 0.000000 0.385690
          396  +vt 1.000000 0.614310
          397  +vt 1.000000 0.385690
          398  +vt 1.000000 0.385690
          399  +vt 1.000000 0.614310
          400  +vt 0.000000 0.614310
          401  +vt 0.000000 0.385690
          402  +vt 0.000000 0.385690
          403  +vt 0.000000 0.614310
          404  +vt 1.000000 0.000000
          405  +vt 0.000000 0.000000
          406  +vt 0.047386 0.047386
          407  +vt 0.952614 0.047386
          408  +vt 0.000000 1.000000
          409  +vt 0.047386 0.952614
          410  +vt 1.000000 1.000000
          411  +vt 0.952614 0.952614
          412  +vt 1.000000 0.000000
          413  +vt 1.000000 1.000000
          414  +vt 0.835714 0.835714
          415  +vt 0.835714 0.164286
          416  +vt 0.949216 0.050784
          417  +vt 0.949216 0.949216
          418  +vt 1.000000 1.000000
          419  +vt 1.000000 0.000000
          420  +vt 0.625000 0.250000
          421  +vt 0.625000 0.000000
          422  +vt 0.625000 0.000000
          423  +vt 0.625000 0.250000
          424  +vt 0.625000 0.750000
          425  +vt 0.625000 0.500000
          426  +vt 0.625000 0.500000
          427  +vt 0.625000 0.750000
          428  +vt 0.625000 1.000000
          429  +vt 0.625000 1.000000
          430  +vt 0.050784 0.949216
          431  +vt 0.050784 0.050784
          432  +vt 0.000000 0.000000
          433  +vt 0.000000 1.000000
          434  +vn 0.0000 1.0000 0.0000
          435  +vn 0.0000 0.0000 1.0000
          436  +vn -1.0000 0.0000 0.0000
          437  +vn 1.0000 0.0000 0.0000
          438  +vn 0.0000 0.0000 -1.0000
          439  +vn -0.0000 -1.0000 -0.0000
          440  +vn 0.0000 -0.3768 0.9263
          441  +vn -0.9263 -0.3768 0.0000
          442  +vn 0.0000 -0.3768 -0.9263
          443  +vn 0.9263 -0.3768 0.0000
          444  +g bronze_Cube.006_bronze.002
          445  +usemtl bronze.002
          446  +s off
          447  +f 90/182/12 91/183/12 101/184/12 103/185/12
          448  +f 94/186/13 89/187/13 91/188/13 93/189/13
          449  +f 93/189/14 91/188/14 90/190/14 95/191/14
          450  +f 92/192/15 88/193/15 89/194/15 94/195/15
          451  +f 95/196/16 90/197/16 88/193/16 92/192/16
          452  +f 94/198/17 93/199/17 97/200/17 98/201/17
          453  +f 93/199/17 95/202/17 99/203/17 97/200/17
          454  +f 92/204/17 94/198/17 98/201/17 96/205/17
          455  +f 95/202/17 92/204/17 96/205/17 99/203/17
          456  +f 89/206/12 88/207/12 102/208/12 100/209/12
          457  +f 88/207/12 90/182/12 103/185/12 102/208/12
          458  +f 91/183/12 89/206/12 100/209/12 101/184/12
          459  +f 104/210/17 107/211/17 109/212/17 111/213/17
          460  +f 103/214/15 101/215/15 105/216/15 106/217/15
          461  +f 100/218/14 102/219/14 107/220/14 104/221/14
          462  +f 102/219/13 103/214/13 106/217/13 107/220/13
          463  +f 101/222/16 100/218/16 104/221/16 105/223/16
          464  +f 106/224/17 105/225/17 108/226/17 110/227/17
          465  +f 107/211/17 106/224/17 110/227/17 109/212/17
          466  +f 105/225/17 104/210/17 111/213/17 108/226/17
          467  +f 96/205/18 109/212/18 110/227/18 99/203/18
          468  +f 98/201/19 111/213/19 109/212/19 96/205/19
          469  +f 97/200/20 108/226/20 111/213/20 98/201/20
          470  +f 99/203/21 110/227/21 108/226/21 97/200/21
          471  +o stone.001_Cube.009
          472  +v 0.452614 0.365016 -0.452614
          473  +v 0.452614 -0.410913 -0.452614
          474  +v -0.452614 -0.410913 0.452614
          475  +v -0.452614 0.365016 0.452614
          476  +v 0.452614 0.365016 0.452614
          477  +v 0.452614 -0.410913 0.452614
          478  +v -0.452614 0.365016 -0.452614
          479  +v -0.452614 -0.410913 -0.452614
          480  +v 0.452614 0.152026 -0.452614
          481  +v -0.452614 0.152026 -0.452614
          482  +v -0.288539 -0.410913 -0.452614
          483  +v 0.288539 -0.410913 -0.452614
          484  +v -0.288539 0.152026 -0.452614
          485  +v 0.288539 0.152026 -0.452614
          486  +v -0.288540 -0.410913 -0.500000
          487  +v 0.288540 -0.410913 -0.500000
          488  +v 0.288540 0.152026 -0.500000
          489  +v -0.288540 0.152026 -0.500000
          490  +v 0.412127 0.365016 -0.420224
          491  +v 0.412127 -0.410913 -0.420224
          492  +v -0.412127 -0.410913 0.404029
          493  +v -0.428808 0.361504 0.401848
          494  +v 0.412127 0.365016 0.404029
          495  +v 0.412127 -0.410913 0.404029
          496  +v -0.428808 0.361503 -0.422405
          497  +v -0.412127 -0.410913 -0.420224
          498  +v -0.262728 -0.410913 -0.420224
          499  +v 0.262728 -0.410913 -0.420224
          500  +vt 0.000000 0.718717
          501  +vt 0.000000 0.990645
          502  +vt 1.000000 0.990645
          503  +vt 1.000000 0.718717
          504  +vt 0.818747 0.718717
          505  +vt 0.181253 0.718717
          506  +vt 1.000000 -0.000000
          507  +vt 1.000000 0.723899
          508  +vt 1.000000 0.997788
          509  +vt 0.000000 0.997788
          510  +vt 0.000000 -0.000000
          511  +vt 0.000000 0.000000
          512  +vt 0.000000 0.995330
          513  +vt 1.000000 0.995330
          514  +vt 1.000000 0.722116
          515  +vt 1.000000 -0.000000
          516  +vt 1.000000 0.000000
          517  +vt 1.000000 0.987517
          518  +vt 0.000000 0.987518
          519  +vt 0.818747 0.000000
          520  +vt 0.000000 0.000000
          521  +vt 0.181253 0.000000
          522  +vt 0.458944 -0.150330
          523  +vt 0.541056 -0.150330
          524  +vt 0.541056 0.825165
          525  +vt 0.458944 0.825165
          526  +vt 0.000003 0.458944
          527  +vt 0.000000 0.541056
          528  +vt 1.000000 0.541056
          529  +vt 0.999997 0.458944
          530  +vt 0.458944 0.825165
          531  +vt 0.541056 0.825165
          532  +vt 0.541056 -0.150330
          533  +vt 0.458944 -0.150330
          534  +vt 0.000000 0.000000
          535  +vt 1.000000 0.000000
          536  +vt 1.000000 0.997788
          537  +vt 0.000000 0.997788
          538  +vt 1.000000 0.000000
          539  +vt 0.818747 0.000000
          540  +vt 1.000000 0.987517
          541  +vt 0.000000 0.987518
          542  +vt 1.000000 -0.000000
          543  +vt 0.181253 0.000000
          544  +vt 0.000000 0.000000
          545  +vt 0.000000 0.990645
          546  +vt 1.000000 0.990645
          547  +vt 0.000000 0.995330
          548  +vt 1.000000 0.995330
          549  +vt 0.000000 -0.000000
          550  +vt 0.000000 0.000000
          551  +vt 0.000000 0.000000
          552  +vn 0.0000 0.0000 -1.0000
          553  +vn 1.0000 0.0000 0.0000
          554  +vn -1.0000 0.0000 0.0000
          555  +vn -0.0000 0.0000 1.0000
          556  +vn 0.0000 -1.0000 -0.0000
          557  +vn 0.0000 1.0000 0.0000
          558  +vn -0.0020 0.9994 -0.0350
          559  +vn -0.0021 0.9985 0.0555
          560  +vn 0.1460 0.9893 -0.0000
          561  +vn 0.0000 0.0028 1.0000
          562  +vn -0.0016 0.0014 1.0000
          563  +vn 0.0013 -0.0014 -1.0000
          564  +vn 0.9998 0.0216 -0.0000
          565  +g stone.001_Cube.009_stone.002
          566  +usemtl stone.002
          567  +s off
          568  +f 121/228/22 118/229/22 112/230/22 120/231/22 125/232/22 124/233/22
          569  +f 113/234/23 120/235/23 112/236/23 116/237/23 117/238/23
          570  +f 114/239/24 115/240/24 118/241/24 121/242/24 119/243/24
          571  +f 117/244/25 116/245/25 115/246/25 114/239/25
          572  +f 123/247/22 125/232/22 120/231/22 113/234/22
          573  +f 119/248/22 121/228/22 124/233/22 122/249/22
          574  +f 122/249/22 124/233/22 125/232/22 123/247/22
          575  +f 122/250/23 126/251/23 129/252/23 124/253/23
          576  +f 124/254/26 129/255/26 128/256/26 125/257/26
          577  +f 125/258/24 128/259/24 127/260/24 123/261/24
          578  +f 117/244/26 114/239/26 132/262/26 135/263/26
          579  +f 116/237/27 112/236/27 130/264/27 134/265/27
          580  +f 123/247/26 113/234/26 131/266/26 139/267/26
          581  +f 115/246/28 116/245/28 134/268/28 133/269/28
          582  +f 114/239/26 119/243/26 137/270/26 132/262/26
          583  +f 119/248/26 122/249/26 138/271/26 137/272/26
          584  +f 112/230/29 118/229/29 136/273/29 130/274/29
          585  +f 118/241/30 115/240/30 133/275/30 136/276/30
          586  +f 122/249/26 123/247/26 139/267/26 138/271/26
          587  +f 113/234/26 117/238/26 135/277/26 131/266/26
          588  +f 138/271/31 136/278/31 137/272/31
          589  +f 130/279/25 139/267/25 131/266/25
          590  +f 130/279/32 136/278/32 138/271/32 139/267/32
          591  +f 135/277/24 134/265/24 130/264/24 131/266/24
          592  +f 132/262/33 133/269/33 134/268/33 135/277/33
          593  +f 137/272/34 136/278/34 133/275/34 132/262/34

Modified ores.lua from [45b89ab03b] to [a381d20de4].

     1      1   local fragments_per_ingot = 4
     2      2   
     3      3   minetest.register_lbm {
     4         -	label = "delete duranium ore";
     5         -	name = "sorcery:delete_duranium_ore";
            4  +	label = "delete duranium ore again";
            5  +	name = "sorcery:delete_duranium_ore_again";
     6      6   	nodenames = {'sorcery:stone_with_duranium'};
     7      7   	action = function(pos,node)
     8      8   		minetest.set_node(pos, {name = 'default:stone'})
     9      9   	end
    10     10   }
    11     11   
    12     12   sorcery.data.alloys = {}
           13  +sorcery.data.kilnrecs = {}
    13     14   sorcery.data.metallookup = {
    14     15   	-- compat bullshit
    15     16   	['moreores:silver_ingot'] = {
    16     17   		id = 'silver'; data = sorcery.data.metals.silver;
    17     18   		value = fragments_per_ingot;
    18     19   	};
    19     20   	['moreores:silver_block'] = {
................................................................................
    39     40   	};
    40     41   }
    41     42   
    42     43   local tools, armors = sorcery.matreg.tools, sorcery.matreg.armors
    43     44   for name, metal in pairs(sorcery.data.metals) do
    44     45   	local ingot = metal.ingot or 'sorcery:' .. name .. '_ingot'
    45     46   	local block = metal.block or 'sorcery:' .. name .. '_block'
           47  +	local screw = 'sorcery:screw_' .. name
    46     48   	local fragment = 'sorcery:fragment_' .. name
    47     49   	if not metal.no_tools then for _,t in pairs(tools) do
    48     50   		sorcery.matreg.lookup[(metal.items and metal.items[t]) or ('sorcery:' .. t .. '_' .. name)] = {
    49     51   			metal = true;
    50     52   			id = name; data = metal;
    51     53   		}
    52     54   	end end
    53     55   	if not metal.no_armor then for _,a in pairs(armors) do
    54         -		sorcery.matreg.lookup[(metal.items and metal.items[t]) or ('sorcery:' .. a .. '_' .. name)] = {
           56  +		sorcery.matreg.lookup[(metal.items and metal.items[a]) or ('sorcery:' .. a .. '_' .. name)] = {
    55     57   			metal = true;
    56     58   			id = name; data = metal;
    57     59   		}
    58     60   	end end
    59     61   	sorcery.data.metallookup[ingot] = {
    60     62   		id = name; data = metal;
    61     63   		value = fragments_per_ingot;
................................................................................
    63     65   	sorcery.data.metallookup[block] = {
    64     66   		id = name; data = metal;
    65     67   		value = fragments_per_ingot * 9;
    66     68   	}
    67     69   	sorcery.data.metallookup[fragment] = {
    68     70   		id = name; data = metal;
    69     71   		value = 1;
           72  +	}
           73  +	sorcery.data.metallookup[screw] = {
           74  +		id = name; data = metal;
           75  +		value = 0; -- prevent use in smelting
           76  +	}
           77  +	minetest.register_craftitem(screw, {
           78  +		description = sorcery.lib.str.capitalize(name) .. ' screw';
           79  +		inventory_image = sorcery.lib.image('sorcery_screw.png'):multiply(sorcery.lib.color(metal.tone)):render();
           80  +	})
           81  +	-- TODO: replace crafting recipe with kiln recipe
           82  +	minetest.register_craft {
           83  +		output = screw.. ' 4';
           84  +		recipe = {
           85  +			{fragment,fragment,fragment};
           86  +			{'',      fragment,''};
           87  +			{'',      fragment,''};
           88  +		};
    70     89   	}
    71     90   	if not sorcery.compat.defp(ingot) then
    72     91   		-- TODO: remove instant_ores dependency
    73     92   		instant_ores.register_metal {
    74     93   			name = 'sorcery:' .. name;
    75     94   			description = sorcery.lib.str.capitalize(name);
    76     95   			color = sorcery.lib.color(metal.tone):hex() .. ':' .. ((metal.alpha and tostring(metal.alpha)) or '45');

Modified potions.lua from [676a9b76a9] to [fc0276bae8].

    28     28   			newstack[1]:get_meta():from_table(meta)
    29     29   		end;
    30     30   		walkable = false;
    31     31   		selection_box = {
    32     32   			type = "fixed",
    33     33   			fixed = {-0.25, -0.5, -0.25, 0.25, 0.3, 0.25}
    34     34   		};
           35  +		on_construct = function(pos)
           36  +			minetest.get_meta(pos):set_string('infotext',label)
           37  +		end;
    35     38   		sounds = default.node_sound_glass_defaults();
    36     39   	}
    37     40   	if extra then for k,v in pairs(extra) do node[k] = v end end
    38     41   	if not node.groups then node.groups = {} end
    39     42   	node.groups.dig_immediate = 3;
    40     43   	node.groups.attached_node = 1;
    41     44   	node.groups.vessel = 1;

Deleted smelter.lua version [07ab2b0675].

     1         --- alloying furnace
     2         ---
     3         --- there are several kinds of alloy furnace, with varying
     4         --- capabilities. the simplest can alloy two simple metals;
     5         --- the most complex can alloy four high-grade metals such
     6         --- as titanium, platinum, iridium, or levitanium.
     7         ---
     8         --- furnace recipes follow a pattern: the number of crucibles
     9         --- determines the input slots, the type of crucible determines
    10         --- how hot the furnace can get, and various other components
    11         --- (like a coolant circulator) can be added to allow the
    12         --- creation of exotic metals.
    13         ---
    14         --- alloy furnaces produce ingots that can later be melted down
    15         --- again and cast into a mold to produce items of particular
    16         --- shapes.
    17         -
    18         --- there are four kinds of crucibles: clay, aluminum, platinum,
    19         --- and duranium.  clay crucibles are made by molding clay into
    20         --- the proper shape and then firing it in a furnace. others
    21         --- are made by casting.
    22         -
    23         -local fragments_per_ingot = 4
    24         -
    25         -for _, c in pairs { 'clay', 'aluminum', 'platinum', 'duranium' } do
    26         -	minetest.register_craftitem('sorcery:crucible_' .. c, {
    27         -		description = sorcery.lib.str.capitalize(c .. ' crucible');
    28         -		inventory_image = 'sorcery_crucible_' .. c .. '.png';
    29         -	})
    30         -end
    31         -
    32         -minetest.register_craftitem('sorcery:crucible_clay_molding', {
    33         -	description = sorcery.lib.str.capitalize('Crucible molding');
    34         -	inventory_image = 'sorcery_crucible_clay_molding.png';
    35         -})
    36         -
    37         -minetest.register_craft {
    38         -	recipe = {
    39         -		{ 'default:clay_lump', '', 'default:clay_lump'};
    40         -		{ 'default:clay_lump', '', 'default:clay_lump'};
    41         -		{ 'default:clay_lump', 'default:clay_lump', 'default:clay_lump'};
    42         -	};
    43         -	output = 'sorcery:crucible_clay_molding';
    44         -}
    45         -minetest.register_craft {
    46         -	type = 'shapeless';
    47         -	recipe = { 'sorcery:crucible_clay_molding' };
    48         -	output = 'default:clay_lump 7';
    49         -}
    50         -minetest.register_craft {
    51         -	type = 'cooking';
    52         -	recipe = 'sorcery:crucible_clay_molding';
    53         -	cooktime = 40;
    54         -	output = 'sorcery:crucible_clay';
    55         -}
    56         -
    57         -local smelter_formspec = function(kind, fuel_progress, smelt_progress)
    58         -	local layouts = {
    59         -		[1] = {w = 1, h = 1}; [2] = {w = 2, h = 1}; [3] = {w = 3, h = 1};
    60         -		[4] = {w = 2, h = 2}; [5] = {w = 3, h = 2}; [6] = {w = 3, h = 2};
    61         -	}
    62         -	local inpos  = { x = 2.8, y = 1 }
    63         -	local insize = layouts[kind.size]
    64         -	local outpos = { x = 5.2, y = 2.4 }
    65         -	local outsize = layouts[kind.outsize]
    66         -	local fuelpos = { x = 2.8, y = 3.4 }
    67         -	local fuelsize = layouts[kind.fuelsize]
    68         -	return string.format([[
    69         -		size[8,8]
    70         -		list[context;input;%f,%f;%f,%f;]
    71         -		list[context;output;%f,%f;%f,%f;]
    72         -		list[context;fuel;%f,%f;%f,%f;]
    73         -		list[current_player;main;0,4.2;8,4]
    74         -
    75         -		image[2.3,1.9;1,1;default_furnace_fire_bg.png^[lowpart:%u%%:default_furnace_fire_fg.png]
    76         -		image[3.2,1.9;1,1;gui_furnace_arrow_bg.png^[lowpart:%u%%:gui_furnace_arrow_fg.png^[transformR270]
    77         -
    78         -		listring[context;output] listring[current_player;main]
    79         -		listring[context;input]  listring[current_player;main]
    80         -		listring[context;fuel]   listring[current_player;main]
    81         -	]],
    82         -	--input
    83         -		inpos.x - insize.w/2, -- pos
    84         -		inpos.y - insize.h/2, 
    85         -		insize.w, insize.h, -- size
    86         -	--output
    87         -		outpos.x - outsize.w/2, -- pos
    88         -		outpos.y - outsize.h/2, 
    89         -		outsize.w, outsize.h, -- size
    90         -	--fuel
    91         -		fuelpos.x - fuelsize.w/2, -- pos
    92         -		fuelpos.y - fuelsize.h/2, 
    93         -		fuelsize.w, fuelsize.h, -- size
    94         -
    95         -		fuel_progress, smelt_progress
    96         -	)
    97         -end
    98         -
    99         -local find_recipe = function(inv)
   100         -	local mix = {}
   101         -	local count = 0
   102         -	for i=1,inv:get_size('input') do
   103         -		local m = inv:get_stack('input',i)
   104         -		if m:is_empty() then goto skip end
   105         -		local l = sorcery.data.metallookup[m:get_name()]
   106         -		if not l then return false end
   107         -		mix[l.id] = (mix[l.id] or 0) + l.value
   108         -		count = count + l.value
   109         -	::skip::end
   110         -	-- everything is metal, we've finished summing it up.
   111         -	-- let's see if the assembled items match the ratio
   112         -	-- specified in any of the smelting recipes.
   113         -	local matches = 0
   114         -	for _,rec in pairs(sorcery.data.alloys) do
   115         -		local fac = nil
   116         -		local meltpoint = 1
   117         -		if rec.metals == nil then goto skip_recipe end
   118         -		for metal, ratio in pairs(rec.metals) do
   119         -			if mix[metal] and mix[metal] % ratio == 0 then
   120         -				if fac then
   121         -					if mix[metal] / ratio ~= fac then goto skip_recipe end
   122         -				else fac = math.floor(mix[metal] / ratio) end
   123         -				local m = sorcery.data.metals[metal]
   124         -				if m.meltpoint then
   125         -					meltpoint = math.max(meltpoint, m.meltpoint) end
   126         -			else goto skip_recipe end
   127         -		end
   128         -		do return rec, count, fac, meltpoint end
   129         -	::skip_recipe::end
   130         -	return false
   131         -end
   132         -
   133         -local update_smelter = function(pos)
   134         -	local meta = minetest.get_meta(pos)
   135         -	local inv = meta:get_inventory()
   136         -	local proto = minetest.registered_nodes[minetest.get_node(pos).name]._proto
   137         -	local recipe, count, factor, meltpoint = find_recipe(inv)
   138         -	if recipe and proto.temp >= meltpoint then
   139         -		minetest.get_node_timer(pos):start(1)
   140         -	else
   141         -		meta:set_float('burntime',0)
   142         -	end
   143         -end
   144         -
   145         -local smelter_step = function(kind,active,pos,delta)
   146         -	local meta = minetest.get_meta(pos)
   147         -	local inv = meta:get_inventory()
   148         -	local recipe, count, factor = find_recipe(inv)
   149         -	local cooktime
   150         -	local elapsed = meta:get_float('burntime') + delta
   151         -	meta:set_float('burnleft',meta:get_float('burnleft') - delta)
   152         -	if (not active) and (not recipe) then return false end
   153         -	if meta:get_float('burnleft') <= 0 or not active then
   154         -		if recipe then
   155         -			local burn, frep = minetest.get_craft_result {
   156         -				method = 'fuel', width = 1;
   157         -				items = { inv:get_stack('fuel',1) };
   158         -			}
   159         -			if burn.time == 0 then goto nofuel end
   160         -			inv:set_stack('fuel', 1, frep.items[1])
   161         -			meta:set_float('burnleft',burn.time)
   162         -			meta:set_float('burnmax',burn.time)
   163         -			if not active then
   164         -				minetest.swap_node(pos,
   165         -					sorcery.lib.tbl.merge(minetest.get_node(pos), {
   166         -						name = kind.id .. '_active'
   167         -					}))
   168         -				active = true
   169         -			end
   170         -		else goto nofuel end
   171         -	end
   172         -
   173         -	if not recipe then goto update end
   174         -	
   175         -	cooktime = ((recipe.cooktime / fragments_per_ingot) * count) / factor
   176         -	if elapsed >= cooktime then
   177         -		elapsed = 0
   178         -		-- remove used items
   179         -		for i=1,inv:get_size('input') do
   180         -			local s = inv:get_stack('input',i)
   181         -			if s:is_empty() then goto skip end
   182         -			s:take_item(1) inv:set_stack('input',i,s)
   183         -		::skip::end
   184         -
   185         -		local outstack
   186         -		if count % fragments_per_ingot == 0 then
   187         -			outstack = ItemStack {
   188         -				name = sorcery.data.metals[recipe.output].ingot or 'sorcery:' .. recipe.output .. '_ingot';
   189         -				count = count / fragments_per_ingot;
   190         -			}
   191         -		else
   192         -			outstack = ItemStack {
   193         -				name = 'sorcery:fragment_' .. recipe.output;
   194         -				count = count;
   195         -			}
   196         -		end
   197         -
   198         -		local leftover = inv:add_item('output',outstack)
   199         -		if not leftover:is_empty() then
   200         -			minetest.add_item(pos, leftover)
   201         -		end
   202         -	end
   203         -
   204         -	::update::
   205         -		meta:set_float('burntime',elapsed)
   206         -		meta:set_string('formspec', smelter_formspec(kind,
   207         -			math.min(1, meta:get_float('burnleft') /
   208         -						meta:get_float('burnmax')
   209         -					) * 100, -- fuel
   210         -			(cooktime and math.min(1, elapsed / cooktime) * 100) or 0 -- smelt
   211         -		))
   212         -		do return active end
   213         -
   214         -	::nofuel::
   215         -		if active then
   216         -			minetest.swap_node(pos,
   217         -				sorcery.lib.tbl.merge(minetest.get_node(pos), { name = kind.id }))
   218         -		end
   219         -		meta:set_float('burnleft',0) -- just in case
   220         -	::noburn::
   221         -		meta:set_float('burntime',0) -- just in case
   222         -		meta:set_string('formspec', smelter_formspec(kind, 0, 0))
   223         -		return false
   224         -end
   225         -
   226         -local register_smelter = function(kind)
   227         -	local recipe = {{},{};
   228         -		{'default:stone','default:furnace','default:stone'};
   229         -	} do
   230         -		local on = kind.crucible
   231         -		local ti = 'default:tin_ingot'
   232         -		local cu = 'default:copper_ingot'
   233         -		local crucmap = {
   234         -			[2] = { {cu,cu,cu}, {on,ti,on} };
   235         -			[3] = { {cu,on,cu}, {on,ti,on} };
   236         -			[4] = { {on,cu,on}, {on,ti,on} };
   237         -			[5] = { {on,cu,on}, {on,on,on} };
   238         -			[6] = { {on,on,on}, {on,on,on} };
   239         -		};
   240         -		for y=1,2 do recipe[y] = crucmap[kind.size][y] end
   241         -	end
   242         -	
   243         -	local desc = 'smelter';
   244         -	if kind.temp_name then desc = kind.temp_name .. ' ' .. desc end
   245         -	if kind.size_name then desc = kind.size_name .. ' ' .. desc end
   246         -	desc = sorcery.lib.str.capitalize(desc);
   247         -	local id = 'sorcery:smelter_' .. kind.material .. kind.size_name
   248         -	kind.id = id
   249         -	for _, active in pairs {false, true} do
   250         -		minetest.register_node((active and id .. '_active') or id, {
   251         -			_proto = kind;
   252         -			description = desc;
   253         -			drop = id;
   254         -			groups = { cracky = 2; };
   255         -			paramtype2 = 'facedir';
   256         -			light_source = (active and 9) or 0;
   257         -			on_construct = function(pos)
   258         -				local meta = minetest.get_meta(pos)
   259         -				local inv = meta:get_inventory()
   260         -				inv:set_size('input',kind.size)
   261         -				inv:set_size('output',kind.outsize)
   262         -				inv:set_size('fuel',kind.fuelsize)
   263         -				meta:set_string('infotext', desc)
   264         -				meta:set_string('formspec', smelter_formspec(kind, 0, 0))
   265         -				meta:set_float('burnleft',0)
   266         -				meta:set_float('burnmax',0)
   267         -				meta:set_float('burntime',0)
   268         -			end;
   269         -			on_metadata_inventory_put = update_smelter;
   270         -			on_metadata_inventory_move = update_smelter;
   271         -			on_metadata_inventory_take = update_smelter;
   272         -			on_timer = function(pos,delta) return smelter_step(kind, active, pos, delta) end;
   273         -			-- allow_metadata_inventory_put = function(pos, listname, index, stack, player)
   274         -			-- end;
   275         -			tiles = {
   276         -				'sorcery_smelter_top_' .. tostring(kind.size) .. '.png';
   277         -				'sorcery_smelter_bottom.png';
   278         -				'sorcery_smelter_side.png';
   279         -				'sorcery_smelter_side.png';
   280         -				'sorcery_smelter_side.png';
   281         -				'sorcery_smelter_front' .. ((active and '_hot') or '') .. '.png';
   282         -			};
   283         -		})
   284         -	end
   285         -	minetest.register_craft {
   286         -		recipe = recipe;
   287         -		output = id;
   288         -	}
   289         -end
   290         -
   291         -for _, s in pairs {
   292         -	{2, 'small'};
   293         -	{3, 'large'};
   294         -	{4, 'grand'};
   295         -} do for _, t in pairs {
   296         -	{1, nil, 'clay'};
   297         -	{2, 'hot','aluminum'};
   298         -	{3, 'volcanic','platinum'};
   299         -	{4, 'stellar','duranium'};
   300         -	{5, 'nova','impervium'};
   301         -} do
   302         -	register_smelter {
   303         -		size = s[1], size_name = s[2];
   304         -		temp = t[1], temp_name = t[2];
   305         -		material = t[3];
   306         -		crucible = 'sorcery:crucible_' .. t[3];
   307         -		outsize = 4, fuelsize = 1;
   308         -	}
   309         -end end

Added test/common.lua version [7f2fc6a53d].

            1  +local qv = 0
            2  +local testn = 0
            3  +test = function(name,expr)
            4  +	testn = testn + 1
            5  +	print(string.format('% 3u ',testn) ..
            6  +		(expr and '\x1b[32mPASS\x1b[m' or '\x1b[31mFAIL\x1b[m') ..
            7  +		'  ' .. name)
            8  +	if not expr and qv == 0 then qv = testn end
            9  +end
           10  +report = function() os.exit(qv) end
           11  +
           12  +hexdump = function(s)
           13  +	local hexlines, charlines = {},{}
           14  +	for i=1,#s do
           15  +		local line = math.floor((i-1)/16) + 1
           16  +		hexlines[line] = (hexlines[line] or '') .. string.format("%02x ",string.byte(s, i))
           17  +		charlines[line] = (charlines[line] or '') .. ' ' .. string.gsub(string.sub(s, i, i), '[^%g ]', '\x1b[;35m·\x1b[36;1m') .. ' '
           18  +	end
           19  +	local str = ''
           20  +	for i=1,#hexlines do
           21  +		str = str .. '\x1b[1;36m' .. charlines[i] .. '\x1b[m\n' .. hexlines[i] .. '\n'
           22  +	end
           23  +	return str
           24  +end
           25  +
           26  +function dump(o)
           27  +	if type(o) == "table" then
           28  +		local str = ''
           29  +		for k,p in pairs(o) do
           30  +			str = str .. (k .. ' = {' .. dump(p) ..'}\n')
           31  +		end
           32  +		return str
           33  +	else
           34  +		return tostring(o)
           35  +	end
           36  +end
           37  +

Added test/encode.lua version [765ecd04d6].

            1  +dofile'test/common.lua'
            2  +local m = dofile'lib/marshal.lua'
            3  +local s = dofile'lib/str.lua'
            4  +local ench_t = m.g.struct {
            5  +	id = m.t.str;
            6  +	slot = m.t.u8;
            7  +	boost = m.t.u8;
            8  +	reliability = m.t.u8;
            9  +}
           10  +local pack, unpack = m.transcoder {
           11  +	spells = m.g.array(8, ench_t);
           12  +	energy = m.t.u16;
           13  +}
           14  +
           15  +local packed = pack{
           16  +	energy = 1550;
           17  +	spells = {
           18  +		{ slot = 0; boost = 15; reliability = 100; id = 'dowse' };
           19  +		{ slot = 1; boost = 3; reliability = 0; id = 'e' };
           20  +	};
           21  +}
           22  +
           23  +print('\x1b[1munarmored\x1b[m\n' .. hexdump(packed))
           24  +print('\x1b[1marmored\x1b[m\n' .. hexdump(s.meta_armor(packed)))
           25  +print('\x1b[1marmored (struct mode)\x1b[m\n' .. hexdump(s.meta_armor(packed,true)))
           26  +print('\x1b[1mdearmored\x1b[m\n' .. hexdump(s.meta_dearmor(s.meta_armor(packed))))
           27  +print('\x1b[1mdearmored (struct mode)\x1b[m\n' .. hexdump(s.meta_dearmor(s.meta_armor(packed,true),true)))
           28  +
           29  +test('dearmor(armor(x)) == x',s.meta_dearmor(s.meta_armor(packed)) == packed)
           30  +test('struct_dearmor(struct_armor(x)) == x',s.meta_dearmor(s.meta_armor(packed,true),true) == packed)
           31  +test('struct_dearmor("string") == "string"',s.meta_dearmor('string',true) == 'string')
           32  +report()

Modified test/marshal.lua from [f9d9d412d5] to [c7cd728357].

            1  +dofile'test/common.lua'
     1      2   local m = dofile"lib/marshal.lua"
     2      3   local car_t = m.g.struct {
     3      4   	brand = m.t.str;
     4      5   	year = m.t.u16;
     5      6   }
     6      7   local pack, unpack = m.transcoder {
     7      8   	version = m.t.u16;
................................................................................
    35     36   	cars = {
    36     37   		{brand = 'dodge', year = 2596};
    37     38   		{brand = 'subaru', year = 321};
    38     39   	};
    39     40   }
    40     41   if m.wrong(s) then print(s.exp) os.exit(1) end
    41     42   
    42         -local str = 'serialized:'
    43         -for i=1,#s do
    44         -	str = str ..' '.. string.format("%x",string.byte(str, i))
    45         -end
    46         -print(str)
           43  +print(hexdump(s))
    47     44   
    48     45   local v = unpack(s)
    49     46   
    50         -local function dump(o)
    51         -	if type(o) == "table" then
    52         -		local str = ''
    53         -		for k,p in pairs(o) do
    54         -			str = str .. (k .. ' = {' .. dump(p) ..'}\n')
    55         -		end
    56         -		return str
    57         -	else
    58         -		return tostring(o)
    59         -	end
    60         -end
    61         -
    62     47   print(dump(v))

Modified test/rand.lua from [1bb191f684] to [a1bc3992a0].

            1  +dofile'test/common.lua'
     1      2   local str = dofile "lib/str.lua"
     2      3   local rnd = str.rand(5,45)
     3      4   print(rnd)
     4         -print(str.capitalize(rnd))
            5  +
            6  +test('random strings do not match',str.rand(5,45) ~= str.rand(5,45))
            7  +test('strings are correct length',#str.rand(44,45) == 45)
            8  +report()

Added textures/sorcery_amulet.png version [9548ac041a].

cannot compute difference between binary files

Modified textures/sorcery_enchant_conserve.png from [cbdb21cff9] to [3451249f04].

cannot compute difference between binary files

Modified textures/sorcery_enchant_dowse.png from [d2f6a92230] to [efa02ac638].

cannot compute difference between binary files

Modified textures/sorcery_enchant_endure.png from [aafc064fe5] to [84094793b8].

cannot compute difference between binary files

Modified textures/sorcery_enchant_harvest.png from [1be6ae9ec0] to [40b1201c27].

cannot compute difference between binary files

Modified textures/sorcery_enchant_pierce.png from [cbba8fc2f9] to [fd3ecbaa4b].

cannot compute difference between binary files

Modified textures/sorcery_enchant_rend.png from [db77c91590] to [3f9d55bb9f].

cannot compute difference between binary files

Added textures/sorcery_enchanter_channeler.png version [cf624a00b3].

cannot compute difference between binary files

Added textures/sorcery_enchanter_pedestal.png version [acba9558af].

cannot compute difference between binary files

Added textures/sorcery_gem_diamond_shard.png version [1d314919ff].

cannot compute difference between binary files

Added textures/sorcery_screw.png version [0f1a74e38b].

cannot compute difference between binary files

Added textures/sorcery_transparent.png version [574440e638].

cannot compute difference between binary files

Added tnodes.lua version [74f7716464].

            1  +for i=1,minetest.LIGHT_MAX do
            2  +	minetest.register_node('sorcery:air_glimmer_' .. tostring(i), {
            3  +		drawtype = 'airlike';
            4  +		light_source = 5 + math.ceil(i * (11/minetest.LIGHT_MAX));
            5  +		sunlight_propagates = true;
            6  +		buildable_to = true;
            7  +		pointable = false;
            8  +		walkable = false;
            9  +		floodable = true;
           10  +		on_construct = function(pos)
           11  +			local meta = minetest.get_meta(pos)
           12  +			meta:set_float('duration',10)
           13  +			meta:set_float('timeleft',10)
           14  +			meta:set_int('power',minetest.LIGHT_MAX)
           15  +			minetest.get_node_timer(pos):start(1)
           16  +		end;
           17  +		on_timer = function(pos,dtime)
           18  +			local meta = minetest.get_meta(pos)
           19  +			local elapsed = dtime + meta:get_float('duration') - meta:get_float('timeleft')
           20  +			local level = 1 - (elapsed / meta:get_float('duration'))
           21  +			local lum = math.ceil(level*meta:get_int('power'))
           22  +			print('elapsed time',elapsed)
           23  +			print('light level',level)
           24  +			print('lum',lum)
           25  +			if lum ~= i then
           26  +				if lum <= 0 then
           27  +					minetest.remove_node(pos)
           28  +					return false
           29  +				else
           30  +					minetest.swap_node(pos,{name='sorcery:air_glimmer_'..tostring(lum)})
           31  +				end
           32  +			end
           33  +			minetest.add_particlespawner {
           34  +				amount = 3 * meta:get_int('power');
           35  +				time = 1.1;
           36  +				minpos = vector.subtract(pos,2);
           37  +				maxpos = vector.add(pos,2);
           38  +				minvel = {x = -0.5; z = -0.5; y = -0.3};
           39  +				maxvel = {x =  0.5; z =  0.5; y =  0.3};
           40  +				minsize = 0.3, maxsize = 0.9;
           41  +				minexptime = 0.5, maxexptime = 1.2;
           42  +				texture = 'sorcery_spark.png';
           43  +				animation = {
           44  +					glow = i;
           45  +					length = 1.2;
           46  +					type = 'vertical_frames';
           47  +					aspect_w = 16;
           48  +					aspect_h = 16;
           49  +				}
           50  +			}
           51  +			meta:set_float('timeleft',meta:get_float('duration') - elapsed)
           52  +			return true
           53  +		end;
           54  +	})
           55  +end

Modified wands.lua from [327c4e54ef] to [8ffba5fe57].

    73     73   			local name = wand.wood .. 'wood wand'
    74     74   			if wand.core ~= nil then name = wand.core .. '-core ' .. name end
    75     75   			if full then
    76     76   				if wand.gem  ~= nil then name = wand.gem .. ' ' .. name end
    77     77   				if wand.wire ~= nil then name = wand.wire .. 'clad ' .. name end
    78     78   			end
    79     79   			return u.str.capitalize(name)
           80  +		end;
           81  +		fullname = function(stack)
           82  +			local proto = sorcery.wands.util.getproto(stack)
           83  +			local wm = stack:get_meta()
           84  +			local spell = wm:get_string('sorcery_wand_spell')
           85  +			if spell ~= '' then
           86  +				local sd = sorcery.data.spells[spell]
           87  +				return u.str.capitalize(sd.name) .. ' wand';
           88  +			else
           89  +				return sorcery.wands.util.basename(proto)
           90  +			end
    80     91   		end;
    81     92   		wear = function(item)
    82     93   			local meta = item:get_meta()
    83     94   			local spell = meta:get_string('sorcery_wand_spell')
    84     95   			local proto = sorcery.wands.util.getproto(item)
    85     96   			if spell == "" then return 0 end
    86     97   
................................................................................
   218    229   	if kind.gem then
   219    230   		img.gem = u.image('sorcery_wand_' .. kind.gem .. '_tip.png')
   220    231   		img.whole = img.gem:blit(img.whole)
   221    232   	end
   222    233   	return img
   223    234   end
   224    235   
   225         -local createstand = function(name, desc, tex, extra)
          236  +local createstand = function(name, wood, desc, tex, extra)
   226    237   	local hitbox = {
   227    238   		type = "fixed";
   228    239   		fixed = {
   229    240   			-0.5, -0.5, -0.3;
   230    241   			0.5, -0.1, 0.3;
   231    242   		};
   232    243   	}
................................................................................
   239    250   		sunlight_propagates = true;
   240    251   		paramtype = 'light';
   241    252   		paramtype2 = 'facedir';
   242    253   		tiles = images;
   243    254   		selection_box = hitbox;
   244    255   		collision_box = hitbox;
   245    256   		use_texture_alpha = true;
          257  +		_proto = {
          258  +			wood = wood;
          259  +		};
   246    260   		groups = {
   247    261   			sorcery_wand_stand = 1;
   248    262   			choppy = 2;
   249    263   			oddly_breakable_by_hand = 2;
   250    264   		};
   251    265   	}
   252    266   	minetest.register_node(name, u.tbl.merge(auto,extra))
   253    267   end
   254         -
          268  +local update_stand_info = function(pos)
          269  +	local woodname = minetest.registered_nodes[minetest.get_node(pos).name]._proto.wood
          270  +	local meta = minetest.get_meta(pos)
          271  +	local inv = meta:get_inventory()
          272  +	if inv:is_empty('wand') then
          273  +		meta:set_string('infotext',u.str.capitalize(woodname) .. ' wand stand')
          274  +	else
          275  +		local stack = inv:get_stack('wand',1)
          276  +		local spell = stack:get_meta():get_string('sorcery_wand_spell')
          277  +		local color = u.color(127,127,127)
          278  +		if spell ~= '' then
          279  +			color = u.color(sorcery.data.spells[spell].color):readable()
          280  +		end
          281  +		local wand_proto = sorcery.wands.util.getproto(stack)
          282  +		meta:set_string('infotext',color:fmt(sorcery.wands.util.fullname(stack) .. ' stand'))
          283  +	end
          284  +end
   255    285   for woodname, wood in pairs(sorcery.wands.materials.wood) do
   256         -	local blank = u.image('doors_blank.png'); -- haaaaack
          286  +	local blank = u.image('sorcery_transparent.png'); -- haaaaack
   257    287   	local name = 'sorcery:wand_stand_' .. woodname
   258         -	createstand(name ,
          288  +	createstand(name, woodname,
   259    289   		u.str.capitalize(woodname .. 'wood wand stand'),
   260    290   		{ wood.tex; blank; blank; blank; }, {
   261    291   			on_construct = function(pos)
   262    292   				local meta = minetest.get_meta(pos)
   263    293   				local inv = meta:get_inventory()
   264    294   				inv:set_size('wand', 1)
          295  +				update_stand_info(pos)
   265    296   			end;
   266    297   			on_rightclick = function(pos,node,user,stack)
   267    298   				local meta = minetest.get_meta(pos)
   268    299   				local stand = meta:get_inventory()
          300  +				local wand_proto = sorcery.wands.util.getproto(stack)
   269    301   				if minetest.get_item_group(stack:get_name(), 'sorcery_wand') ~= 0 then
   270    302   					stand:set_stack('wand',1,stack)
   271    303   					minetest.swap_node(pos, {
   272         -						name = sorcery.wands.util.baseid(stack:get_definition()._proto) .. '_stand_' .. woodname;
          304  +						name = sorcery.wands.util.baseid(wand_proto) .. '_stand_' .. woodname;
   273    305   						param2 = node.param2;
   274    306   					})
   275    307   					stack = ItemStack(nil)
   276    308   				end
          309  +				update_stand_info(pos)
   277    310   				return stack
   278    311   			end
   279    312   		}
   280    313   	)
   281    314   	local plank = wood.plank or 'default:' .. woodname .. '_wood'
   282    315   	minetest.register_craft {
   283    316   		recipe = {
................................................................................
   294    327   		local gemimg = u.image('default_diamond_block.png')
   295    328   		if kind.gem
   296    329   			then gemimg = gemimg:multiply(u.color(sorcery.wands.materials.gem[kind.gem].tone))
   297    330   			else gemimg = gemimg:fade(1) end
   298    331   
   299    332   		createstand(
   300    333   			kind.id .. '_stand_' .. woodname, 
          334  +			woodname,
   301    335   			u.str.capitalize(woodname) .. 'wood wand stand with ' ..  string.lower(sorcery.wands.util.basename(kind,true)), {
   302    336   				wood.tex;
   303    337   				sorcery.wands.materials.wood[kind.wood].tex;
   304    338   				gemimg;
   305         -				(kind.wire and sorcery.wands.materials.wire[kind.wire].tex) or u.image('doors_blank.png'); -- haaaaack
          339  +				(kind.wire and sorcery.wands.materials.wire[kind.wire].tex) or u.image('sorcery_transparent.png'); -- haaaaack
   306    340   			}, {
   307    341   				drop = 'sorcery:wand_stand_' .. woodname;
   308    342   				after_dig_node = function(pos,node,meta,digger)
   309    343   					local stack = meta.inventory.wand[1]
   310    344   					if stack and not stack:is_empty() then
   311    345   						-- luv 2 defensive coding
   312    346   						minetest.add_item(pos, stack)
................................................................................
   325    359   						else goto failure end
   326    360   					end
   327    361   					stand:set_stack('wand',1,ItemStack(nil))
   328    362   					minetest.swap_node(pos, {
   329    363   						name = 'sorcery:wand_stand_' .. woodname;
   330    364   						param2 = node.param2;
   331    365   					})
   332         -					::failure:: return stack
          366  +					::failure::
          367  +						update_stand_info(pos)
          368  +						return stack
   333    369   				end;
   334    370   			}
   335    371   		)
   336    372   	end
   337    373   end
   338    374   sorcery.wands.createkind = function(kind)
   339    375   	if sorcery.wands.kinds[kind.id] then return false end
................................................................................
   372    408   local update_wand_description = function(stack)
   373    409   	local proto = sorcery.wands.util.getproto(stack)
   374    410   	local wm = stack:get_meta()
   375    411   	local spell = wm:get_string('sorcery_wand_spell')
   376    412   	if spell ~= "" then
   377    413   		local sd = sorcery.data.spells[spell]
   378    414   		wm:set_string('description', u.ui.tooltip {
   379         -			title = u.str.capitalize(sd.name) .. ' wand';
          415  +			title = sorcery.wands.util.fullname(stack);
   380    416   			desc = sorcery.wands.util.basedesc(proto);
   381    417   			props = {
   382    418   				{ color = u.color(sd.color); desc = sd.desc }
   383    419   			};
   384    420   		})
   385    421   	else
   386    422   		wm:set_string('description', u.ui.tooltip {