Index: coins.lua ================================================================== --- coins.lua +++ coins.lua @@ -181,10 +181,11 @@ local i = inv:get_stack(slot,1) i:take_item(items_used) inv:set_stack(slot,1,i) end reduce_slot('ingot') if not inv:is_empty('gem') then reduce_slot('gem') end + minetest.sound_play('sorcery_coins', { pos = pos, gain = 0.7 }) end update_press_output(meta) end; }) end Index: data/metals.lua ================================================================== --- data/metals.lua +++ data/metals.lua @@ -220,11 +220,11 @@ affinity = {'counterpraxic'}; confluence = 0.65; interference = {speed = 1}; }; }; - amulet = {}; + amulet = { power = 1.5 }; }; lithium = { tone = {255,252,93}, alpha = 80; dye = 'yellow'; rarity = 13; @@ -280,11 +280,11 @@ }; slots = { {affinity={'counterpraxic','syncretic'}, confluence = 1.1}; {affinity={'cognic','entropic'}, confluence = 0.8}; }; - amulet = {}; + amulet = { power = 1.7 }; }; duridium = { tone = {255,64,175}, alpha = 70; cooktime = 120; artificial = true; Index: data/oils.lua ================================================================== --- data/oils.lua +++ data/oils.lua @@ -97,7 +97,17 @@ 'sorcery:extract_onion'; 'farming:peas'; 'farming:peas'; 'farming:peas'; }; + }; + luscious = { + color = {10,255,10}; + mix = { + 'sorcery:extract_marram'; + 'sorcery:extract_grape'; + 'farming:cocoa_beans'; + 'farming:sugar'; + 'farming:sugar'; + }; }; } Index: data/potions.lua ================================================================== --- data/potions.lua +++ data/potions.lua @@ -33,6 +33,10 @@ }; Isolating = { color = {188,78,225}; infusion = 'sorcery:extract_fern'; }; + Subtle = { + color = {230,253,150}, glow = 6; + infusion = 'sorcery:oil_luscious'; + }; } Index: data/runes.lua ================================================================== --- data/runes.lua +++ data/runes.lua @@ -11,11 +11,16 @@ minpower = 3; rarity = 15; amulets = { amethyst = { name = 'Joining'; - desc = 'Give this amulet to another and they can arrive at your side in a flash from anywhere in the world — though returning whence they came may be a more difficult matter'; + desc = 'Give this amulet to another and they can arrive safely at your side in a flash from anywhere in the world — though returning whence they came may be a more difficult matter'; + apply = function(ctx) + local maker = ctx.user:get_player_name() + ctx.meta:set_string('rune_join_target',maker) + end; + remove = function(ctx) ctx.meta:set_string('rune_join_target','') end; frame = { gold = { name = 'Exchange'; desc = 'Give this amulet to another and they will be able to trade places with you no matter where in the world each of you might be.'; }; @@ -29,11 +34,36 @@ }; }; }; sapphire = { name = 'Return'; - desc = 'Use this amulet once to bind it to a particular point in the world, then use it again to return instantly to that point.'; + desc = 'Use this amulet once to bind it to a particular point in the world, then discharge its spell to return instantly to that point.'; + remove = function(ctx) + ctx.meta:set_string('rune_return_dest','') + end; + cast = function(ctx) + if not ctx.meta:contains('rune_return_dest') then + local pos = ctx.caster:get_pos() + ctx.meta:set_string('rune_return_dest',minetest.pos_to_string(pos)) + return true -- play effects but do not break spell + else + local pos = minetest.string_to_pos(ctx.meta:get_string('rune_return_dest')) + ctx.meta:set_string('rune_return_dest','') + local subjects = { ctx.caster } + local center = ctx.caster:get_pos() + ctx.sparkle = false + for _,s in pairs(subjects) do + local offset = vector.subtract(s:get_pos(), center) + local pt = sorcery.lib.node.get_arrival_point(vector.add(pos,offset)) + if pt then + sorcery.vfx.body_sparkle(s,sorcery.lib.color(20,120,255),2) + sorcery.vfx.body_sparkle(nil,sorcery.lib.color(20,255,120),2,pt) + s:set_pos(pt) + end + end + end + end; frame = { iridium = { name = 'Mass Return'; desc = 'Use this amulet once to bind it to a particular point in the world, then carry yourself and everyone around you back to that point in a flash simply by using it again'; }; @@ -116,10 +146,19 @@ desc = 'Tear a violent wound in the earth with the destructive force of this amulet'; }; diamond = { name = 'Killing'; desc = 'Wield this amulet against a foe to instantly snuff the life out of their mortal form, regardless of their physical protections.'; + cast = function(ctx) + if not (ctx.target and ctx.target.type == 'object') then return false end + local tgt = ctx.target.ref + if not minetest.is_player(obj) then return false end + local tgth = tgt:get_properties().eye_height + sorcery.vfx.bloodburst(vector.add(tgt:get_pos(),{x=0,y=tgth/2,z=0}),20) + minetest.sound_play('sorcery_bloody_burst', { pos = pos, gain = 1.5 }) + tgt:set_hp(0) + end; frame = { iridium = { name = 'Massacre'; desc = "Unleash the dark and wicked force that lurks within this fell amulet to instantaneously slay all those who surround you, friend and foe alike"; }; @@ -181,13 +220,73 @@ name = 'Dominate'; tone = {235,0,228}; minpower = 4; rarity = 40; amulets = { + amethyst = { + name = 'Suffocation'; + desc = 'Wrap this spell tightly around your victim\'s throat, cutting off their oxygen until you release them.'; + }; ruby = { name = 'Exsanguination'; desc = 'Rip the life force out of another, leaving them on the brink of death, and use it to mend your own wounds and invigorate your own being'; + cast = function(ctx) + if not (ctx.target and ctx.target.type == 'object') then return false end + local tgt = ctx.target.ref + local takefac = math.min(99,50 + (ctx.stats.power * 5)) / 100 + local dmg = tgt:get_hp() * takefac + print("!!! dmg calc",takefac,dmg,tgt:get_hp()) + + local numhits = math.random(6,10+ctx.stats.power/2) + local function dohit(hitsleft) + if tgt == nil or tgt:get_properties() == nil then return end + tgt:punch(ctx.caster, 1, { + full_punch_interval = 1; + damage_groups = { fleshy = dmg / numhits } + }) + local tgth = tgt:get_properties().eye_height + sorcery.vfx.bloodburst(vector.add(tgt:get_pos(),{x=0,y=tgth/2,z=0}),math.random(10 * takefac, 40 * takefac)) + ctx.caster:set_hp(ctx.caster:get_hp() + math.max(1,(dmg/numhits)*takefac)) + + local sound = {'sorcery_bloody_hit','sorcery_crunch',false} + sound = sound[math.random(#sound)] + if sound ~= false then + minetest.sound_play(sound, { pos = pos, gain = math.random(5,15)*0.1 }) + end + + local nexthit = math.random() * 0.4 + 0.1 + local dir = vector.subtract(ctx.caster:get_pos(), tgt:get_pos()) + local spark = sorcery.lib.image('sorcery_spark.png') + minetest.add_particlespawner { + amount = math.random(80*takefac,150*takefac); + texture = spark:blit(spark:multiply(sorcery.lib.color(255,20,10))):render(); + time = nexthit; + attached = tgt; + minpos = {x = -0.3, y = -0.5, z = -0.3}; + maxpos = {x = 0.3, y = tgth, z = 0.3}; + minvel = vector.multiply(dir,0.5); + maxvel = vector.multiply(dir,0.9); + minacc = vector.multiply(dir,0.1); + maxacc = vector.multiply(dir,0.2); + minexptime = nexthit * 1.5; + maxexptime = nexthit * 2; + minsize = 0.5; + maxsize = 5 * takefac; + glow = 14; + animation = { + type = 'vertical_frames'; + aspect_w = 16, aspect_h = 16; + length = nexthit*2 + 0.1; + }; + } + + if hitsleft > 0 then + minetest.after(nexthit, function() dohit(hitsleft-1) end) + end + end + dohit(numhits) + end; }; amethyst = { name = 'Disarming'; desc = 'Wield this amulet against a foe to rip all the weapons in their possession out of their grasp'; frame = { Index: enchanter.lua ================================================================== --- enchanter.lua +++ enchanter.lua @@ -313,10 +313,12 @@ 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 Index: forcefield.lua ================================================================== --- forcefield.lua +++ forcefield.lua @@ -47,10 +47,12 @@ walkable = true; pointable = false; sunlight_propagates = true; paramtype = 'light'; light_source = i; + drop = {max_items = 0, items = {}}; + on_blast = function() end; -- not affected by explosions tiles = {'sorcery_transparent.png'}; groups = { air = 1; sorcery_air = 1; sorcery_force_barrier = i; Index: gems.lua ================================================================== --- gems.lua +++ gems.lua @@ -54,15 +54,51 @@ end if not gem.foreign_amulet then local img = sorcery.lib.image local img_stone = img('sorcery_amulet.png'):multiply(sorcery.lib.color(gem.tone)) local img_sparkle = img('sorcery_amulet_sparkle.png') + local useamulet = function(stack,user,target) + local sp = sorcery.amulet.getspell(stack) + if not sp or not sp.cast then return nil end + local stats = sorcery.amulet.stats(stack) + + local ctx = { + caster = user; + target = target; + stats = stats; + sound = "xdecor_enchanting"; --FIXME make own sounds + sparkle = true; + amulet = stack; + meta = stack:get_meta(); -- avoid spell boilerplate + color = sorcery.lib.color(sp.tone); + } + print('casting') + local res = sp.cast(ctx) + + if res == nil or res == true then + minetest.sound_play(ctx.sound, { + pos = user:get_pos(); + gain = 1; + }) + end + if ctx.sparkle then + sorcery.vfx.cast_sparkle(user, ctx.color, stats.power,0.5) + end + if res == nil then + if not minetest.check_player_privs(user, 'sorcery:infinirune') then + sorcery.amulet.setrune(stack) + end + end + + return ctx.amulet + end; minetest.register_craftitem(amuletname, { description = sorcery.lib.str.capitalize(name) .. ' amulet'; inventory_image = img_sparkle:blit(img_stone):render(); wield_scale = { x = 0.6, y = 0.6, z = 0.6 }; groups = { sorcery_amulet = 1 }; + on_use = useamulet; _sorcery = { material = { gem = true, id = name, data = gem; value = (5 * shards_per_gem) + 4; }; @@ -76,10 +112,11 @@ minetest.register_craftitem(framedid, { description = string.format("%s-framed %s amulet",sorcery.lib.str.capitalize(metalid), name); inventory_image = img_sparkle:blit(img_frame):blit(img_stone):render(); wield_scale = { x = 0.6, y = 0.6, z = 0.6 }; groups = { sorcery_amulet = 1 }; + on_use = useamulet; _sorcery = { amulet = { base = name, frame = metalid }; }; }) local frag = metal.parts.fragment Index: init.lua ================================================================== --- init.lua +++ init.lua @@ -130,10 +130,10 @@ 'tnodes'; 'forcefield'; 'farcaster'; 'portal'; 'cookbook', 'writing'; 'disassembly'; 'displacer'; 'gravitator'; 'precipitator'; 'calendar', 'astrolabe'; 'keypunch'; 'runeforge'; - 'admin'; + 'privs', 'admin'; } do sorcery.load(u) end sorcery.stage('finalize') sorcery.registry.defercheck() Index: lib/node.lua ================================================================== --- lib/node.lua +++ lib/node.lua @@ -65,10 +65,28 @@ purge_only = function(lst) return function(...) return purge_container(lst, ...) end end; + + is_air = function(pos) + local n = sorcery.lib.node.force(pos) + if n.name == 'air' then return true end + local d = minetest.registered_nodes[n.name] + if not d then return false end + return not d.walkable + end; + + get_arrival_point = function(pos) + local air = sorcery.lib.node.is_air + if air(pos) then + local n = {x=0,y=1,z=0} + if air(vector.add(pos,n)) then return pos end + local down = vector.subtract(pos,n) + if air(down) then return down end + else return nil end + end; amass = function(startpoint,names,directions) if not directions then directions = ofs.neighbors end local nodes, positions, checked = {},{},{} local checkedp = function(pos) Index: potions.lua ================================================================== --- potions.lua +++ potions.lua @@ -1,9 +1,12 @@ local u = sorcery.lib sorcery.registry.mk('infusions',false) sorcery.registry.mk('residue',false) +sorcery.register_potion_tbl = function(tbl) -- :/ + return sorcery.register_potion(tbl.name,tbl.label,tbl.desc,tbl.color,tbl.imgvariant,tbl.glow,tbl.extra) +end sorcery.register_potion = function(name,label,desc,color,imgvariant,glow,extra) local image = 'sorcery_liquid_'..(imgvariant or 'dull')..'.png' .. '^[multiply:'..tostring(color).. '^vessels_glass_bottle.png' @@ -21,11 +24,11 @@ drawtype = "plantlike"; tiles = {image}; inventory_image = image; paramtype = "light"; is_ground_content = false; - light_source = glow or 0; + light_source = glow and math.min(minetest.LIGHT_MAX,glow) or 0; drop = 'sorcery:' .. name; preserve_metadata = function(pos,node,meta,newstack) newstack[1]:get_meta():from_table(meta) end; walkable = false; ADDED privs.lua Index: privs.lua ================================================================== --- privs.lua +++ privs.lua @@ -0,0 +1,5 @@ +minetest.register_privilege('sorcery:infinirune', { + description = "runes don't discharge upon use, for debugging use only"; + give_to_singleplayer = false; + give_to_admin = false; +}) Index: runeforge.lua ================================================================== --- runeforge.lua +++ runeforge.lua @@ -1,15 +1,39 @@ +-- TODO make some kind of disposable "filter" tool that runeforges require +-- to generate runes and that wears down over time, to make amulets more +-- expensive than they currently are? the existing system is neat but +-- i think amulets are a little overpowered for something that just +-- passively consumes ley-current + local constants = { - rune_mine_interval = 90; + rune_mine_interval = 250; -- how often a powered forge rolls for new runes rune_cache_max = 4; -- how many runes a runeforge can hold at a time - rune_grades = {'Fragile', 'Shoddy', 'Ordinary', 'Pristine'}; + rune_grades = {'Fragile', 'Weak', 'Ordinary', 'Pristine', 'Sublime'}; -- how many grades of rune quality/power there are + + amulet_grades = {'Slight', 'Minor', 'Major', 'Grand', 'Ultimate' }; + -- what kind of amulet each rune grade translates to + + phial_kinds = { + lesser = {grade = 1; name = 'Lesser'; infusion = 'sorcery:powder_brass'}; + simple = {grade = 2; name = 'Simple'; infusion = 'sorcery:powder_silver'}; + great = {grade = 3; name = 'Great'; infusion = 'sorcery:powder_gold'}; + splendid = {grade = 4; name = 'Splendid'; infusion = 'sorcery:powder_electrum'}; + exalted = {grade = 5; name = 'Exalted'; infusion = 'sorcery:powder_levitanium'}; + supreme = {grade = 6; name = 'Supreme'; infusion = 'sorcery:essence_force'}; + }; } +local calc_phial_props = function(phial) --> mine interval: float, time factor: float + local g = phial:get_definition()._proto.grade + local i = constants.rune_mine_interval + local fac = (g-1) / 5 + return i - ((i*0.5) * fac), 0.5 * fac +end sorcery.register.runes.foreach('sorcery:generate',{},function(name,rune) local id = 'sorcery:rune_' .. name rune.image = rune.image or string.format('sorcery_rune_%s.png',name) rune.item = id minetest.register_craftitem(id, { @@ -22,10 +46,73 @@ not_in_creative_inventory = 1; }; _proto = { id = name, data = rune; }; }) end) + +for name,p in pairs(constants.phial_kinds) do + local f = string.format + local color = sorcery.lib.color(255,27,188) + local fac = p.grade / 6 + local id = f('phial_%s', name); + sorcery.register_potion_tbl { + name = id; + label = f('%s Phial',p.name); + desc = "A powerful liquid consumed in the operation of a rune forge. Its quality determines how fast new runes can be constructed."; + color = color:brighten(1 + fac*0.5); + imgvariant = (fac >= 5) and 'sparkle' or 'dull'; + glow = 5+p.grade; + extra = { + groups = { sorcery_phial = p.grade }; + _proto = { id = name, data = p }; + }; + } + sorcery.register.infusions.link { + infuse = p.infusion; + into = 'sorcery:potion_subtle'; + output = id; + } +end + +local register_rune_wrench = function(w) + local mp = sorcery.data.metals[w.metal].parts + minetest.register_tool(w.name, { + description = w.desc; + inventory_image = w.img; + groups = { + sorcery_magitech = 1; + sorcery_rune_wrench = 1; + crafttool = 50; + }; + _proto = w; + _sorcery = { + recipe = { note = w.note }; + }; + }) + minetest.register_craft { + output = w.name; + recipe = { + {'', mp.fragment,''}; + {'', mp.ingot, mp.fragment}; + {'sorcery:vidrium_fragment','', ''}; + }; + } +end + +register_rune_wrench { + name = 'sorcery:rune_wrench', desc = 'Rune Wrench'; + img = 'sorcery_rune_wrench.png', metal = 'brass'; + powers = { imbue = 30 }; + note = 'A runeworking tool used to imbue amulets with enchantments'; +} + +register_rune_wrench { + name = 'sorcery:rune_wrench_iridium', desc = 'Iridium Rune Wrench'; + img = 'sorcery_rune_wrench_iridium.png', metal = 'iridium'; + powers = { imbue = 80, extract = 40 }; + note = 'A rare and powerful runeworking tool used to imbue amulets with enchantments, or extract runes intact from enchanted amulets'; +} local rune_set = function(stack,r) local m = stack:get_meta() local def = stack:get_definition()._proto.data local grade @@ -38,34 +125,63 @@ m:set_int('rune_grade',grade) m:set_string('description',title) end sorcery.amulet = {} -sorcery.amulet.setrune = function(stack,rune) +sorcery.amulet.setrune = function(stack,rune,user) local m = stack:get_meta() if rune then local rp = rune:get_definition()._proto local rg = rune:get_meta():get_int('rune_grade') m:set_string('amulet_rune', rp.id) m:set_int('amulet_rune_grade', rg) local spell = sorcery.amulet.getspell(stack) if not spell then return nil end - local name = string.format('Amulet of %s', spell.name) - + local name = string.format('Amulet of %s %s', constants.amulet_grades[rg], spell.name) m:set_string('description', sorcery.lib.ui.tooltip { title = name; color = spell.tone; desc = spell.desc; }) + + if spell.apply then spell.apply { + stack = stack; + meta = m; + user = user; + self = spell; + } end else + local spell = sorcery.amulet.getspell(stack) m:set_string('description','') m:set_string('amulet_rune','') m:set_string('amulet_rune_grade','') + if spell and spell.remove then spell.remove { + stack = stack; + meta = m; + user = user; + self = spell; + } end end return stack end + +sorcery.amulet.stats = function(stack) + local spell = sorcery.amulet.getspell(stack) + if not spell then return nil end + local power = spell.grade + + if spell.base_spell then + -- only consider the default effect of the frame metal + -- if the frame doesn't totally override the spell + power = power * (spell.framestats and spell.framestats.power or 1) + end + + return { + power = power; + } +end sorcery.amulet.getrune = function(stack) local m = stack:get_meta() if not m:contains('amulet_rune') then return nil end local rune = m:get_string('amulet_rune') @@ -76,30 +192,39 @@ end sorcery.amulet.getspell = function(stack) local m = stack:get_meta() local proto = stack:get_definition()._sorcery.amulet + if not m:contains('amulet_rune') then return nil end local rune = m:get_string('amulet_rune') + local rg = m:get_string('amulet_rune_grade') local rd = sorcery.data.runes[rune] local spell = rd.amulets[proto.base] if not spell then return nil end - local title,desc,cast = spell.name, spell.desc, spell.cast + local title,desc,cast,apply,remove = spell.name, spell.desc, spell.cast, spell.apply, spell.remove -- FIXME in serious need of refactoring + local base_spell = true if proto.frame and spell.frame and spell.frame[proto.frame] then local sp = spell.frame[proto.frame] title = sp.name or title desc = sp.desc or desc cast = sp.desc or cast + apply = sp.apply or apply + remove = sp.remove or remove + base_spell = false end return { rune = rune; + grade = rg; spell = spell; - name = title; - desc = desc; - cast = cast; + name = title, desc = desc; + cast = cast, apply = apply, remove = remove; + frame = proto.frame; + framestats = proto.frame and sorcery.data.metals[proto.frame].amulet; tone = sorcery.lib.color(rd.tone); + base_spell = base_spell; } end local runeforge_update = function(pos,time) @@ -107,13 +232,14 @@ local i = m:get_inventory() local l = sorcery.ley.netcaps(pos,time or 1) local pow_min = l.self.powerdraw >= l.self.minpower local pow_max = l.self.powerdraw >= l.self.maxpower + local has_phial = function() return not i:is_empty('phial') end - if time and pow_min then -- roll for runes - local rolls = math.floor(time/constants.rune_mine_interval) + if time and has_phial() and pow_min then -- roll for runes + local rolls = math.floor(time/calc_phial_props(i:get_stack('phial',1))) local newrunes = {} for _=1,rolls do local choices = {} for name,rune in pairs(sorcery.data.runes) do if rune.minpower*time <= l.self.powerdraw and math.random(rune.rarity) == 1 then @@ -122,32 +248,56 @@ end end if #choices > 0 then newrunes[#newrunes + 1] = choices[math.random(#choices)] end end - print('rolled for runes, got', dump(newrunes)) for _,r in pairs(newrunes) do - if i:room_for_item('cache',r) then + if i:room_for_item('cache',r) and has_phial() then local qual = math.random(#constants.rune_grades) rune_set(r,{grade = qual}) i:add_item('cache',r) - end + -- consume a phial + local ph = i:get_stack('phial',1) + local n = ph:get_name() + ph:take_item(1) i:set_stack('phial',1,ph) + minetest.add_item(pos,i:add_item('refuse',ItemStack(sorcery.register.residue.db[n]))) + else break end end end + has_phial = has_phial() local spec = string.format([[ formspec_version[3] size[10.25,8] real_coordinates[true] list[context;cache;%f,0.25;%u,1;] list[context;amulet;3.40,1.50;1,1;] list[context;active;5.90,1.50;1,1;] + + list[context;wrench;1.25,1.75;1,1;] + list[context;phial;7.25,1.75;1,1;] + list[context;refuse;8.50,1.75;1,1;] + list[current_player;main;0.25,3;8,4;] image[0.25,0.50;1,1;sorcery_statlamp_%s.png] ]], (10.5 - constants.rune_cache_max*1.25)/2, constants.rune_cache_max, - pow_max and 'green' or (pow_min and 'yellow') or 'off') + ((has_phial and pow_max) and 'green' ) or + ((has_phial and pow_min) and 'yellow') or 'off') + + local ghost = function(slot,x,y,img) + if i:is_empty(slot) then spec = spec .. string.format([[ + image[%f,%f;1,1;%s.png] + ]], x,y,img) end + end + + ghost('active',5.90,1.50,'sorcery_ui_ghost_rune') + ghost('amulet',3.40,1.50,'sorcery_ui_ghost_amulet') + ghost('wrench',1.25,1.75,'sorcery_ui_ghost_rune_wrench') + ghost('phial',7.25,1.75,'vessels_shelf_slot') m:set_string('formspec',spec) + + if i:is_empty('phial') then return false end return true end local rfbox = { type = 'fixed'; @@ -182,15 +332,22 @@ _sorcery = { ley = { mode = 'consume'; affinity = {'praxic'}; power = function(pos,time) + local i = minetest.get_meta(pos):get_inventory() + if i:is_empty('phial') then return 0 end + local phial = i:get_stack('phial',1) + local max,min = 0 for _,r in pairs(sorcery.data.runes) do if r.minpower > max then max = r.minpower end if min == nil or r.minpower < min then min = r.minpower end end + -- high-quality phials reduce power usage + local fac = select(2, calc_phial_props(phial)) + min = min * fac max = max * fac return min*time,max*time end; }; on_leychange = runeforge_update; recipe = { @@ -199,65 +356,111 @@ }; on_construct = function(pos) local m = minetest.get_meta(pos) local i = m:get_inventory() i:set_size('cache',constants.rune_cache_max) - i:set_size('amulet',1) - i:set_size('active',1) + i:set_size('wrench',1) i:set_size('phial',1) i:set_size('refuse',1) + i:set_size('amulet',1) i:set_size('active',1) m:set_string('infotext','Rune Forge') runeforge_update(pos) - minetest.get_node_timer(pos):start(constants.rune_mine_interval) end; after_dig_node = sorcery.lib.node.purge_only {'amulet'}; on_timer = runeforge_update; on_metadata_inventory_move = function(pos, fl,fi, tl,ti, count, user) local inv = minetest.get_meta(pos):get_inventory() + local wrench if not inv:is_empty('wrench') then + wrench = inv:get_stack('wrench',1):get_definition()._proto + end + local wwear = function(cap) + local s = inv:get_stack('wrench',1) + local wear = 65535 / wrench.powers[cap] + s:add_wear(wear) + inv:set_stack('wrench',1,s) + end if fl == 'active' then - inv:set_stack('amulet',1,sorcery.amulet.setrune(inv:get_stack('amulet',1))) - elseif tl == 'active' then - inv:set_stack('amulet',1,sorcery.amulet.setrune(inv:get_stack('amulet',1), inv:get_stack(tl,ti))) + inv:set_stack('amulet',1,sorcery.amulet.setrune(inv:get_stack('amulet',1),nil,user)) + -- only special wrenches can extract runes intact + if wrench.powers.extract then wwear('extract') + minetest.sound_play('sorcery_chime', { pos = pos, gain = 0.5 }) + elseif wrench.powers.purge then wwear('purge') + inv:set_stack(tl,ti,ItemStack(nil)) + minetest.sound_play('sorcery_disjoin', { pos = pos, gain = 0.5 }) + end + elseif tl == 'active' and wrench.powers.imbue then + local amulet = sorcery.amulet.setrune(inv:get_stack('amulet',1), inv:get_stack(tl,ti), user) + local spell = sorcery.amulet.getspell(amulet) + sorcery.vfx.enchantment_sparkle({ + under = pos; + above = vector.add(pos,{x=0,y=1,z=0}); + }, spell.tone:brighten(1.2):hex()) + minetest.sound_play('xdecor_enchanting', { pos = pos, gain = 0.5 }) + inv:set_stack('amulet',1,amulet) + wwear('imbue') end + -- trigger the update early to clean up the ghost image :/ + -- minetest needs a cleaner way to handle these + runeforge_update(pos) end; on_metadata_inventory_put = function(pos, list, idx, stack, user) + local inv = minetest.get_meta(pos):get_inventory() if list == 'amulet' then - local inv = minetest.get_meta(pos):get_inventory() inv:set_stack('active',1,ItemStack(sorcery.amulet.getrune(stack))) end + runeforge_update(pos) + if not inv:is_empty('phial') then + minetest.get_node_timer(pos):start(calc_phial_props(inv:get_stack('phial',1))) + end end; on_metadata_inventory_take = function(pos, list, idx, stack, user) if list == 'amulet' then minetest.get_meta(pos):get_inventory():set_stack('active',1,ItemStack()) end + runeforge_update(pos) end; allow_metadata_inventory_put = function(pos,list,idx,stack,user) if list == 'amulet' then if minetest.get_item_group(stack:get_name(), 'sorcery_amulet') ~= 0 then return 1 end + end + if list == 'phial' then + if minetest.get_item_group(stack:get_name(), 'sorcery_phial') ~= 0 then + return stack:get_count() + end + end + if list == 'wrench' then + if minetest.get_item_group(stack:get_name(), 'sorcery_rune_wrench') ~= 0 then + return 1 + end end return 0 end; allow_metadata_inventory_take = function(pos,list,idx,stack,user) - if list == 'amulet' then return 1 end + if list == 'amulet' or list == 'wrench' then return 1 end + if list == 'phial' or list == 'refuse' then return stack:get_count() end return 0 end; allow_metadata_inventory_move = function(pos, fl,fi, tl,ti, count, user) + local inv = minetest.get_meta(pos):get_inventory() + local wrench if not inv:is_empty('wrench') then + wrench = inv:get_stack('wrench',1):get_definition()._proto + end if fl == 'cache' then if tl == 'cache' then return 1 end if tl == 'active' then - local inv = minetest.get_meta(pos):get_inventory() - if not inv:is_empty('amulet') then + print(dump(wrench)) + if wrench and wrench.powers.imbue and not inv:is_empty('amulet') then local amulet = inv:get_stack('amulet',1) local rune = inv:get_stack(fl,fi) if sorcery.data.runes[rune:get_definition()._proto.id].amulets[amulet:get_definition()._sorcery.amulet.base] then return 1 end end end end if fl == 'active' then - if tl == 'cache' then return 1 end + if tl == 'cache' and wrench and (wrench.powers.extract or wrench.powers.purge) then return 1 end end return 0 end; }) ADDED sounds/sorcery_bloody_burst.ogg Index: sounds/sorcery_bloody_burst.ogg ================================================================== --- sounds/sorcery_bloody_burst.ogg +++ sounds/sorcery_bloody_burst.ogg cannot compute difference between binary files ADDED sounds/sorcery_bloody_hit.1.ogg Index: sounds/sorcery_bloody_hit.1.ogg ================================================================== --- sounds/sorcery_bloody_hit.1.ogg +++ sounds/sorcery_bloody_hit.1.ogg cannot compute difference between binary files ADDED sounds/sorcery_bloody_hit.2.ogg Index: sounds/sorcery_bloody_hit.2.ogg ================================================================== --- sounds/sorcery_bloody_hit.2.ogg +++ sounds/sorcery_bloody_hit.2.ogg cannot compute difference between binary files ADDED sounds/sorcery_bloody_hit.3.ogg Index: sounds/sorcery_bloody_hit.3.ogg ================================================================== --- sounds/sorcery_bloody_hit.3.ogg +++ sounds/sorcery_bloody_hit.3.ogg cannot compute difference between binary files ADDED sounds/sorcery_chime.1.ogg Index: sounds/sorcery_chime.1.ogg ================================================================== --- sounds/sorcery_chime.1.ogg +++ sounds/sorcery_chime.1.ogg cannot compute difference between binary files ADDED sounds/sorcery_chime.2.ogg Index: sounds/sorcery_chime.2.ogg ================================================================== --- sounds/sorcery_chime.2.ogg +++ sounds/sorcery_chime.2.ogg cannot compute difference between binary files ADDED sounds/sorcery_coins.ogg Index: sounds/sorcery_coins.ogg ================================================================== --- sounds/sorcery_coins.ogg +++ sounds/sorcery_coins.ogg cannot compute difference between binary files ADDED sounds/sorcery_crunch.1.ogg Index: sounds/sorcery_crunch.1.ogg ================================================================== --- sounds/sorcery_crunch.1.ogg +++ sounds/sorcery_crunch.1.ogg cannot compute difference between binary files ADDED sounds/sorcery_crunch.2.ogg Index: sounds/sorcery_crunch.2.ogg ================================================================== --- sounds/sorcery_crunch.2.ogg +++ sounds/sorcery_crunch.2.ogg cannot compute difference between binary files ADDED sounds/sorcery_crunch.3.ogg Index: sounds/sorcery_crunch.3.ogg ================================================================== --- sounds/sorcery_crunch.3.ogg +++ sounds/sorcery_crunch.3.ogg cannot compute difference between binary files ADDED sounds/sorcery_crunch.4.ogg Index: sounds/sorcery_crunch.4.ogg ================================================================== --- sounds/sorcery_crunch.4.ogg +++ sounds/sorcery_crunch.4.ogg cannot compute difference between binary files ADDED sounds/sorcery_disjoin.1.ogg Index: sounds/sorcery_disjoin.1.ogg ================================================================== --- sounds/sorcery_disjoin.1.ogg +++ sounds/sorcery_disjoin.1.ogg cannot compute difference between binary files ADDED sounds/sorcery_disjoin.2.ogg Index: sounds/sorcery_disjoin.2.ogg ================================================================== --- sounds/sorcery_disjoin.2.ogg +++ sounds/sorcery_disjoin.2.ogg cannot compute difference between binary files ADDED textures/sorcery_rune_wrench.png Index: textures/sorcery_rune_wrench.png ================================================================== --- textures/sorcery_rune_wrench.png +++ textures/sorcery_rune_wrench.png cannot compute difference between binary files ADDED textures/sorcery_rune_wrench_iridium.png Index: textures/sorcery_rune_wrench_iridium.png ================================================================== --- textures/sorcery_rune_wrench_iridium.png +++ textures/sorcery_rune_wrench_iridium.png cannot compute difference between binary files ADDED textures/sorcery_ui_ghost_amulet.png Index: textures/sorcery_ui_ghost_amulet.png ================================================================== --- textures/sorcery_ui_ghost_amulet.png +++ textures/sorcery_ui_ghost_amulet.png cannot compute difference between binary files ADDED textures/sorcery_ui_ghost_rune.png Index: textures/sorcery_ui_ghost_rune.png ================================================================== --- textures/sorcery_ui_ghost_rune.png +++ textures/sorcery_ui_ghost_rune.png cannot compute difference between binary files ADDED textures/sorcery_ui_ghost_rune_wrench.png Index: textures/sorcery_ui_ghost_rune_wrench.png ================================================================== --- textures/sorcery_ui_ghost_rune_wrench.png +++ textures/sorcery_ui_ghost_rune_wrench.png cannot compute difference between binary files Index: tnodes.lua ================================================================== --- tnodes.lua +++ tnodes.lua @@ -5,10 +5,12 @@ sunlight_propagates = true; buildable_to = true; pointable = false; walkable = false; floodable = true; + drop = {max_items = 0, items = {}}; + on_blast = function() end; -- not affected by explosions groups = { air = 1; sorcery_air = 1; not_in_creative_inventory = 1; }; on_construct = function(pos) local meta = minetest.get_meta(pos) meta:set_float('duration',10) meta:set_float('timeleft',10) Index: vfx.lua ================================================================== --- vfx.lua +++ vfx.lua @@ -1,15 +1,19 @@ sorcery.vfx = {} -sorcery.vfx.cast_sparkle = function(caster,color,strength,duration) +sorcery.vfx.cast_sparkle = function(caster,color,strength,duration,pos) + local ofs = pos + and function(x) return vector.add(pos,x) end + or function(x) return x end + local height = caster:get_properties().eye_height minetest.add_particlespawner { amount = 70 * strength; time = duration or 1.5; attached = 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}; + minpos = ofs({ x = 0.0, z = 0.6, y = height*0.7}); + maxpos = ofs({ x = 0.4, z = 0.2, y = height*1.1}); 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; @@ -21,10 +25,39 @@ aspect_h = 16; length = 1.1; }; } end + +sorcery.vfx.body_sparkle = function(body,color,str,pos) + local img = sorcery.lib.image + local tex = img('sorcery_spark.png') + local pi = tex:blit(tex:multiply(color)):render() + local ofs = pos + and function(x) return vector.add(pos,x) end + or function(x) return x end + return minetest.add_particlespawner { + amount = 25 * str; + time = 0.5; + attached = body; + minpos = ofs{x = -0.5, y = -0.5, z = -0.5}; + maxpos = ofs{x = 0.5, y = 1.5, z = 0.5}; + minacc = {x = -0.3, y = 0.0, z = 0.3}; + maxacc = {x = -0.3, y = 0.0, z = 0.3}; + minvel = {x = -0.6, y = -0.2, z = 0.6}; + maxvel = {x = -0.6, y = 0.2, z = 0.6}; + minexptime = 1.0; + maxexptime = 1.5; + texture = pi; + glow = 14; + animation = { + type = 'vertical_frames'; + aspect_w = 16, aspect_h = 16; + length = 1.6; + }; + } +end sorcery.vfx.enchantment_sparkle = function(tgt,color) local minvel, maxvel if minetest.get_node(vector.add(tgt.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}; Index: wands.lua ================================================================== --- wands.lua +++ wands.lua @@ -93,16 +93,16 @@ tex = u.image('default_copper_block.png'); wandprops = { flux = 0.7, chargetime = 0.5 }; }; silver = { tone = u.color(215,238,241); - tex = u.image('default_gold_block'):colorize(u.color(255,238,241), 255); + tex = u.image('default_gold_block.png'):colorize(u.color(255,238,241), 255); wandprops = {}; }; steel = { tone = u.color(255,255,255); - tex = u.image('default_steel_block'); + tex = u.image('default_steel_block.png'); wandprops = {}; }; }; gem = sorcery.data.gems; };