Index: data/runes.lua ================================================================== --- data/runes.lua +++ data/runes.lua @@ -99,10 +99,31 @@ }; } end end end + +local purge = function(target) + local h = target:get_properties().eye_height * 1.1 + minetest.add_particlespawner { + time = 0.2, amount = math.random(200,250), attached = target; + glow = 14, texture = sorcery.vfx.glowspark(sorcery.lib.color(156,255,10)):render(); + minpos = {x = -0.3, y = -0.5, z = -0.3}; + maxpos = {x = 0.3, y = h, z = 0.3}; + minvel = {x = -1.8, y = -1.8, z = -1.8}; + maxvel = {x = 1.8, y = 1.8, z = 1.8}; + minsize = 0.2, maxsize = 5; + animation = { + type = 'vertical_frames', length = 4.1; + aspect_w = 16, aspect_h = 16; + }; + minexptime = 2, maxexptime = 4; + } + minetest.sound_play('sorcery_disjoin',{object=target},true) + sorcery.spell.disjoin{target=target} +end + return { translocate = { name = 'Translocate'; tone = {0,235,233}; minpower = 3; @@ -135,11 +156,11 @@ end end; frame = { tungsten = { name = 'Quick Joining'; - desc = 'Give this amulet to another and they can arrive safely at your side almost instantaneously 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 almost instantaneously from anywhere at all in the world — though returning whence they came may be a more difficult matter'; }; 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.'; }; @@ -163,10 +184,11 @@ 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 + if ctx.caster:get_attach() ~= nil then return false end 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 @@ -196,10 +218,11 @@ }; }; ruby = minetest.get_modpath('beds') and { name = 'Escape'; desc = 'Immediately transport yourself out of a dangerous situation back to the last place you slept, before anyone has time to net you in a disjunction'; + mingrade = 4; cast = function(ctx) -- if not beds.spawns then beds.read_spawns() end local subjects = {ctx.caster} for _,s in pairs(subjects) do local spp = beds.spawn[ctx.caster:get_player_name()] @@ -391,40 +414,30 @@ desc = 'Wielding this amulet, a touch of your hand will unravel even the mightiest protective magics, leaving doors unsealed and walls free to tear down'; }; amethyst = { name = 'Purging'; desc = 'Free yourself from the grip of any malicious spellwork with a snap of your fingers — interrupting all of your own active spells in the process, including impending translocations'; - cast = function(ctx) - local h = ctx.heading.eyeheight * 1.1 - minetest.add_particlespawner { - time = 0.2, amount = math.random(200,250), attached = ctx.caster; - glow = 14, texture = sorcery.vfx.glowspark(sorcery.lib.color(156,255,10)):render(); - minpos = {x = -0.3, y = -0.5, z = -0.3}; - maxpos = {x = 0.3, y = h, z = 0.3}; - minvel = {x = -1.8, y = -1.8, z = -1.8}; - maxvel = {x = 1.8, y = 1.8, z = 1.8}; - minsize = 0.2, maxsize = 5; - animation = { - type = 'vertical_frames', length = 4.1; - aspect_w = 16, aspect_h = 16; - }; - minexptime = 2, maxexptime = 4; - } - minetest.sound_play('sorcery_disjoin',{object=ctx.caster},true) - sorcery.spell.disjoin{target=ctx.caster} - end; + cast = function(ctx) purge(ctx.caster) end; }; emerald = { name = 'Disjunction Field'; desc = 'Render an area totally opaque to spellwork for a period of time, disrupting any existing spells and preventing further spellcasting therein'; }; ruby = { name = 'Disjunction'; desc = 'Wield this amulet against a spellcaster to disrupt and abort all their spells in progress, perhaps to trap a foe intent on translocating away, or unleash its force upon the victim of a malign hex to free them from its clutches'; + mingrade = 3; + cast = function(ctx) + if ctx.target.type == 'object' + then purge(ctx.target.ref) + else return false + end + end; frame = { iridium = { name = 'Nullification'; + mingrade = 5; desc = 'Not only will your victim\'s spells be nullified, but all enchanted objects they carry will be stripped of their power — or possibly even destroyed outright'; }; }; }; luxite = { @@ -853,11 +866,11 @@ desc = 'Bring an exact twin of any object or item into existence, no matter how common or rare it might be'; cast = function(ctx) local color = sorcery.lib.color(255,61,205) local dup, sndpos, anchor, sbj, ty if ctx.target.type == 'object' and ctx.target.ref:get_luaentity().name == '__builtin:item' then - sorcery.vfx.imbue(color, ctx.target.ref) + -- sorcery.vfx.imbue(color, ctx.target.ref) -- causes graphics card problems??? sndpos = 'subjects' sbj = {{player = ctx.target.ref}} local item = ItemStack(ctx.target.ref:get_luaentity().itemstring) local r = function() return math.random() * 2 - 1 end local putpos = vector.offset(ctx.target.ref:get_pos(), r(), 1, r()) @@ -883,10 +896,13 @@ end if #vp > 0 then npos=vp[math.random(#vp)] end end if npos then minetest.set_node(npos, minetest.get_node(ctx.target.under)) + if minetest.registered_nodes[ty].on_construct then + minetest.registered_nodes[ty].on_construct(npos) + end minetest.get_meta(npos):from_table(origmeta) return npos, true else local nstack = ItemStack(ty) nstack:get_meta():from_table(origmeta) @@ -974,10 +990,57 @@ rarity = 5; amulets = { luxite = { name = 'Glow'; desc = 'Swathe yourself in an aura of sparkling radiance, casting light upon all the dark places where you voyage'; + cast = function(ctx) + local fac = (ctx.stats.power * 0.1) + local radius = 2 + 5*fac + local period = 0.4 - 0.3*fac + local glowduration = 5 + 50*fac + sorcery.spell.cast { + name = "sorcery:glow"; + caster = ctx.caster; + subjects = {{player=ctx.caster}}; + duration = 40 + 120*fac; + nodes = {}; + disjoin = function(self) + for _,n in pairs(self.nodes) do + if sorcery.lib.str.beginswith(minetest.get_node(n).name,'sorcery:air_glimmer_') then + minetest.remove_node(n) + end + end + end; + intervals = { + {period = period, after = {whence=0,secs=0.7}, fn = function(c) + print('cycling!') + for _,sub in pairs(c.spell.subjects) do + local ox, oy, oz = math.random(-radius,radius), + math.random(-radius,radius), + math.random(-radius,radius) + local pos = vector.offset(sub.player:get_pos(), ox,oy,oz) + print('pos',minetest.pos_to_string(pos),'player',minetest.pos_to_string(sub.player:get_pos())) + if sorcery.lib.node.is_air(pos) then + print('is air!') + local power = math.random(4,minetest.LIGHT_MAX) + minetest.set_node(pos, { + name = 'sorcery:air_glimmer_' .. tostring(power); + }) + c.spell.nodes[#c.spell.nodes + 1] = pos + local d = glowduration * (0.5 + math.random()*0.5) + local m = minetest.get_meta(pos) + m:set_float('duration', d) + m:set_float('timeleft', d) + m:set_int('power', power) + else + print('not air!', dump(minetest.get_node(pos))) + end + end + end}; + }; + } + end; iridium = { name = 'Aura'; desc = 'Dazzling golden luminance emanates from the bodies of all those around you, and you walk in light even amid the darkest depths of the earth'; }; }; @@ -987,10 +1050,31 @@ frame = { iridium = { name = 'Sunshine'; mingrade = 5; desc = 'Unleash the power of this amulet to seize ultimate control over the forces of nature and summon the Sun high into the nighttime sky'; + cast = function(ctx) + local time = minetest.get_timeofday() + if not (time < 0.3 or time > 0.7) then return false end + local diff = 0.5 - time + local frames = 40 + local duration = 1.5 + local delta = diff / frames + local tl = {} + for i=1,frames do + local wh = {whence=0, secs=duration*(i/frames)} + tl[wh] = function(s) + minetest.set_timeofday(time + delta*i) + end + end + sorcery.spell.cast { + name = 'sorcery:sunshine'; + caster = ctx.caster; + timeline = tl; + duration = duration; + } + end; }; }; }; }; }; Index: data/spells.lua ================================================================== --- data/spells.lua +++ data/spells.lua @@ -783,12 +783,12 @@ if minetest.get_node(pos).name == 'air' then minetest.set_node(pos,{name='sorcery:air_glimmer_' .. tostring(lum)}) do local lm = minetest.get_meta(pos) lm:set_float('duration',duration) lm:set_float('timeleft',duration) - lm:set_float('power',lum * near) + lm:set_int('power',lum * near) end end end end; }; } Index: leylines.lua ================================================================== --- leylines.lua +++ leylines.lua @@ -12,15 +12,15 @@ 'praxic'; 'counterpraxic'; 'cognic'; 'mandatic'; 'occlutic'; 'imperic'; 'syncretic'; 'entropic'; }; local forcemap = minetest.get_perlin(0xe9a01d, 3, 2, 150) - local aff1map = minetest.get_perlin(0x10eb03, 3, 2, 300) - local aff2map = minetest.get_perlin(0x491e12, 3, 2, 240) - local txpos = { --- :( :( :( :( + local aff1map = minetest.get_perlin(0x10eb03, 3, 2, 300) + local aff2map = minetest.get_perlin(0x491e12, 3, 2, 240) + local txpos = { x = pos.x; - y = pos.z; + y = pos.z; --- :( :( :( :( z = pos.y; } local normalize = function(map) local v = map:get_2d(txpos) @@ -369,10 +369,23 @@ recipe = { note = 'Captures radiant force and suffuses it through distribution net. Energy production varies with local leyline strength.'; }; }; }) + + minetest.register_abm { + name = 'Condenser sound effects'; + nodenames = {'sorcery:condenser'}; + neighbors = {'group:sorcery_ley_device'}; + interval = 5.6, chance = 1, catch_up = false; + action = function(pos) + local force = sorcery.ley.estimate(pos).force + minetest.sound_play('sorcery_condenser_bg', { + pos = pos, max_hear_distance = 5 + 8*force, gain = force*0.3; + }) + end; + } end minetest.register_craft { output = 'sorcery:condenser'; recipe = { Index: lib/node.lua ================================================================== --- lib/node.lua +++ lib/node.lua @@ -71,11 +71,11 @@ 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 + return (d.walkable == false) and (d.drawtype == 'airlike' or d.buildable_to == true) end; is_clear = function(pos) if not sorcery.lib.node.is_air(pos) then return false end local ents = minetest.get_objects_inside_radius(pos,0.5) Index: runeforge.lua ================================================================== --- runeforge.lua +++ runeforge.lua @@ -16,16 +16,28 @@ 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'}; + lesser = {grade = 1, name = 'Lesser'; infusion = 'sorcery:powder_brass'; + dist = { Fragile = 1, Weak = 0.7, Ordinary = 0.1, Pristine = 0.05, Sublime = 0.01 }; + }; + simple = {grade = 2, name = 'Simple'; infusion = 'sorcery:powder_silver'; + dist = { Fragile = 1, Weak = 0.8, Ordinary = 0.2, Pristine = 0.07, Sublime = 0.015 }; + }; + great = {grade = 3, name = 'Great'; infusion = 'sorcery:powder_gold'; + dist = { Fragile = 1, Weak = 0.9, Ordinary = 0.5, Pristine = 0.1, Sublime = 0.05 }; + }; + splendid = {grade = 4, name = 'Splendid'; infusion = 'sorcery:powder_electrum'; + dist = { Fragile = 1, Weak = 0.95, Ordinary = 0.7, Pristine = 0.3, Sublime = 0.1 }; + }; + exalted = {grade = 5, name = 'Exalted'; infusion = 'sorcery:powder_iridium'; + dist = { Fragile = 0, Weak = 1, Ordinary = 0.9, Pristine = 0.5, Sublime = 0.25 }; + }; + supreme = {grade = 6, name = 'Supreme'; infusion = 'sorcery:powder_levitanium'; + dist = { Fragile = 0, Weak = 0, Ordinary = 1, Pristine = 0.7, Sublime = 0.4 }; + }; }; } local calc_phial_props = function(phial) --> mine interval: float, time factor: float local g = phial:get_definition()._proto.data.grade local i = constants.rune_mine_interval @@ -55,11 +67,11 @@ 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 and how much energy is required by the process."; + desc = "A powerful liquid consumed in the operation of a rune forge. Its quality determines how fast new runes can be constructed and how much energy is required by the process, and affects your odds of getting a high-quality rune."; color = color:brighten(1 + fac*0.5); imgvariant = (fac >= 5) and 'sparkle' or 'dull'; glow = 5+p.grade; extra = { groups = { sorcery_phial = p.grade }; @@ -198,26 +210,28 @@ 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 rg = m:get_int('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,apply,remove,mingrade = spell.name, spell.desc, spell.cast, spell.apply, spell.remove, spell.mingrade -- 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 - mingrade = sp.mingrade or remove - base_spell = false + if not sp.mingrade or rg >= sp.mingrade then + 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 + mingrade = sp.mingrade or mingrade + base_spell = false + end end return { rune = rune, grade = rg; spell = spell, mingrade = mingrade; @@ -239,11 +253,12 @@ 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 has_phial() and pow_min and not probe.disjunction then -- roll for runes - local int, powerfac = calc_phial_props(i:get_stack('phial',1)) + local phial = i:get_stack('phial',1) + local int, powerfac = calc_phial_props(phial) local rolls = math.floor(time/int) local newrunes = {} for _=1,rolls do local choices = {} for name,rune in pairs(sorcery.data.runes) do @@ -269,18 +284,27 @@ -- print('me',dump(l.self)) end for _,r in pairs(newrunes) do if i:room_for_item('cache',r) and has_phial() then - local qual = math.random(#constants.rune_grades) + local qual + -- iterate through qualities from highest to lowest, rolling against the phial's + -- distribution for each, and stopping when we find one + local qdist = phial:get_definition()._proto.data.dist + for i=#constants.rune_grades,1,-1 do + local chance = qdist[constants.rune_grades[i]] + if chance == 1 or math.random() <= chance then + qual = i + break + end + end rune_set(r,{grade = qual}) i:add_item('cache',r) -- 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]))) + minetest.add_item(pos,i:add_item('refuse',ItemStack(sorcery.register.residue.db[ph:get_name()]))) else break end end end has_phial = has_phial() ADDED sounds/sorcery_condenser_bg.ogg Index: sounds/sorcery_condenser_bg.ogg ================================================================== --- sounds/sorcery_condenser_bg.ogg +++ sounds/sorcery_condenser_bg.ogg cannot compute difference between binary files