local hitbox = {
type = 'fixed';
fixed = {
-0.5, -0.5, -0.5;
0.5, 0.1, 0.5;
};
}
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 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 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 = {
[1] = {0};
[2] = {0.3, 0.3};
[3] = {0.3, 0, 0.3};
[4] = {0.3, 0, 0, 0.3};
[5] = {0.3, 0, 0.1, 0, 0.3};
[6] = {0.3, 0.1, 0, 0.1, 0.3};
};
local y = 3.1 - offtbl[n][i]
local affs = #slot.affinity
local iconf = math.min(math.floor(slot.confluence * 10), 20)
local pwr = ''
local ap = {}
for i,aff in pairs(slot.affinity) do
pwr = pwr .. string.format([[
image[%f,%f;%f,%f;sorcery_pentacle_power_%s.png^[verticalframe:20:%u]
]], x,y, sw,sh, aff, math.max(1,iconf - i))
ap[#ap+1] = {
title = sorcery.lib.str.capitalize(aff) .. ' affinity';
color = sorcery.lib.color(sorcery.data.affinities[aff].color);
desc = sorcery.data.affinities[aff].desc;
}
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 = 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([[
image[%f,%f;%f,%f;sorcery_pentacle.png]
tooltip[%f,%f;%f,%f;%s;%s;%s]
]],
x,y, sw,sh,
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
end
end
meta:set_string('formspec', [[
size[8,8.5]
background[-0.25,-0.25;8.5,9;sorcery_enchanter_bg.png;true]
image[2.13,0;4.35,4;sorcery_enchanter_glyphs.png]
list[context;foci;3.5,0;1,1;0]
list[context;item;3.5,1.2;1,1;]
list[context;foci;2.5,2;1,1;1]
list[context;foci;4.5,2;1,1;2]
list[current_player;main;0,4.7;8,4;]
listring[context;foci]
listring[current_player;main]
listring[context;item]
listring[current_player;main]
]] .. 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()
if data then
meta:set_string(key, sorcery.lib.str.meta_armor(pack(data),true))
else
meta:set_string(key, '')
end
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 ct = 0
local slots = sorcery.matreg.lookup[stack:get_name()]
if not (slots and slots.data and slots.data.slots) then return p, ct end
slots = slots.data.slots
-- TODO handle strength-boosting spells!
for _,s in pairs(e.spells) do
if s.id == id then
p = p + ((s.boost * slots[s.slot].confluence)/10)
ct = ct + 1
end
end
return p, ct
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] or 0) + 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';
mesh = 'sorcery-enchanter.obj';
paramtype = 'light';
paramtype2 = 'facedir';
groups = { cracky = 2, dig_immediate = 2, sorcery_magitech = 1 };
sunlight_propagates = true;
selection_box = hitbox;
collision_box = hitbox;
after_dig_node = sorcery.lib.node.purge_container;
tiles = {
"default_obsidian.png";
"default_steel_block.png";
"default_bronze_block.png";
"default_junglewood.png";
"default_gold_block.png";
};
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;
on_metadata_inventory_put = enchanter_update;
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 = {
{'sorcery:grease_enchanting','sorcery:platinum_ingot','sorcery:grease_enchanting'};
{'basic_materials:gold_wire','basic_materials:steel_strip','basic_materials:gold_wire'};
{'','sorcery:electrum_ingot',''};
};
replacements = {
{'sorcery:grease_enchanting','xdecor:bowl'};
{'sorcery:grease_enchanting','xdecor:bowl'};
{'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'};
{'','sorcery:iridium_ingot',''};
{'','group:wood',''};
};
}
minetest.register_craft {
output = 'sorcery:enchanter';
recipe = {
{'sorcery:grease_sealant','sorcery:enchanter_channeler','sorcery:grease_sealant'};
{'sorcery:enchanter_channeler','sorcery:enchanter_pedestal','sorcery:enchanter_channeler'};
{'group:wood','group:wood','group:wood'};
};
replacements = {
{'sorcery:grease_sealant','xdecor:bowl'};
{'sorcery:grease_sealant','xdecor:bowl'};
};
}
for i=1,10 do
minetest.register_node('sorcery:air_flash_' .. i, {
drawtype = 'airlike';
pointable = false; walkable = false;
buildable_to = true;
sunlight_propagates = true;
light_source = i + 4;
groups = {
air = 1, sorcery_air = 1;
not_in_creative_inventory = 1;
};
drop = {max_items = 0, items = {}};
on_blast = function() end; -- not affected by explosions
on_construct = function(pos)
minetest.get_node_timer(pos):start(0.05)
end;
on_timer = function(pos)
if i <= 2 then minetest.remove_node(pos) else
minetest.set_node(pos, {name='sorcery:air_flash_1'})
return true
end
end
});
end
local function enchpwrhud(user, flash, fac)
-- this function displays or changes the HUD element
-- that shows how much energy is left in an enchanted
-- item. it is called by the dig handler and sequences
-- callbacks to remove the HUD from the screen once
-- its timeleft property has been used up. timeleft is
-- reset if the 'flash' argument, which indicates
-- whether the enchantment has just been used, is set
-- to true; otherwise, it is left alone.
--
-- this whole thing is really unfriendly and without
-- FP tricks it would have been intolerably painful
-- to implement. minetest needs better primitives.
local frame = math.ceil(16 * (1-math.min(1,fac)))
if tostring(frame) == '-0' then frame = '0' -- ??????
else frame = tostring(frame) end
local tex = 'sorcery_ui_manaring_' .. (flash and 'flash_' or '') .. frame .. '.png';
local c = sorcery.ctx.get(user)
if not c.hud_ench then
local hid = user:hud_add {
name = 'sorcery:manaring';
hud_elem_type = 'image';
text = tex;
position = { x = 0.5, y = 0.5 };
offset = { x = 0, y = 0 };
scale = { x = 2.0, y = 2.0 };
z_index = 0;
}
c.hud_ench = {
id = hid;
timeleft = 2.0;
fn = false;
fac = fac;
}
c = c.hud_ench
else
c = c.hud_ench
c.fac = fac
user:hud_change(c.id,'text',tex)
if flash then c.timeleft = 2.0 end
end
if c.fn == false then
c.fn = true
local delta = 0.10
-- tried making Δ conditional on 'flash' but it
-- turns out that causes the flash not to always
-- disappear in a timely manner. solving this
-- efficiently would be a major, complex headache
-- so i'm just compromising and setting delta to a
-- constant :/
minetest.after(delta, function()
if not sorcery.ctx.stat(user) then return end
local u = sorcery.ctx.get(user)
local h = u.hud_ench
if not h then return end
if h.timeleft - delta <= 0 then
user:hud_remove(h.id)
u.hud_ench = nil
else
h.timeleft = h.timeleft - delta
h.fn = false
enchpwrhud(user, false, h.fac)
end
end)
end
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 crashes without it
-- perform leyline checks and call notify if necessary
if minetest.get_item_group(node.name, 'sorcery_ley_device') ~= 0 then
sorcery.lib.node.notifyneighbors(pos)
end
-- is there an active disjunction in effect here?
-- if so, return immediately and perform no magic
local probe = sorcery.spell.probe(pos)
if probe.disjunction then return end
-- 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 ench = sorcery.enchant.get(tool)
if #ench.spells == 0 then return end
local sparks = {}
-- 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, nsp = 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,props.cost * nsp) --[[math.max(1,math.floor(props.cost * strength))]] end
-- strength does not increase cost but number of spell slots occupied does -- incentive for the player to be more clever, less brute-force-y
if ch > sp.reliability then goto skip end
sparks[#sparks + 1] = {
color = sorcery.lib.color(data.tone):brighten(1.1);
count = strength * 8;
}
::skip::end end
if totalcost > 0 then
local conservation = math.floor(sorcery.enchant.strength(tool,'conserve') * 6)
-- totalcost = totalcost - (totalcost * conservation)
if conservation == 0 or math.random(conservation) == 1 then
ench.energy = math.max(0,ench.energy - (totalcost - (material.data.energysource or 0)))
end
end
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
local range = function(min, max)
local span = max - min
local val = math.random() * span
return val + min
end
for _,s in pairs(sparks) 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);
z = pos.z + range(-0.5,0.5);
y = pos.y + range(-0.5,0.5);
};
acceleration = {
x = range(-0.5,0.5);
z = range(-0.5,0.5);
y = -0.1;
};
velocity = {
x = range(-1.3,1.3);
z = range(-1.3,1.3);
y = range( 0.3,0.9);
};
expirationtime = life;
size = range(0.2,1.5);
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 + 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)
local epct = ench.energy / material.data.maxenergy
enchpwrhud(puncher, true, epct)
end)