@@ -1,5 +1,7 @@ -local infuser_formspec = function(percent) +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] @@ -9,14 +11,18 @@ 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) + ]], percent, + active and '71' or '0', + active and '71' or '0') end local infuser_stop = function(pos) local meta = minetest.get_meta(pos) @@ -24,20 +30,87 @@ 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 - elixir._proto == nil or potion == nil then return false end - if elixir._proto.apply and potion.on_use then + if elixir.apply and potion.on_use then -- the ingredient is an elixir and at least one potion -- is a fully enchanted, usable potion - if elixir._proto.flag and potion._proto and - potion._proto['no_' .. elixir._proto.flag] == true then + if elixir.flag and potion._proto and + potion._proto['no_' .. elixir.flag] == true 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 @@ -69,8 +142,64 @@ 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 = pdef._proto.name .. ' Draught'; + 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) @@ -78,8 +207,9 @@ 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) if probe.disjunction then return true end local potionct = 0 @@ -91,9 +221,9 @@ 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,potion) then + if elixir_can_apply(elixir._proto,potion) then -- at least one combination makes a valid potion; -- we can start the infuser goto start end @@ -115,15 +245,15 @@ end local time = meta:get_float("runtime") or 0 local newtime = time + elapsed - local infusion_time = potionct * (15 * 60) -- 15 minutes per potion + 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; - time = 15; + 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}; @@ -154,39 +284,42 @@ if newtime >= infusion_time then -- finished local ingredient = infusion[1]:get_name() - 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() - if elixir_can_apply(elixir, potion) then - local newstack = inv:get_stack('potions',i) - elixir._proto.apply(newstack, potion._proto) - newstack:get_meta():set_string('description', sorcery.lib.ui.tooltip { - title = potion._proto.name .. ' Draught'; - desc = potion._proto.desc; - color = sorcery.lib.color(potion._proto.color):readable(); - props = effects_table(newstack); - }); - inv:set_stack('potions',i,discharge(newstack)) - 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 - inv:set_stack('potions',i,discharge(ItemStack(v.output))) - end - end + 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(dump(r)) + 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 - ::skip:: end + inv:set_stack('potions',i,discharge(out)) + end - inv:set_stack('infusion',1,ItemStack(sorcery.register.residue.db[ingredient])) + inv:set_stack('infusion',1,residue) infuser_stop(pos) return false else meta:set_float('runtime', newtime) - meta:set_string('formspec', infuser_formspec(percent)) + meta:set_string('formspec', infuser_formspec(percent, true)) meta:set_string('infotext', 'Infuser (active)') return true end end @@ -194,16 +327,26 @@ local infuser_start = function(pos) local meta = minetest.get_meta(pos) infuser_stop(pos) infuser_timer(pos,0) - minetest.get_node_timer(pos):start(15) + 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"; - paramtype2 = "facedir"; + sunlight_propagates = true; + paramtype = "light"; after_dig_node = sorcery.lib.node.purge_container; tiles = { -- FIXME "default_stone.png", "default_copper_block.png", @@ -212,30 +355,17 @@ "default_tin_block.png", }; paramtype2 = 'facedir'; groups = { - cracky = 2, oddly_breakable_by_hand = 1, heavy = 1; + 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 = { - type = 'fixed'; - fixed = { - -0.37, -0.5, -0.37, - 0.37, 0.5, 0.37 - }; - }; - collision_box = { - type = 'fixed'; - fixed = { - -0.37, -0.5, -0.37, - 0.37, 0.5, 0.37 - }; - }; + selection_box = infbox, collision_box = infbox; on_construct = function(pos) local meta = minetest.get_meta(pos) local inv = meta:get_inventory() @@ -284,4 +414,277 @@ 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',''}; + {'default:wood','sorcery:core_syncretic','default:wood'}; + {'sorcery:gem_sapphire','default:copperblock','sorcery:gem_sapphire'}; + }; +} + + +-- other mods: +-- * amplifier +-- * elixir enhancer +-- * 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