@@ -1,53 +1,25 @@ +local target_node = function(ctx,tgt) + if not ctx.target or ctx.target.type ~= 'node' then return false end + local node = minetest.get_node(ctx.target.under) + if node.name ~= tgt then return false end + return node +end; + +local get_enchanter = function(ctx) + local ench = target_node(ctx, 'sorcery:enchanter') + if not ench then return false end + return minetest.get_meta(ctx.target.under):get_inventory() +end + local cast_sparkle = function(ctx,color,strength,duration) - minetest.add_particlespawner { - amount = 70 * strength; - time = duration or 1.5; - attached = ctx.caster; - texture = sorcery.lib.image('sorcery_spark.png'):multiply(color):render(); - minpos = { x = -0.1, z = 0.5, y = 1.2}; - maxpos = { x = 0.1, z = 0.3, y = 1.6}; - minvel = { x = -0.5, z = -0.5, y = -0.5}; - maxvel = { x = 0.5, z = 0.5, y = 0.5}; - minacc = { x = 0.0, z = 0.0, y = 0.5}; - maxacc = { x = 0.0, z = 0.0, y = 0.5}; - minsize = 0.4, maxsize = 0.8; - minexptime = 1, maxexptime = 1; - glow = 14; - animation = { - type = 'vertical_frames'; - aspect_w = 16; - aspect_h = 16; - length = 1.1; - }; - } + sorcery.vfx.cast_sparkle(ctx.caster,color,strength,duration) +end + +local enchantment_sparkle = function(ctx,color) + sorcery.vfx.enchantment_sparkle(ctx.target,color) end -local enchantment_sparkle = function(ctx,color) - local minvel, maxvel - if minetest.get_node(vector.add(ctx.target.under,{y=1,z=0,x=0})).name == 'air' then - minvel = {x=0,z=0,y= 0.3} maxvel = {x=0,z=0,y= 1.5}; - else - local dir = vector.subtract(ctx.target.under,ctx.target.above) - minvel = vector.multiply(dir, 0.3) - maxvel = vector.multiply(dir, 1.2) - end - return minetest.add_particlespawner { - amount = 50; - time = 0.5; - minpos = vector.subtract(ctx.target.under, 0.5); - maxpos = vector.add(ctx.target.under, 0.5); - minvel = minvel, maxvel = maxvel; - minexptime = 1, maxexptime = 2; - minsize = 0.5, maxsize = 2; - texture = sorcery.lib.image('sorcery_spark.png'):multiply(color):render(); - animation = { - type = 'vertical_frames'; - aspect_w = 16, aspect_h = 16; - length = 2; - }; - glow = 14; - } -end + local anchorwand = function(aff,uses,recipe) local affcolor = sorcery.lib.color(sorcery.data.affinities[aff].color) return { name = aff .. ' anchor'; @@ -56,11 +28,10 @@ affinity = recipe; color = affcolor; sound = 'xdecor_enchanting'; -- FIXME make own cast = function(ctx) - if (not ctx.target) or ctx.target.type ~= 'node' then return false end - local node = minetest.get_node(ctx.target.under) - if node.name ~= 'sorcery:enchanter' then return false end + local node = target_node(ctx, 'sorcery:enchanter') + if not node then return false end local inv = minetest.get_meta(ctx.target.under):get_inventory() if inv:is_empty('item') then return false end local subj = inv:get_stack('item',1) @@ -196,23 +167,29 @@ return { flame = { name = 'flamebolt'; color = {255,89,16}; - uses = 64; + uses = 32; affinity = {'acacia','blazing'}; leytype = 'praxic'; desc = 'Conjure a gout of fire to scorch your foes with a flick of this wand'; cast = function(ctx) local speed = 30 -- TODO maybe amethyst tip increases speed? + local radius + if ctx.base.gem == 'sapphire' + then radius = math.random(2,3) + else radius = math.random(1,2) + end local heading = ctx.heading - heading.pos.y = heading.pos.y + 1.5 -- TODO maths + heading.pos.y = heading.pos.y + heading.eyeheight*0.9 local bolt = minetest.add_entity(heading.pos,'sorcery:spell_projectile_flamebolt') bolt:set_rotation(heading.yaw) local vel = { x = heading.yaw.x * speed; y = heading.yaw.y * speed; z = heading.yaw.z * speed; }; + bolt:get_luaentity()._blastradius = radius bolt:set_velocity(vel) end; }; seal = { @@ -226,9 +203,13 @@ if ctx.target == nil or ctx.target.type ~= 'node' then return false end local meta = minetest.get_meta(ctx.target.under) -- first we need to check if the wand has an identifying 'key' yet, -- and set one if not. - local wandmode = ctx.base.gem == 'sapphire' + local modes = { + sapphire = 'lockdown'; + diamond = 'steal'; + } + local wandmode = modes[ctx.base.gem] or 'seal' local keycode if ctx.meta:contains('sorcery_wand_key') then keycode = ctx.meta:get_string('sorcery_wand_key') else @@ -237,8 +218,16 @@ -- ctx.meta:mark_as_private('sorcery_wand_key') end if meta:contains('owner') then -- owner is already set -- can we break the enchantment? + if wandmode == 'steal' then + if meta:get_string('owner') ~= ctx.caster:get_player_name() then + meta:set_string('owner',ctx.caster:get_player_name()) + enchantment_sparkle(ctx,sorcery.lib.color(101,255,238)) + end + return + end + if meta:get_string('sorcery_wand_key') == keycode then meta:set_string('owner','') meta:set_string('sorcery_wand_key','') meta:set_string('sorcery_seal_mode','') @@ -247,9 +236,9 @@ else meta:set_string('sorcery_wand_key',keycode) meta:mark_as_private('sorcery_wand_key') meta:set_string('owner',ctx.caster:get_player_name()) - if wandmode then + if wandmode == 'lockdown' then meta:set_string('sorcery_seal_mode','wand') end enchantment_sparkle(ctx,sorcery.lib.color(255,201,27)) end @@ -377,34 +366,133 @@ uses = 48; leytype = 'syncretic'; color = {172,65,255}; affinity = {'apple','verdant'}; - desc = 'Meld the properties of three balanced items on an enchanter to create a new one with special properties, but destroying the old ones and losing two thirds of the mass in the process. The precise outcome is not always predictable.'; + desc = 'Meld the properties of three balanced items on an enchanter to create a new one with special properties, but destroying the old ones and losing two thirds of the mass in the process. The precise outcome is not always predictable, and may vary with the moons and the stars.'; + cast = function(ctx) + local e = get_enchanter(ctx) + if not e then return false end + + for _,m in pairs(sorcery.data.resonance.meld) do + if m.restrict and not m.restrict(ctx) + then goto next_meld end + + local g = {} + for i,set in ipairs(m.set) do + if type(set) == 'table' then + g[i] = set + else g[i] = { take = set; } end + + local found = false + for j=1,e:get_size('foci') do + local match,res = sorcery.lib.item.groupmatch(g[i].take, e:get_stack('foci',j),false) + if match then + found = true + g[i].slot = j + g[i].leftover = res + break + end + end + if not found then goto next_meld end + end + -- we've made it past the tests; this meld + -- matches the spec + + for _,t in pairs(g) do + if t.leftover and t.leftover:get_count() > 0 then + e:set_stack('foci',t.slot,t.leftover) + if t.replacement then + minetest.add_item(ctx.target.above, ItemStack(t.replacement)) + end + else + e:set_stack('foci',t.slot,ItemStack(t.replacement)) + end + end + + local res + if type(m.results) == 'function' then + res = m.results(ctx) + elseif type(m.results) == 'table' and m.results[1] then -- haaaack + res = select(2,sorcery.lib.tbl.pick(m.results)) + else + res = m.results + end + + e:set_stack('item',1,ItemStack(res)) + enchantment_sparkle(ctx,sorcery.lib.color(228,4,201)) + ::next_meld::end + end; }; divide = { name = 'division'; uses = 19; leytype = 'syncretic'; color = {255,65,121}; affinity = {'apple','shimmering'}; - desc = 'Shatter an item on an enchanter, dividing its essence equally into three parts and precipitating it into new items embodying various properties of the destroyed item. The outcome is not always predictable.'; + desc = 'Shatter an item on an enchanter, dividing its essence equally into three parts and precipitating it into new items embodying various properties of the destroyed item. The outcome is not always predictable, and may vary with the moons and the stars.'; + cast = function(ctx) + local e = get_enchanter(ctx) + if not e then return false end + + local orig = e:get_stack('item',1) + local div = sorcery.data.resonance.divide[orig:get_name()] + if not div then return false end + + local bitch = function(err) + sorcery.log('data/spells(divide)', err .. ' for ' .. orig:get_name()) + return false + end + + if not (div.mode and div.give) then + return bitch('improperly specified division') + end + + if div.restrict and not div.restrict(ctx) then + return false + end + + local dst + if div.mode == 'any' then + local lst = sorcery.lib.tbl.cshuf(div.give) + dst = function(i) return lst[i] end + elseif div.mode == 'random' then + dst = function() return sorcery.lib.tbl.pick(div.give) end + elseif div.mode == 'set' then + dst = function(i) return div.give[i] end + elseif div.mode == 'all' then + dst = function() return div.give end + elseif div.mode == 'fn' then + dst = function(i) return div.give(i,ctx) end + else return bitch('invalid division mode') end + for i=1,e:get_size('foci') do + e:set_stack('foci',i,ItemStack(dst(i))) + end + e:set_stack('item',1,ItemStack(div.replacement)) + + for _,color in pairs{{245,63,63},{63,245,178}} do + enchantment_sparkle(ctx, sorcery.lib.color(color)) + end + end; }; obliterate = { name = 'obliteration'; uses = 129; color = {175,6,212}; affinity = {'aspen','dark'}; leytype = 'occlutic'; - desc = 'Totally and irreversibly obliterate all items on an enchanter.'; + desc = 'Incinerate all items on an enchanter, rendering them down to ash or obliterating them entirely.'; cast = function(ctx) - if not ctx.target or ctx.target.type ~= 'node' then return false end - local tgt = minetest.get_node(ctx.target.under) - if tgt.name ~= 'sorcery:enchanter' then return false end + local tgt = target_node(ctx, 'sorcery:enchanter') + if not tgt then return false end local inv = minetest.get_meta(ctx.target.under):get_inventory() for _,name in pairs{'foci','item'} do for i=1,inv:get_size(name) do - inv:set_stack(name,i,ItemStack(nil)) + local stack = 'sorcery:ash' + if ctx.base.gem == 'sapphire' then + stack = nil + end + inv:set_stack(name,i,ItemStack(stack)) end end enchantment_sparkle(ctx,sorcery.lib.color(255,12,0)) @@ -433,9 +521,10 @@ uses = 7; color = {255,90,18}; leytype = 'imperic'; affinity = {'aspen','shimmering','dark','blazing'}; - desc = 'Transmute three ingots into one of a different metal, determined by chance and influenced by configuration of the wand'; + desc = 'Transmute three ingots into one of a different metal, determined by chance, and influenced by configuration of the wand as well as the stars and the phase of the moon'; + -- diamond = quantity varies between 1-3 }; disjoin = { name = 'disjunction'; uses = 32; @@ -442,8 +531,26 @@ color = {17,6,212}; leytype = 'occlutic'; affinity = {'jungle','silent'}; desc = 'With an enchanter, disjoin the anchor holding a spell into an object so a new spell can instead be bound in'; + cast = function(ctx) + local ench = target_node(ctx, 'sorcery:enchanter') + if not ench then return false end + local ei = minetest.get_meta(ctx.target.under):get_inventory() + local item = ei:get_stack('item',1) + local e = sorcery.enchant.get(item) + if next(e.spells) == nil then return false end + if #e.spells == 1 then e = nil else + if ctx.base.gem == 'sapphire' + then e.spells = {} e.energy = 0 + else table.remove(e.spells, math.random(#e.spells)) + end + end + sorcery.enchant.set(item,e) + ei:set_stack('item',1,item) + enchantment_sparkle(ctx,sorcery.lib.color(255,154,44)) + enchantment_sparkle(ctx,sorcery.lib.color(226,44,255)) + end; }; divine = { name = 'divining'; desc = 'Steal away the secrets of the cosmos'; @@ -458,9 +565,9 @@ if stack:is_empty() then return nil end if minetest.get_item_group(stack:get_name(), 'dye') == 0 then return nil end for _,ink in pairs(inks) do if minetest.get_item_group(stack:get_name(), 'color_' ..ink) ~= 0 - then print('found',ink,'ink') return ink end + then return ink end end end if not ctx.target or ctx.target.type ~= 'node' then return false end local tgt = minetest.get_node(ctx.target.under) @@ -475,9 +582,8 @@ local restrict, kind, mod = {} do local ms = inv:get_stack('foci',1) if not ms:is_empty() then mod = ms:get_name() end end - print(ink1,ink2,mod) if ink1 == 'black' and ink2 == 'black' then kind = 'craft' if mod then if mod == sorcery.data.metals.cobalt.parts.powder then restrict.group = 'sorcery_magitech' @@ -519,9 +625,9 @@ restrict.aff = 'counterpraxic' elseif mod == sorcery.data.metals.aluminum.parts.powder then restrict.aff = 'syncretic' elseif mod == sorcery.data.metals.lithium.parts.powder then - -- restrict.aff = 'mandatic' -- no enchants yet, will cause infinite loop + -- restrict.aff = 'mandatic' -- no enchants yet, will cause infinite loop 🙃 elseif mod == sorcery.data.metals.iridium.parts.powder then restrict.aff = 'entropic' elseif mod == sorcery.data.metals.gold.parts.powder then restrict.aff = 'cognic' @@ -533,11 +639,9 @@ end elseif ink1 == 'red' and ink2 == 'yellow' then kind = 'cook'; -- elseif ink1 == 'red' and ink2 == 'orange' then kind = 'smelt'; end - print('result',kind,dump(restrict)) if kind then - print('found kind') local rec = ItemStack('sorcery:recipe') local m = rec:get_meta() if ctx.base.gem == 'diamond' then -- make recipe for thing in slot 1 @@ -577,9 +681,9 @@ cast = function(ctx) local center = ctx.heading.pos local maxpower = 20 local power = (ctx.base.gem == 'sapphire' and maxpower) or maxpower/2 - local range = (ctx.base.gem == 'emerald' and 10) or 5 + local range = (ctx.base.gem == 'emerald' and 6) or 3 local duration = (ctx.base.gem == 'amethyst' and 60) or 30 if ctx.base.gem == 'diamond' then power = power * (math.random()*2) range = range * (math.random()*2)