@@ -5,116 +5,20 @@ 0.5, 0.1, 0.5; }; } -local enchantable_tools = { - pickaxe = {}, pick = {}; - axe = {}; - sword = {}; - sickle = {}; - shovel = {}; - hoe = {}; -}; - -sorcery.enchant = {} do - local m = sorcery.lib.marshal - local ench_t = m.g.struct { - id = m.t.str; - slot = m.t.u8; - boost = m.t.u8; -- every enchantment has an intrinsic force - -- separate from the confluence of the slot, which is - -- determined by the composition of the wand used to generate - -- it (for instance, a gold-wired wand at low wear, or a wand - -- with specific gemstones, may have a boost level above 0) - } - local pack, unpack = m.transcoder { - spells = m.g.array(8, ench_t); - energy = m.t.u16; - } - local key = 'sorcery_enchantment_recs' - sorcery.enchant.set = function(stack, data) - local meta = stack:get_meta() - meta:set_string(key, pack(data)) - end - sorcery.enchant.get = function(stack) - local meta = stack:get_meta() - if meta:contains(key) then - local data = meta:get_string(key) - return unpack(data) - else - return { - spells = {}; - energy = 0; - } - end - end - sorcery.enchant.strength = function(stack,id) - -- this functions should be used whenever you need to - -- determine the power of a particular enchantment on - -- an enchanted item. - local e = sorcery.enchant.get(stack) - local p = 0.0 - local slots = sorcery.matreg.lookup[stack:get_name()].data.slots - -- TODO handle strength-boosting spells! - for _,s in pairs(e.spells) do - if s.id == id then p = p + slots[s.slot] end - end - return p - end - sorcery.enchant.stackup = function(stack) - -- stack update function. this should be called whenever - -- the enchantment status of a stack changes; it will - -- alter/reset tool capabilities and tooltip as necessary - local e = sorcery.enchant.get(stack) - local meta = stack:get_meta() - local def = stack:get_definition() - meta:set_string('tool_capabilities','') - local done = {} - local props = {} - for _,s in pairs(e.spells) do - if done[s.id] then goto skip end - done[s.id] = true - local pwr = sorcery.enchant.strength(stack,s.id) - -- somewhat wasteful… - local name, color, desc = sorcery.data.enchants[s.id].apply(stack,pwr) - props[#props+1] = { - title = name; - desc = desc; - color = sorcery.lib.color(desc); - } - ::skip::end - if #e.spells > 0 then - meta:set_string('description', sorcery.lib.ui.tooltip { - title = 'Enchanted ' .. def.description; - props = props; - }) - else - meta:set_string('description','') - end - end -end - -local enchanter_getsubj = function(item) - if not item:is_empty() then - for group, spells in pairs(enchantable_tools) do - if minetest.get_item_group(item:get_name(), group) ~= 0 then - return group, sorcery.matreg.lookup[item:get_name()] - end - end - end - return false -end local enchanter_update = function(pos) local meta = minetest.get_meta(pos) local inv = meta:get_inventory() local item = inv:get_stack('item',1) local slots = '' - local itype, imat = enchanter_getsubj(item) - if itype and imat and imat.data.slots then + local imat = sorcery.enchant.getsubj(item) + if imat and imat.data.slots then local n = #imat.data.slots local sw, sh = 2.1, 2.1; local w = sw * n; - local spells = sorcery.enchant.get(item).spells + local item_enchantment = sorcery.enchant.get(item) + local spells = item_enchantment.spells for i=1,n do local slot=imat.data.slots[i] local x = (4 - (w/2) + (sw * (i-1))) + 0.2 local offtbl = { @@ -142,12 +46,36 @@ end local hovertitle = 'Empty spell slot'; local conf = tostring(math.floor(slot.confluence*100)) .. '%' local hoverdesc = 'An enchantment of one the following affinities can be anchored into this slot at ' .. conf .. ' confluence'; + local hovercolor = nil for _,sp in pairs(spells) do + local spdata = sorcery.data.enchants[sp.id] if sp.slot == i then + hovercolor = sorcery.lib.color(spdata.tone):readable() hovertitle = sorcery.lib.str.capitalize(sp.id) - hoverdesc = 'An enchantment is anchored in this slot at ' .. conf .. ' confluence' + hoverdesc = sorcery.lib.str.capitalize(spdata.desc) .. '. Anchored in this slot at ' .. conf .. ' confluence' + if sp.boost > 10 then + hoverdesc = hoverdesc .. ' and boosted by ' .. tostring((sp.boost - 10) * 10) .. '%' + elseif sp.boost < 10 then + hoverdesc = hoverdesc .. ' but weakened by ' .. tostring((10 - sp.boost) * 10) .. '%' + end + hoverdesc = hoverdesc .. '.' + local addrune = function(tex) + pwr = pwr .. string.format([[ + image[%f,%f;%f,%f;%s] + ]], x+0.43,y+0.6,sw/2.7,sh/2.7, tex) + end + local rune = 'sorcery_enchant_' .. sp.id .. '.png' + local energy = item_enchantment.energy + local fullenergy = imat.data.maxenergy + if energy <= fullenergy then + addrune(rune .. '^[opacity:110^[lowpart:' .. tostring(math.floor((energy/fullenergy) * 100)) .. '%:' .. rune) + elseif energy == fullenergy then addrune(rune) + elseif energy >= fullenergy then + addrune(rune .. '^[colorize:#ffffff:' .. + tostring(255 * math.min(1,(energy / fullenergy * 2)))) + end break end end slots = slots .. string.format([[ @@ -158,8 +86,9 @@ x+0.20,y+0.16, sw-0.84,sh-0.76, minetest.formspec_escape(sorcery.lib.ui.tooltip { title = hovertitle; desc = hoverdesc; + color = hovercolor; props = ap; }), '#37002C','#FFC8F5' ) .. pwr @@ -178,8 +107,125 @@ listring[current_player;main] listring[context;item] ]] .. slots) end + +sorcery.enchant = {} do + sorcery.enchant.update_enchanter = enchanter_update + local m = sorcery.lib.marshal + local ench_t = m.g.struct { + id = m.t.str; + slot = m.t.u8; + boost = m.t.u8; -- every enchantment has an intrinsic force + -- separate from the confluence of the slot, which is + -- determined by the composition of the wand used to generate + -- it (for instance, a gold-wired wand at low wear, or a wand + -- with specific gemstones, may have a boost level above 10) + -- boost is divided by 10 to yield a float + reliability = m.t.u8; + -- reliability is a value between 0 and 100, where 0 means the + -- spell fails every time and 100 means it never fails. not + -- relevant for every spell + } + local pack, unpack = m.transcoder { + spells = m.g.array(8, ench_t); + energy = m.t.u16; + } + sorcery.enchant.getsubj = function(item) + if not item:is_empty() then + local eligible = {} + for name, spell in pairs(sorcery.data.enchants) do + for g,v in pairs(item:get_definition().groups) do + if v~= 0 and sorcery.lib.tbl.has(spell.groups,g) then + eligible[#eligible+1] = name + goto skip + end + end + ::skip::end + return sorcery.matreg.lookup[item:get_name()], eligible + else return nil end + end + local key = 'sorcery_enchantment_recs' + sorcery.enchant.set = function(stack, data, noup) + local meta = stack:get_meta() + meta:set_string(key, sorcery.lib.str.meta_armor(pack(data),true)) + if not noup then stack=sorcery.enchant.stackup(stack) end + end + sorcery.enchant.get = function(stack) + local meta = stack:get_meta() + if meta:contains(key) then + local data = sorcery.lib.str.meta_dearmor(meta:get_string(key),true) + return unpack(data) + else + return { + spells = {}; + energy = 0; + } + end + end + sorcery.enchant.strength = function(stack,id) + -- this functions should be used whenever you need to + -- determine the power of a particular enchantment on + -- an enchanted item. + local e = sorcery.enchant.get(stack) + local p = 0.0 + local slots = sorcery.matreg.lookup[stack:get_name()].data.slots + -- TODO handle strength-boosting spells! + for _,s in pairs(e.spells) do + print(dump(s)) + if s.id == id then p = p + ((s.boost * slots[s.slot].confluence)/10) end + end + return p + end + sorcery.enchant.stackup = function(stack) + -- stack update function. this should be called whenever + -- the enchantment status of a stack changes; it will + -- alter/reset tool capabilities and tooltip as necessary + local e = sorcery.enchant.get(stack) + local meta = stack:get_meta() + local def = stack:get_definition() + local mat = sorcery.enchant.getsubj(stack) + local done = {} + local props = {} + local interference = {} + -- meta:set_string('tool_capabilities','') + meta:set_tool_capabilities(nil); -- TODO this probably only works + -- in >5.3; maybe bring in the old JSON mechanism so it works in + -- older versions as well? + local basecaps = def.tool_capabilities + for _,s in pairs(e.spells) do + if done[s.id] then goto skip end + done[s.id] = true + local pwr = sorcery.enchant.strength(stack,s.id) + -- somewhat wasteful… + local e = sorcery.data.enchants[s.id] + if e.apply then stack = e.apply(stack,pwr,basecaps) end + props[#props+1] = { + title = e.name; + desc = e.desc; + color = sorcery.lib.color(e.tone); + } + local inf = mat.data.slots[s.slot].interference + if inf then for k,v in pairs(inf) do + interference[k] = interference[k] + v + end end + ::skip::end + if #interference > 0 then + if interference.speed then stack = sorcery.data.enchants.pierce.apply(stack,-interference.speed,basecaps) end + if interference.durability then stack = sorcery.data.enchants.endure.apply(stack,-interference.durability,basecaps) end + end + meta = stack:get_meta() -- necessary? unclear + if #e.spells > 0 then + meta:set_string('description', sorcery.lib.ui.tooltip { + title = 'Enchanted ' .. def.description; + props = props; + }) + else + meta:set_string('description',def.description) + end + return stack + end +end minetest.register_node('sorcery:enchanter', { description = 'Enchanter'; drawtype = 'mesh'; @@ -199,8 +245,9 @@ }; on_construct = function(pos) local meta = minetest.get_meta(pos) local inv = meta:get_inventory() + meta:set_string('infotext','Enchanter') inv:set_size('item', 1) inv:set_size('foci', 3) enchanter_update(pos) end; @@ -208,8 +255,44 @@ on_metadata_inventory_move = enchanter_update; on_metadata_inventory_take = enchanter_update; }) +minetest.register_craftitem('sorcery:enchanter_channeler',{ + inventory_image = 'sorcery_enchanter_channeler.png'; + description = 'Channeler'; +}) +minetest.register_craftitem('sorcery:enchanter_pedestal',{ + inventory_image = 'sorcery_enchanter_pedestal.png'; + description = 'Pedestal'; +}) +minetest.register_craft { + output = 'sorcery:enchanter_channeler'; + recipe = { + {'','default:bronze_ingot',''}; + {'basic_materials:gold_wire','basic_materials:steel_strip','basic_materials:gold_wire'}; + {'','sorcery:electrum_ingot',''}; + }; + replacements = { + {'basic_materials:gold_wire','basic_materials:empty_spool'}; + {'basic_materials:gold_wire','basic_materials:empty_spool'}; + }; +} +minetest.register_craft { + output = 'sorcery:enchanter_pedestal'; + recipe = { + {'basic_materials:copper_strip','group:wood','basic_materials:copper_strip'}; + {'','default:bronze_ingot',''}; + {'','group:wood',''}; + }; +} +minetest.register_craft { + output = 'sorcery:enchanter'; + recipe = { + {'','sorcery:enchanter_channeler',''}; + {'sorcery:enchanter_channeler','sorcery:enchanter_pedestal','sorcery:enchanter_channeler'}; + {'group:wood','group:wood','group:wood'}; + }; +} for i=1,10 do minetest.register_node('sorcery:air_flash_' .. i, { drawtype = 'airlike'; pointable = false; walkable = false; @@ -229,28 +312,57 @@ end minetest.register_on_dignode(function(pos, node, puncher) if puncher == nil then return end -- i don't know why - -- this is necessary but you get rare crashed without it + -- this is necessary but you get rare crashes without it -- we're goint to do something VERY evil here and -- replace the air with a "glow-air" that removes -- itself after a short period of time, to create -- a flash of light when an enchanted tool's used -- to dig out a node local tool = puncher:get_wielded_item() - local meta = tool:get_meta() + local ench = sorcery.enchant.get(tool) + if #ench.spells == 0 then return end + print('enchanted!') local sparks = {} - local spark = function(name,color) - if meta:contains('enchant_' .. name) then - sparks[#sparks + 1] = { - color = color; - count = meta:get_int('enchant_' .. name); - } - end + -- local spark = function(name,color) + local material = sorcery.enchant.getsubj(tool) + local totalcost = 0 + do local done = {} for _,sp in pairs(ench.spells) do + if done[sp.id] then goto skip end + done[sp.id] = true + + local data = sorcery.data.enchants[sp.id] + local strength = sorcery.enchant.strength(tool,sp.id) + local ch = math.random(1,100) + local props = { + fail = ch > sp.reliability; + user = puncher; + pos = pos; + node = node; + tool = tool; + material = material.data; + enchantment = ench; + power = strength; + spell = sp; + sparks = sparks; + cost = data.cost; + } + if data.on_dig then data.on_dig(props) end + if props.cost ~= 0 then totalcost = totalcost + math.max(1,math.floor(props.cost * strength)) end + + if ch > sp.reliability then goto skip end + sparks[#sparks + 1] = { + color = sorcery.lib.color(data.tone):brighten(1.1); + count = strength * 7; + } + ::skip::end end + if totalcost > 0 then + local conservation = sorcery.enchant.strength(tool,'conserve') * 0.5 + totalcost = totalcost - (totalcost * conservation) + ench.energy = math.max(0,ench.energy - (totalcost - (material.data.energysource or 0))) end - spark('durable',sorcery.lib.color(0,89,245)) - spark('fast',sorcery.lib.color(245,147,89)) if #sparks == 0 then return end if math.random(5) == 1 then minetest.set_node(pos, {name='sorcery:air_flash_' .. tostring(math.random(10))}) end @@ -259,9 +371,9 @@ local val = math.random() * span return val + min end for _,s in pairs(sparks) do - for i=0,math.floor(s.count * range(1,3)) do + for i=1,math.floor(s.count * range(1,3)) do local life = range(0.3,1); minetest.add_particle { pos = { x = pos.x + range(-0.5,0.5); @@ -279,17 +391,40 @@ y = range( 0.3,0.9); }; expirationtime = life; size = range(0.5,1.5); - vertical = true; - texture = sorcery.lib.image('sorcery_spark.png'):multiply(s.color:brighten(1.2)):render(); + texture = sorcery.lib.image('sorcery_spark.png'):multiply(s.color):render(); glow = 14; animation = { type = "vertical_frames"; aspect_w = 16; aspect_h = 16; - length = life * 1.1; + length = life + 0.1; }; } end end + + -- destroy spells that can no longer be sustained + if ench.energy == 0 then + ench.spells = {} + sorcery.enchant.set(tool,ench) + else + sorcery.enchant.set(tool,ench,true) + end + puncher:set_wielded_item(tool) end) + +minetest.register_chatcommand('enchants', { + description = 'Log information about the currently held object\'s enchantment'; + privs = { server = true }; + func = function(caller,params) + local tool = minetest.get_player_by_name(caller):get_wielded_item() + minetest.chat_send_player(caller, dump(sorcery.enchant.get(tool))) + local binary = tool:get_meta():get_string('sorcery_enchantment_recs') + local dumpout = '' + for i=1,string.len(binary) do + dumpout = dumpout .. string.format('%x%s',string.byte(binary,i),(i%16==0 and '\n') or ' ') + end + print(dumpout) + end; +})