local log = sorcery.logger('infuser')
local infuser_formspec = function(percent,active)
return string.format([[
size[8,7]
list[context;infusion;3.5,0;1,1;]
list[context;potions;2.5,1.7;1,1;0]
list[context;potions;3.5,2;1,1;1]
list[context;potions;4.5,1.7;1,1;2]
image[2.5,1.7;1,1;vessels_shelf_slot.png]
image[3.5,2;1,1;vessels_shelf_slot.png]
image[4.5,1.7;1,1;vessels_shelf_slot.png]
image[3.5,1;1,1;gui_furnace_arrow_bg.png^[lowpart:%d:gui_furnace_arrow_fg.png^[transformR180]
list[current_player;main;0,3.3;8,4;]
animated_image[2.5,0;1,2;tube_l;sorcery_ui_infuse_tube.png;28;%s;28]
animated_image[4.5,0;1,2;tube_l;sorcery_ui_infuse_tube.png^[transformFX;28;%s;28]
listring[context;infusion]
listring[current_player;main]
listring[context;potions]
listring[current_player;main]
listring[context;infusion]
]], percent,
active and '71' or '0',
active and '71' or '0')
end
local infuser_stop = function(pos)
local meta = minetest.get_meta(pos)
meta:set_float('runtime', 0)
meta:set_string('formspec', infuser_formspec(0))
meta:set_string('infotext', 'Infuser')
minetest.get_node_timer(pos):stop()
end
sorcery.alchemy = {}
sorcery.alchemy.is_module = function(name)
local def = minetest.registered_nodes[name]
if def and def._sorcery and def._sorcery.alchemy and def._sorcery.alchemy.module then
return def._sorcery.alchemy.module
else return nil end
end
local infuser_mods = function(pos)
local getmod = function(pos)
local st = sorcery.lib.node.force(pos)
return sorcery.alchemy.is_module(st.name)
end
local st = sorcery.lib.node.force(pos)
if st.name ~= 'sorcery:infuser' then return false end
local devs = {}
repeat
pos = vector.offset(pos, 0, -1, 0)
local mod = getmod(pos)
if mod then
if mod.attach == 'stack' then
devs[#devs+1] = { pos = pos, mod = mod }
if mod.stmod_connect then
for _,ofs in pairs(mod.stmod_connect) do
local wh = vector.add(pos,ofs)
local stm = getmod(wh)
if stm and stm.attach == 'stand' then
-- TODO check facing correct direction
devs[#devs+1] = {pos = wh, mod = stm}
end
end
end
if mod.endstack then break end
else break end -- invalid attachment
else break end
until false
local props = {}
local comp = function(a,b) return function(...) return a(b(...)) end end
local mult = function(a,b) return a * b end;
local sum = function(a,b) return a + b end;
local combine = {
brewtime = mult;
onbrew = function(a, b)
return function(ctx)
ctx.stack = b(ctx)
return a(ctx)
end
end;
}
for _, d in pairs(devs) do
for k,v in pairs(d.mod) do
if combine[k] then
if props[k]
then props[k] = (combine[k])(v, props[k])
else props[k] = v
end
end
end
end
return props, devs
end
local elixir_can_apply = function(elixir, potion)
-- accepts an elixir def and potion def
if elixir == nil or
potion == nil then return false end
if elixir.apply and potion._proto and potion._proto.quals then
-- the ingredient is an elixir and at least one potion has a
-- quality that can be enhanced
if elixir.qual and potion._proto and not potion._proto.quals[elixir.qual] then
-- does the elixir have a property used to denote
-- compatibility? if so, check the potion to see if it's
-- marked as incompatible
return false
else
return true
end
end
return false
end
local effects_table = function(potion)
local meta = potion:get_meta()
local tbl = {}
for k,v in pairs(sorcery.data.elixirs) do
if not v.qual then goto skip end
local val = meta:get_int(v.qual)
if val > 0 then
local aff, title, desc = v.describe(potion)
if val > 3 then title = title .. ' x' .. val
elseif val == 3 then title = 'thrice-' .. title
elseif val == 2 then title = 'twice-' .. title
end
tbl[#tbl + 1] = {
title = sorcery.lib.str.capitalize(title);
desc = desc;
affinity = aff;
}
end
::skip::end
return tbl
end
sorcery.alchemy.elixir_apply = function(elixir, potion)
if not potion then return end
local pdef = potion:get_definition()
if elixir_can_apply(elixir, pdef) then
elixir.apply(potion, pdef._proto)
potion:get_meta():set_string('description', sorcery.lib.ui.tooltip {
title = string.format('%s %s', pdef._proto.name, pdef._proto.kind.label);
desc = pdef._proto.desc;
color = sorcery.lib.color(pdef._proto.color):readable();
props = effects_table(potion);
});
end
return potion
end
sorcery.alchemy.infuse = function(infusion, potions)
local ingredient = infusion:get_name()
local creations = {}
local brewed = false
for i = 1,#potions do
if potions[i]:is_empty() then goto skip end
local base = potions[i]:get_name()
local potion = potions[i]:get_definition()
local elixir = infusion:get_definition()
brewed = true
if elixir_can_apply(elixir._proto, potion) then
local newstack = sorcery.alchemy.elixir_apply(elixir._proto, potions[i])
if newstack ~= nil then
creations[i] = {
output = newstack;
elixir = elixir;
enhance = true;
}
else return true end
else
for _,v in pairs(sorcery.register.infusions.db) do
if v.infuse == ingredient and v.into == base then
-- transform the base into the infusion
local out = ItemStack(v.output)
if out ~= nil then
creations[i] = {
recipe = v;
output = out;
proto = minetest.registered_items[v.output]._proto;
brew = true;
}
else return true end
end
end
end
::skip:: end
local residue = ItemStack(sorcery.register.residue.db[ingredient])
return creations, residue
end
local infuser_timer = function(pos, elapsed)
local meta = minetest.get_meta(pos)
local inv = meta:get_inventory()
local infusion = inv:get_list('infusion')
local potions = inv:get_list('potions')
local elixir = infusion[1]:get_definition()
local probe = sorcery.spell.probe(pos)
local fx = infuser_mods(pos)
local sparkle_color = {sorcery.lib.color(255, 0, 145)};
if probe.disjunction then return true end
local potionct = 0
local cancel = true
do
local ingredient -- *eyeroll*
if infusion[1]:is_empty() then goto cancel end
ingredient = infusion[1]:get_name()
for i = 1,#potions do
if potions[i]:is_empty() then goto skip end
potionct = potionct + 1
local base = potions[i]:get_name()
local potion = potions[i]:get_definition()
if elixir_can_apply(elixir._proto,potion) then
-- at least one combination makes a valid potion;
-- we can start the infuser
if elixir._proto.color then
sparkle_color[#sparkle_color+1] = sorcery.lib.color(elixir._proto.color)
end
cancel = false
end
for _,v in pairs(sorcery.register.infusions.db) do
if v.infuse == ingredient and v.into == base then
-- at least one combination makes a valid
-- potion; we can start the infuser
if v.output.data and v.output.data.color then
sparkle_color[#sparkle_color+1] = sorcery.lib.color(v.output.data.color)
end
cancel = false
end
end
::skip:: end
::cancel:: if cancel then
infuser_stop(pos)
return false
end
::start::
end
local time = meta:get_float("runtime") or 0
local newtime = time + elapsed
local infusion_time = potionct * (15 * 60) * (fx.brewtime or 1)-- 15 minutes per potion
-- FIXME make dependent on recipe
local percent = math.min(100, math.floor(100 * (newtime / infusion_time)))
local spawn = function(particle, scale, amt)
minetest.add_particlespawner {
amount = amt / 6;
time = 2;
minpos = pos;
maxpos = pos;
minvel = {x = -0.1, y = 0.05, z = -0.1};
maxvel = {x = 0.1, y = 0.1, z = 0.1};
minacc = {x = 0, y = 0.1, z = 0};
maxacc = {x = 0, y = 0.4, z = 0};
minexptime = 2;
maxexptime = 4;
minsize = 0.5 * scale;
maxsize = 3 * scale;
glow = 14;
texture = particle;
animation = {
type = "vertical_frames";
aspect_h = 16;
aspect_w = 16;
length = 4.1;
};
}
end
local spark = sorcery.lib.image('sorcery_spark.png')
for i = 1,4 do
local fac = 1 / i
local _, spc = sorcery.lib.tbl.pick(sparkle_color)
local sp = spark:glow(spc)
spawn(sp:render(), fac, (32/fac) * 4)
end
local discharge = sorcery.lib.node.discharger(pos)
if newtime >= infusion_time then
-- finished
local ingredient = infusion[1]:get_name()
local result, residue = sorcery.alchemy.infuse(infusion[1], potions)
for i, r in pairs(result) do
local out = r.output
if r.brew then
if fx.onbrew then out = fx.onbrew {
pos = pos;
stack = out;
potion = r.proto;
infusion = infusion[1];
recipe = r.recipe;
} end
log.act(string.format('an infuser at %s has brewed a %s potion',
minetest.pos_to_string(pos), out:get_name()))
elseif r.enhance then
if fx.onenhance then out = fx.onenhance {
pos = pos;
stack = out;
potion = r.proto;
elixir = r.elixir;
} end
log.act(string.format('an infuser at %s has enhanced a %s potion with a %s elixir',
minetest.pos_to_string(pos), out:get_name(), infusion[1]:get_name()))
end
inv:set_stack('potions',i,discharge(out))
end
inv:set_stack('infusion',1,residue)
infuser_stop(pos)
return false
else
meta:set_float('runtime', newtime)
meta:set_string('formspec', infuser_formspec(percent, true))
meta:set_string('infotext', 'Infuser (active)')
return true
end
end
local infuser_start = function(pos)
local meta = minetest.get_meta(pos)
infuser_stop(pos)
infuser_timer(pos,0)
minetest.get_node_timer(pos):start(2)
end
local infrad = 0.42;
local infbox = {
type = 'fixed';
fixed = {
-infrad, -0.5, -infrad;
infrad, 0.5, infrad;
};
};
minetest.register_node("sorcery:infuser", {
description = "Infuser";
drawtype = "mesh";
mesh = "sorcery-infuser.obj";
sunlight_propagates = true;
paramtype = "light";
after_dig_node = sorcery.lib.node.purge_container;
tiles = { -- FIXME
"default_stone.png",
"default_copper_block.png",
"default_steel_block.png",
"default_bronze_block.png",
"default_tin_block.png",
};
paramtype2 = 'facedir';
groups = {
cracky = 2, dig_immediate = 2, heavy = 1;
sorcery_alchemy = 1, sorcery_magitech = 1;
};
_sorcery = {
recipe = {
note = 'Infuse special ingredients into liquids to create and alter powerful potions';
};
};
selection_box = infbox, collision_box = infbox;
on_construct = function(pos)
local meta = minetest.get_meta(pos)
local inv = meta:get_inventory()
inv:set_size('infusion', 1)
inv:set_size('potions', 3)
meta:set_string('infotext','Infuser')
infuser_timer(pos,0)
end;
on_timer = infuser_timer;
on_metadata_inventory_move = infuser_start;
on_metadata_inventory_put = infuser_start;
allow_metadata_inventory_put = function(pos, listname, index, stack, player)
local meta = minetest.get_meta(pos)
local inv = meta:get_inventory()
if not inv:get_stack(listname,index):is_empty() then
return 0
end
if listname == 'infusion' then
return 1
elseif listname == 'potions' then
if minetest.get_item_group(stack:get_name(), "vessel") >= 1 then
return 1
end
end
return 0
end;
allow_metadata_inventory_move = function(pos, from_list, from_idx, to_list, to_idx, count, player)
local meta = minetest.get_meta(pos)
local inv = meta:get_inventory()
if inv:get_stack(to_list, to_idx):is_empty() then
if to_list == 'potions' and to_list ~= from_list then
if minetest.get_item_group(
--[[name]] inv:get_stack(from_list, from_idx):get_name(),
--[[group]] "vessel") >= 1 then
return 1
else
return 0
end
else
return 1
end
else
return 0
end
end;
})
minetest.register_node("sorcery:infuser_accelerator", {
description = "Infusion Accelerator";
drawtype = "mesh";
mesh = "sorcery-infuser-accelerator.obj";
sunlight_propagates = true;
paramtype = "light";
paramtype2 = "facedir";
tiles = { -- FIXME
"basic_materials_brass_block.png",
"default_stone.png",
"default_copper_block.png",
};
paramtype2 = 'facedir';
groups = {
cracky = 2, heavy = 1;
sorcery_alchemy = 1, sorcery_magitech = 1;
};
_sorcery = {
recipe = {
note = 'Connect to an infuser to speed up its operation; these can be stacked';
};
alchemy = {
module = {
attach = 'stack';
brewtime = 0.6;
};
};
};
selection_box = infbox, collision_box = infbox;
})
sorcery.alchemy.purge_stand = function(pos, node, meta, who)
sorcery.lib.node.purge_container(pos,node,meta,who);
local mod = sorcery.alchemy.is_module(node.name)
if not mod then return end
if mod.stmod_connect then
for _,ofs in pairs(mod.stmod_connect) do
local p = vector.add(pos,ofs)
local nn = sorcery.lib.node.force(p).name
local pm = sorcery.alchemy.is_module(nn)
if pm then -- drop any attached modules
minetest.dig_node(p)
end
end
end
end
minetest.register_node("sorcery:infuser_stand", {
description = "Infuser Stand";
drawtype = "mesh";
mesh = "sorcery-infuser-stand.obj";
paramtype2 = "facedir";
after_dig_node = sorcery.alchemy.purge_stand;
tiles = { -- FIXME
"default_silver_sand.png",
"default_stone.png",
"default_copper_block.png",
"default_wood.png",
"default_aspen_wood.png",
"default_steel_block.png",
};
paramtype2 = 'facedir';
groups = {
cracky = 2, choppy = 2, heavy = 1;
sorcery_alchemy = 1, sorcery_magitech = 1;
};
_sorcery = {
recipe = {
note = 'Improve the effectiveness of an infuser by using a stand to attach modules';
};
alchemy = {
module = {
attach = 'stack';
endstack = true;
stmod_connect = {
{x = 1, z = 0, y = 0};
{x = -1, z = 0, y = 0};
{x = 0, z = 1, y = 0};
{x = 0, z = -1, y = 0};
};
};
};
};
selection_box = {
type = 'fixed', fixed = {
-0.5, -0.5, -0.5,
0.5, 0.5, 0.5
};
};
collision_box = {
type = 'fixed', fixed = {
-0.5, -0.5, -0.5,
0.5, 0.5, 0.5
};
};
on_construct = function(pos)
local meta = minetest.get_meta(pos)
local inv = meta:get_inventory()
inv:set_size('shelves',6 * 2)
meta:set_string('infotext','Infuser Stand')
meta:set_string('formspec', [[
size[8,6.25]
list[context;shelves;0,0;3,2;]
list[context;shelves;5,0;3,2;6]
list[current_player;main;0,2.5;8,4;]
]])
end;
})
minetest.register_craft {
output = 'sorcery:infuser_stand';
recipe = {
{'sorcery:screw_copper','sorcery:conduction_plate','sorcery:screw_copper'};
{'group:wood','basic_materials:wet_cement','group:wood'};
{'default:bronze_ingot','sorcery:inversion_matrix','default:bronze_ingot'}
};
}
minetest.register_craft {
output = 'sorcery:infuser_accelerator';
recipe = {
{'sorcery:brass_ingot','sorcery:conduction_plate','sorcery:brass_ingot'};
{'basic_materials:motor','sorcery:catalytic_convector','basic_materials:motor'};
{'default:stone','sorcery:leyline_stabilizer','default:stone'};
};
}
sorcery.alchemy.register_mod = function(prop, m)
minetest.register_node(prop.name, sorcery.lib.tbl.merge({
sunlight_propagates = true;
paramtype = 'light', paramtype2 = 'facedir';
drawtype = 'mesh';
groups = sorcery.lib.tbl.merge({
cracky = 2;
sorcery_magitech = 1;
sorcery_alchemy = 1;
}, prop.groups or {});
_sorcery = sorcery.lib.tbl.merge({
alchemy = {
module = sorcery.lib.tbl.merge({attach = 'stand'}, prop.module);
};
recipe = {
note = prop.note;
}
}, prop.func or {});
node_placement_prediction = '';
on_place = function(stack, user, where)
if not where or where.type ~= 'node' then return nil end
local nd = minetest.get_node(where.under)
if not nd then return nil end
local def = minetest.registered_nodes[nd.name]
if def and def._sorcery and def._sorcery.alchemy and def._sorcery.alchemy.module then
local mod = def._sorcery.alchemy.module
if not mod.stmod_connect then return nil end
for _, ofs in pairs(mod.stmod_connect) do
ofs = vector.rotate(ofs, vector.dir_to_rotation(minetest.facedir_to_dir(nd.param2)))
if vector.equals(vector.add(where.under, ofs), where.above)
and sorcery.lib.node.is_air(where.above) then goto valid end
end
do return nil end
::valid::
minetest.set_node(where.above, {
name = prop.name;
param2 = minetest.dir_to_facedir(vector.subtract(where.under, where.above));
})
stack:take_item(1)
return stack
end
end;
}, m))
end
sorcery.alchemy.register_mod({
name = 'sorcery:infuser_mod_prolongator';
note = 'Attached to an infuser using a stand, a Prolongator will cause it to produce longer-lasting potions';
module = {
onbrew = function(ctx)
return sorcery.alchemy.elixir_apply(sorcery.data.elixirs.Longevity, ctx.stack)
end;
}
}, {
description = 'Infusion Prolongator';
mesh = 'sorcery-infuser-mod-prolongator.obj';
inventory_image = 'sorcery_infuser_mod_prolongator.png';
tiles = {
sorcery.lib.image('default_diamond_block.png'):multiply(sorcery.lib.color(255,50,150)):render();
'default_silver_sand.png';
'default_copper_block.png';
'default_steel_block.png';
};
selection_box = { type = 'fixed', fixed = { -0.23, -0.5, -0.2; 0.5, 0, 0.5; }; };
collision_box = { type = 'fixed', fixed = { -0.23, -0.5, -0.2; 0.5, 0, 0.5; }; };
})
sorcery.alchemy.register_mod({
name = 'sorcery:infuser_mod_amplifier';
note = 'Attached to an infuser using a stand, an Amplifier will cause it to produce more powerful potions';
module = {
onbrew = function(ctx)
return sorcery.alchemy.elixir_apply(sorcery.data.elixirs.Force, ctx.stack)
end;
}
}, {
description = 'Infusion Amplifier';
mesh = 'sorcery-infuser-mod-amplifier.obj';
inventory_image = 'sorcery_infuser_mod_amplifier.png';
tiles = {
sorcery.lib.image('default_diamond_block.png'):multiply(sorcery.lib.color(167,210,255)):render();
'default_silver_sand.png';
'default_copper_block.png';
'default_steel_block.png';
'default_wood.png';
'default_aspen_wood.png';
};
selection_box = { type = 'fixed', fixed = { -0.41, -0.5, -0.1; 0.25, 0.22, 0.5; }; };
collision_box = { type = 'fixed', fixed = { -0.41, -0.5, -0.1; 0.25, 0.22, 0.5; }; };
})
-- sorcery.alchemy.register_mod({
-- name = 'sorcery:infuser_mod_auxiliator';
-- note = 'Attached to an infuser using a stand, provides an extra potion slot';
-- module = {
-- onbrew = function(ctx)
-- end;
-- }
-- }, {
-- description = 'Infusion Auxiliator';
-- mesh = 'sorcery-infuser-mod-auxiliator.obj';
-- inventory_image = 'sorcery_infuser_mod_auxiliator.png';
-- tiles = {
-- -- sorcery.lib.image('default_diamond_block.png'):multiply(sorcery.lib.color(167,210,255)):render();
-- };
-- selection_box = { type = 'fixed', fixed = { -0.41, -0.5, -0.1; 0.25, 0.22, 0.5; }; };
-- collision_box = { type = 'fixed', fixed = { -0.41, -0.5, -0.1; 0.25, 0.22, 0.5; }; };
-- })
minetest.register_craft {
output = 'sorcery:infuser_mod_prolongator';
recipe = {
-- this recipe is crap + way too cheap, come up with
-- some fancy new ingredients and use them instead FIXME
{'basic_materials:silver_wire','stairs:slab_copperblock',''};
{'default:steel_ingot','sorcery:core_syncretic','default:steel_ingot'};
{'sorcery:gem_ruby','default:copperblock','sorcery:gem_ruby'};
};
}
minetest.register_craft {
output = 'sorcery:infuser_mod_amplifier';
recipe = {
-- this recipe is crap + way too cheap, come up with
-- some fancy new ingredients and use them instead FIXME
{'basic_materials:silver_wire','stairs:slab_copperblock',''};
{'group:wood','sorcery:core_syncretic','group:wood'};
{'sorcery:gem_sapphire','default:copperblock','sorcery:gem_sapphire'};
};
}
-- other mods:
-- * speed amplifier
-- * randomizer
-- * device that enables some kind of higher-tier potionmaking
-- * device that allows attaching 1 or 2 stand mods without terminating the stack
-- * radia collector - captures spare radia, occasionally can produce an extra potion, sometimes random