-- alloying furnace
--
-- there are several kinds of alloy furnace, with varying
-- capabilities. the simplest can alloy two simple metals;
-- the most complex can alloy four high-grade metals such
-- as titanium, platinum, iridium, or levitanium.
--
-- furnace recipes follow a pattern: the number of crucibles
-- determines the input slots, the type of crucible determines
-- how hot the furnace can get, and various other components
-- (like a coolant circulator) can be added to allow the
-- creation of exotic metals.
--
-- alloy furnaces produce ingots that can later be melted down
-- again and cast into a mold to produce items of particular
-- shapes.
-- there are five kinds of crucibles: clay, aluminum, platinum,
-- duridium, and impervium. clay crucibles are made by molding
-- clay into the proper shape and then firing it in a furnace.
-- others are made by casting.
local fragments_per_ingot = 4
for _, c in pairs { 'clay', 'aluminum', 'platinum', 'duranium' } do
minetest.register_craftitem('sorcery:crucible_' .. c, {
description = sorcery.lib.str.capitalize(c .. ' crucible');
inventory_image = 'sorcery_crucible_' .. c .. '.png';
})
end
minetest.register_craftitem('sorcery:crucible_clay_molding', {
description = sorcery.lib.str.capitalize('Crucible molding');
inventory_image = 'sorcery_crucible_clay_molding.png';
})
minetest.register_craft {
recipe = {
{ 'default:clay_lump', '', 'default:clay_lump'};
{ 'default:clay_lump', '', 'default:clay_lump'};
{ 'default:clay_lump', 'default:clay_lump', 'default:clay_lump'};
};
output = 'sorcery:crucible_clay_molding';
}
minetest.register_craft {
type = 'shapeless';
recipe = { 'sorcery:crucible_clay_molding' };
output = 'default:clay_lump 7';
}
minetest.register_craft {
type = 'cooking';
recipe = 'sorcery:crucible_clay_molding';
cooktime = 40;
output = 'sorcery:crucible_clay';
}
local burn_layout = function(v)
local layouts = {
[1] = {w = 1, h = 1}; [2] = {w = 2, h = 1}; [3] = {w = 3, h = 1};
[4] = {w = 2, h = 2}; [5] = {w = 3, h = 2}; [6] = {w = 3, h = 2};
[7] = {w = 4, h = 2}; [8] = {w = 4, h = 2}; [9] = {w = 3, h = 3};
}
local inpos = { x = 2.8, y = 1 }
local insize = layouts[v.in]
local outpos = { x = 5.2, y = 2.4 }
local outsize = layouts[v.out]
local fuelpos = { x = 2.8, y = 3.4 }
local fuelsize = layouts[v.fuel]
return string.format([[
list[context;input;%f,%f;%f,%f;]
list[context;output;%f,%f;%f,%f;]
list[context;fuel;%f,%f;%f,%f;]
image[2.3,1.9;1,1;default_furnace_fire_bg.png^[lowpart:%u%%:default_furnace_fire_fg.png]
image[3.2,1.9;1,1;gui_furnace_arrow_bg.png^[lowpart:%u%%:gui_furnace_arrow_fg.png^[transformR270]
listring[context;output] listring[current_player;main]
listring[context;input] listring[current_player;main]
listring[context;fuel] listring[current_player;main]
]],
--input
inpos.x - insize.w/2, -- pos
inpos.y - insize.h/2,
insize.w, insize.h, -- size
--output
outpos.x - outsize.w/2, -- pos
outpos.y - outsize.h/2,
outsize.w, outsize.h, -- size
--fuel
fuelpos.x - fuelsize.w/2, -- pos
fuelpos.y - fuelsize.h/2,
fuelsize.w, fuelsize.h, -- size
v.burn, v.prog
)
end
local kiln_formspec = function(kind, fuel_progress, cook_progress)
return [[
size[8,8]
list[current_player;main;0,4.2;8,4]
list[context;wax;0,2;1,1]
]] .. burn_layout{
in = kind.size^2;
out = kind.outsize;
fuel = kind.fuelsize;
burn = fuel_progress;
prog = cook_progress;
}
end
local smelter_formspec = function(kind, fuel_progress, smelt_progress)
return [[
size[8,8]
list[current_player;main;0,4.2;8,4]
]] .. burn_layout {
in = kind.size;
out = kind.outsize;
fuel = kind.fuelsize;
burn = fuel_progress;
prog = smelt_progress;
}
end
local find_recipe = function(inv)
local mix = {}
local count = 0
for i=1,inv:get_size('input') do
local m = inv:get_stack('input',i)
if m:is_empty() then goto skip end
local l = sorcery.data.metallookup[m:get_name()]
if not l then return false end
mix[l.id] = (mix[l.id] or 0) + l.value
count = count + l.value
::skip::end
-- everything is metal, we've finished summing it up.
-- let's see if the assembled items match the ratio
-- specified in any of the smelting recipes.
local matches = 0
for _,rec in pairs(sorcery.data.alloys) do
local fac = nil
local meltpoint = 1
if rec.metals == nil then goto skip_recipe end
for metal, ratio in pairs(rec.metals) do
if mix[metal] and mix[metal] % ratio == 0 then
if fac then
if mix[metal] / ratio ~= fac then goto skip_recipe end
else fac = math.floor(mix[metal] / ratio) end
local m = sorcery.data.metals[metal]
if m.meltpoint then
meltpoint = math.max(meltpoint, m.meltpoint) end
else goto skip_recipe end
end
do return rec, count, fac, meltpoint end
::skip_recipe::end
return false
end
local update_smelter = function(pos)
local meta = minetest.get_meta(pos)
local inv = meta:get_inventory()
local proto = minetest.registered_nodes[minetest.get_node(pos).name]._proto
local recipe, count, factor, meltpoint = find_recipe(inv)
if recipe and proto.temp >= meltpoint then
minetest.get_node_timer(pos):start(1)
else
meta:set_float('burntime',0)
end
end
local smelter_step = function(kind,active,pos,delta)
local meta = minetest.get_meta(pos)
local inv = meta:get_inventory()
local recipe, count, factor = find_recipe(inv)
local cooktime
local elapsed = meta:get_float('burntime') + delta
meta:set_float('burnleft',meta:get_float('burnleft') - delta)
if (not active) and (not recipe) then return false end
if meta:get_float('burnleft') <= 0 or not active then
if recipe then
local burn, frep = minetest.get_craft_result {
method = 'fuel', width = 1;
items = { inv:get_stack('fuel',1) };
}
if burn.time == 0 then goto nofuel end
inv:set_stack('fuel', 1, frep.items[1])
meta:set_float('burnleft',burn.time)
meta:set_float('burnmax',burn.time)
if not active then
minetest.swap_node(pos,
sorcery.lib.tbl.merge(minetest.get_node(pos), {
name = kind.id .. '_active'
}))
active = true
end
else goto nofuel end
end
if not recipe then goto update end
cooktime = ((recipe.cooktime / fragments_per_ingot) * count) / factor
if elapsed >= cooktime then
elapsed = 0
-- remove used items
for i=1,inv:get_size('input') do
local s = inv:get_stack('input',i)
if s:is_empty() then goto skip end
s:take_item(1) inv:set_stack('input',i,s)
::skip::end
local outstack
if count % fragments_per_ingot == 0 then
outstack = ItemStack {
name = sorcery.data.metals[recipe.output].ingot or 'sorcery:' .. recipe.output .. '_ingot';
count = count / fragments_per_ingot;
}
else
outstack = ItemStack {
name = 'sorcery:fragment_' .. recipe.output;
count = count;
}
end
local leftover = inv:add_item('output',outstack)
if not leftover:is_empty() then
minetest.add_item(pos, leftover)
end
end
::update::
meta:set_float('burntime',elapsed)
meta:set_string('formspec', smelter_formspec(kind,
math.min(1, meta:get_float('burnleft') /
meta:get_float('burnmax')
) * 100, -- fuel
(cooktime and math.min(1, elapsed / cooktime) * 100) or 0 -- smelt
))
do return active end
::nofuel::
if active then
minetest.swap_node(pos,
sorcery.lib.tbl.merge(minetest.get_node(pos), { name = kind.id }))
end
meta:set_float('burnleft',0) -- just in case
::noburn::
meta:set_float('burntime',0) -- just in case
meta:set_string('formspec', smelter_formspec(kind, 0, 0))
return false
end
local register_kiln = function(kind)
local box = {
open = {
type = 'fixed';
fixed = {
-0.5,-0.5,-0.5,
0.5, 1.6, 0.5
};
};
closed = {
type = 'fixed';
fixed = {
-0.5,-0.5,-0.5,
0.5, 0.7, 0.5
};
};
}
local id = 'sorcery:kiln_' .. kind.material
local metal = sorcery.data.metals[kind.material]
-- use some iffy heuristics to guess the texture
local metaltex
if kind.material == 'clay' then
metaltex = 'default_brick.png';
else
metaltex = (metal.img and metal.img.block) or
(metal.ingot and 'default_' .. kind.material .. '_block.png') or
sorcery.lib.image('default_steel_block.png'):multiply(sorcery.lib.color(metal.tone)):render();
end
local tex = {
open = {
'default_furnace_front.png';
metaltex;
'default_copper_block.png';
'default_stone.png';
};
closed = {
'default_furnace_front_active.png';
'default_copper_block.png';
metaltex;
'default_stone.png';
};
};
for _,state in pairs{'open','closed'} do
local id_closed = id .. '_closed'
local id_current = (state == 'closed' and id_closed) or id
local desc = sorcery.lib.str.capitalize(kind.temp_name) .. ' kiln';
minetest.register_node(id_current, {
_active = (state == 'closed');
_proto = kind;
description = desc;
drawtype = "mesh";
mesh = 'sorcery-kiln-' .. state .. '.obj';
drop = id;
sunlight_propagates = true;
paramtype1 = 'light';
paramtype2 = 'facedir';
selection_box = box[state];
collision_box = box[state];
tiles = tex[state];
light_source = (state == 'closed' and 7) or 0;
on_construct = function(pos)
local meta = minetest.get_meta(pos)
local inv = meta:get_inventory()
inv:set_size('input', kind.size^2)
inv:set_size('output', kind.outsize)
inv:set_size('fuel', kind.fuelsize)
inv:set_size('wax', 1)
meta:set_float('burnleft',0)
meta:set_float('burnmax',0)
meta:set_float('burntime',0)
meta:set_string('infotext',desc)
meta:set_string('formspec',kiln_formspec(kind,0,0))
end;
on_timer = function(pos,delta)
end;
on_metadata_inventory_put = function(pos)
minetest.get_node_timer(pos):start(1) end;
on_metadata_inventory_move = function(pos)
minetest.get_node_timer(pos):start(1) end;
on_metadata_inventory_take = function(pos)
minetest.get_node_timer(pos):start(1) end;
})
end
local ingot
if kind.material == 'clay' then
ingot = 'default:clay_brick';
else
ingot = metal.ingot or 'sorcery:' .. kind.material .. '_ingot'
end
minetest.register_craft = {
output = id_open;
recipe = {
{'default:copper_ingot', 'default:copper_ingot', 'default:copper_ingot'};
{ingot,'',ingot};
{ingot,'default:furnace',ingot};
};
}
end
local register_smelter = function(kind)
local recipe = {{},{};
{'default:stone','default:furnace','default:stone'};
} do
local on = kind.crucible
local ti = 'default:tin_ingot'
local cu = 'default:copper_ingot'
local crucmap = {
[2] = { {cu,cu,cu}, {on,ti,on} };
[3] = { {cu,on,cu}, {on,ti,on} };
[4] = { {on,cu,on}, {on,ti,on} };
[5] = { {on,cu,on}, {on,on,on} };
[6] = { {on,on,on}, {on,on,on} };
};
for y=1,2 do recipe[y] = crucmap[kind.size][y] end
end
local desc = 'smelter';
if kind.temp_name then desc = kind.temp_name .. ' ' .. desc end
if kind.size_name then desc = kind.size_name .. ' ' .. desc end
desc = sorcery.lib.str.capitalize(desc);
local id = 'sorcery:smelter_' .. kind.material .. kind.size_name
kind.id = id
for _, active in pairs {false, true} do
minetest.register_node((active and id .. '_active') or id, {
_proto = kind;
description = desc;
drop = id;
groups = { cracky = 2; };
paramtype2 = 'facedir';
light_source = (active and 9) or 0;
on_construct = function(pos)
local meta = minetest.get_meta(pos)
local inv = meta:get_inventory()
inv:set_size('input',kind.size)
inv:set_size('output',kind.outsize)
inv:set_size('fuel',kind.fuelsize)
meta:set_string('infotext', desc)
meta:set_string('formspec', smelter_formspec(kind, 0, 0))
meta:set_float('burnleft',0)
meta:set_float('burnmax',0)
meta:set_float('burntime',0)
end;
on_metadata_inventory_put = update_smelter;
on_metadata_inventory_move = update_smelter;
on_metadata_inventory_take = update_smelter;
on_timer = function(pos,delta) return smelter_step(kind, active, pos, delta) end;
-- allow_metadata_inventory_put = function(pos, listname, index, stack, player)
-- end;
tiles = {
'sorcery_smelter_top_' .. tostring(kind.size) .. '.png';
'sorcery_smelter_bottom.png';
'sorcery_smelter_side.png';
'sorcery_smelter_side.png';
'sorcery_smelter_side.png';
'sorcery_smelter_front' .. ((active and '_hot') or '') .. '.png';
};
})
end
minetest.register_craft {
recipe = recipe;
output = id;
}
end
for _, t in pairs {
{1, nil, 'clay'};
{2, 'hot','aluminum'};
{3, 'volcanic','platinum'};
{4, 'stellar','duridium'};
{5, 'nova','impervium'};
} do register_kiln {
temp = t[1], temp_name = t[2];
material = t[3];
size = 3, outsize = 4, fuelsize = 1; -- future-proofing
}
for _, s in pairs {
{2, 'small'};
{3, 'large'};
{4, 'grand'};
} do register_smelter {
size = s[1], size_name = s[2];
temp = t[1], temp_name = t[2];
material = t[3];
crucible = 'sorcery:crucible_' .. t[3];
outsize = 4, fuelsize = 1; -- future-proofing
} end
end