sorcery  Check-in [90e64c483c]

Overview
Comment:fix some showstopping bugs, more amulet spells, add sound effects, improve teleportation visuals
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: 90e64c483cf23b6ba5b92e8987d2e92c3c7666a305902ca6421ace569ff28ba0
User & Date: lexi on 2020-10-23 00:08:30
Other Links: manifest | tags
Context
2020-10-24
01:21
add some more spells, add spell infrastructure to support metamagic, especially disjunction, various tweaks and bugfixes. [emergency commit] check-in: 00922196a9 user: lexi tags: trunk
2020-10-23
00:08
fix some showstopping bugs, more amulet spells, add sound effects, improve teleportation visuals check-in: 90e64c483c user: lexi tags: trunk
2020-10-22
15:51
balance amulets better, add sound effects, add debugging privilege for runes, swat various glitches and bugs check-in: 83426a2748 user: lexi tags: trunk
Changes

Modified altar.lua from [f718356661] to [71f6a713cb].

     1      1   local altar_item_offset = {
     2      2   	x = 0, y = -0.3, z = 0
     3      3   }
            4  +local log = function(...) sorcery.log('altar',...) end
     4      5   
     5      6   local range = function(min, max)
     6      7   	local span = max - min
     7      8   	local val = math.random() * span
     8      9   	return val + min
     9     10   end
    10     11   
................................................................................
   166    167   					-- we pick a random gift and roll against its rarity
   167    168   					-- to determine if the god is feeling generous
   168    169   					local gift = sorcery.lib.tbl.pick(god.gifts)
   169    170   					local data = god.gifts[gift]
   170    171   					local value, rarity = data[1], data[2]
   171    172   					if value <= divine_favor and math.random(rarity) == 1 then
   172    173   						bestow(gift)
   173         -						print(god.name .. ' has produced ' .. gift .. ' upon an altar as a gift')
          174  +						log(god.name .. ' has produced ' .. gift .. ' upon an altar as a gift')
   174    175   						if math.random(god.generosity) == 1 then
   175    176   							-- unappreciated gifts may incur divine
   176    177   							-- irritation
   177    178   							divine_favor = divine_favor - 1
   178    179   						end
   179    180   					end
   180    181   				end

Modified cookbook.lua from [fe2bed6abe] to [604c53eb6c].

    82     82   	end
    83     83   	return names[math.random(#names)]
    84     84   end end
    85     85   local find_builtin = function(method,kind)
    86     86   	return function(out)
    87     87   		local rec = {}
    88     88   		local crec = sorcery.lib.tbl.walk(minetest.registered_items[out],{'_sorcery','recipe','canonical',kind})
    89         -		local w=0, lst
           89  +		local w, lst = 0
    90     90   		if crec then
    91     91   			lst = {}
    92     92   			for i,v in pairs(crec) do
    93     93   				if #v > w then w = #v end
    94     94   				for j,n in pairs(v) do
    95     95   					lst[#lst+1] = n
    96     96   				end
................................................................................
   108    108   			w = (i.width == 0) and 3 or i.width
   109    109   			lst = i.items
   110    110   		end
   111    111   		-- for j=1,#i.items do
   112    112   		for j,item in pairs(lst) do
   113    113   			local row = math.floor((j-1) / w)
   114    114   			local col = (j-1) % w
   115         -			if item then
   116         -				rec[1 + (row * 3) + col] = item
   117         -			end
          115  +			if item then rec[1 + (row * 3) + col] = item end
   118    116   		end
   119    117   		return rec
   120    118   	end
   121    119   end
   122    120   local function group_eval(i)
   123    121   	if string.sub(i,1,6) == 'group:' then
   124    122   		local g = string.sub(i,7)
................................................................................
   218    216   		chance = 4;
   219    217   		slots = {
   220    218   			{0,0};
   221    219   			{0,1};
   222    220   		};
   223    221   		pick = function(restrict)
   224    222   			-- TODO make sure affinity restrictions match
   225         -			return sorcery.data.infusions[math.random(#sorcery.data.infusions)].output
          223  +			return sorcery.register.infusions.db[math.random(#sorcery.register.infusions.db)].output
   226    224   		end;
   227    225   		title = function(output)
   228         -			for _,i in pairs(sorcery.data.infusions) do
          226  +			for _,i in pairs(sorcery.register.infusions.db) do
   229    227   				if i.output == output then
   230    228   					if i._proto and i._proto.name
   231    229   						then return i._proto.name
   232    230   						else break end
   233    231   				end
   234    232   			end
   235    233   			return 'Mysterious Potion'
   236    234   		end;
   237    235   		find = function(out)
   238         -			for _,i in pairs(sorcery.data.infusions) do
          236  +			for _,i in pairs(sorcery.register.infusions.db) do
   239    237   				if i.output == out then
   240    238   					return { i.infuse, i.into }
   241    239   				end
   242    240   			end
   243    241   		end;
   244    242   		props = function(out)
   245         -			for _,i in pairs(sorcery.data.infusions) do
          243  +			for _,i in pairs(sorcery.register.infusions.db) do
   246    244   				if i.output == out then
   247    245   					if i.recipe then return i.recipe else return {} end
   248    246   				end
   249    247   			end
   250    248   		end;
   251    249   	};
   252    250   	grind = {

Modified data/extracts.lua from [c62e0cc7f2] to [442aef28cd].

    13     13   	raspberry = {"group:food_raspberries", {228,51,210}};
    14     14   	chili = {"farming:chili_pepper", {243,75,49}};
    15     15   	pine = {"default:pine_sapling", {41,166,80}};
    16     16   	cocoa = {"farming:cocoa_beans", {146,38,0}};
    17     17   	grape = {"farming:grapes", {206,56,214}};
    18     18   	kelp = {"default:sand_with_kelp", {109,185,145}};
    19     19   	fern = {"default:fern_1", {164,238,47}};
           20  +	greengrass = {"default:grass_1", {185,255,115}};
    20     21   	marram = {"default:marram_grass_1", {127,255,210}};
           22  +	shrub = {"default:dry_shrub", {187,149,76}};
    21     23   };

Modified data/oils.lua from [fb12becfb1] to [6619d22587].

    99     99   			'farming:peas';
   100    100   			'farming:peas';
   101    101   		};
   102    102   	};
   103    103   	luscious = {
   104    104   		color = {10,255,10};
   105    105   		mix = {
   106         -			'sorcery:extract_marram';
          106  +			'sorcery:extract_greengrass';
   107    107   			'sorcery:extract_grape';
   108    108   			'farming:cocoa_beans';
   109    109   			'farming:sugar';
   110    110   			'farming:sugar';
   111    111   		};
   112    112   	};
   113    113   }

Modified data/resonance.lua from [436a325bac] to [c464435f82].

    42     42   				'sorcery:essence_flame';
    43     43   				'sorcery:gem_ruby';
    44     44   			};
    45     45   		};
    46     46   		['default:mese_crystal'] = {
    47     47   			mode = 'random';
    48     48   			give = {
    49         -				{value = 1; item = 'default:mese_fragment'};
           49  +				{value = 1; item = 'default:mese_crystal_fragment'};
    50     50   				{value = 2; item = 'sorcery:essence_force'};
    51     51   			};
    52     52   		};
    53     53   	};
    54     54   
    55     55   	meld = {
    56     56   		{

Modified data/runes.lua from [720c3d8102] to [47aa367750].

    32     32   						name = 'Arrival';
    33     33   						desc = "Give this amulet to another and they will be able to arrive at your side in a flash from anywhere in the world, carrying others with them in the spell's grip";
    34     34   					};
    35     35   				};
    36     36   			};
    37     37   			sapphire = {
    38     38   				name = 'Return';
    39         -				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.';
           39  +				desc = 'Use this amulet once to bind it to a particular place, then discharge its spell to translocate yourself back to that point from anywhere in the world.';
    40     40   				remove = function(ctx)
    41     41   					ctx.meta:set_string('rune_return_dest','')
    42     42   				end;
    43     43   				cast = function(ctx)
    44     44   					if not ctx.meta:contains('rune_return_dest') then
    45     45   						local pos = ctx.caster:get_pos()
    46     46   						ctx.meta:set_string('rune_return_dest',minetest.pos_to_string(pos))
................................................................................
    47     47   						return true -- play effects but do not break spell
    48     48   					else
    49     49   						local pos = minetest.string_to_pos(ctx.meta:get_string('rune_return_dest'))
    50     50   						ctx.meta:set_string('rune_return_dest','')
    51     51   						local subjects = { ctx.caster }
    52     52   						local center = ctx.caster:get_pos()
    53     53   						ctx.sparkle = false
           54  +						local delay = math.max(3,10 - ctx.stats.power) + 3*(math.random()*2-1)
           55  +						print('teledelay',delay,ctx.stats.power)
    54     56   						for _,s in pairs(subjects) do
    55     57   							local offset = vector.subtract(s:get_pos(), center)
    56     58   							local pt = sorcery.lib.node.get_arrival_point(vector.add(pos,offset))
    57     59   							if pt then
    58         -								sorcery.vfx.body_sparkle(s,sorcery.lib.color(20,120,255),2)
    59         -								sorcery.vfx.body_sparkle(nil,sorcery.lib.color(20,255,120),2,pt)
    60         -								s:set_pos(pt)
           60  +								minetest.sound_play('sorcery_stutter', {
           61  +									object = s, gain = 0.8;
           62  +								},true)
           63  +								local windup = minetest.sound_play('sorcery_windup',{
           64  +									object = s, gain = 0.4;
           65  +								})
           66  +								local mydelay = delay + math.random(-10,10)*.1;
           67  +								local spark = sorcery.lib.image('sorcery_spark.png')
           68  +								local sh = s:get_properties().eye_height
           69  +								local sparkle = function(amt,time,minsize,maxsize)
           70  +									minetest.add_particlespawner {
           71  +										amount = amt, time = time, attached = s;
           72  +										minpos = { x = -0.3, y = -0.5, z = -0.3 };
           73  +										maxpos = { x =  0.3, y = sh*1.1, z = 0.3 };
           74  +										minvel = { x = -0.4, y = -0.2, z = -0.4 };
           75  +										maxvel = { x =  0.4, y =  0.2, z =  0.4 };
           76  +										minacc = { x = -0.5, y = -0.4, z = -0.5 };
           77  +										maxacc = { x =  0.5, y =  0.4, z =  0.5 };
           78  +										minexptime = 1.0, maxexptime = 2.0;
           79  +										minsize = minsize, maxsize = maxsize, glow = 14;
           80  +										texture = spark:blit(spark:multiply(sorcery.lib.color(29,205,247))):render();
           81  +										animation = {
           82  +											type = 'vertical_frames';
           83  +											aspect_w = 16, aspect_h = 16;
           84  +										};
           85  +									}
           86  +								end
           87  +								sparkle(mydelay*100,mydelay,0.3,1.3)
           88  +								minetest.after(mydelay*0.4, function()
           89  +									local timeleft = mydelay - (mydelay*0.4)
           90  +									sparkle(timeleft*150, timeleft, 0.6,1.8)
           91  +								end)
           92  +								minetest.after(mydelay*0.7, function()
           93  +									local timeleft = mydelay - (mydelay*0.7)
           94  +									sparkle(timeleft*80, timeleft, 2,4)
           95  +								end)
           96  +								sorcery.lib.node.preload(pt,s)
           97  +								minetest.after(mydelay, function()
           98  +									minetest.sound_stop(windup)
           99  +									minetest.sound_play('sorcery_zap', { pos = pt, gain = 0.4 },true)
          100  +									minetest.sound_play('sorcery_zap', { pos = s:get_pos(), gain = 0.4 },true)
          101  +									sorcery.vfx.body_sparkle(nil,sorcery.lib.color(20,255,120),2,s:get_pos())
          102  +									s:set_pos(pt)
          103  +									sorcery.vfx.body_sparkle(s,sorcery.lib.color(20,120,255),2)
          104  +								end)
    61    105   							end
    62    106   						end
    63    107   					end
    64    108   				end;
    65    109   				frame = {
    66    110   					iridium = {
    67    111   						name = 'Mass Return';
    68         -						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';
          112  +						desc = 'Use this amulet once to bind it to a particular place, then carry yourself and everyone around you back to that point in a flash simply by using it again';
    69    113   					};
    70    114   				};
    71    115   			};
    72    116   			emerald = {
    73    117   				name = 'Banishment';
    74    118   				desc = 'Use this amulet once to bind it to a particular point in the world, then wield it against a foe to whisk them away immediately to your chosen prison';
    75    119   				frame = {
    76    120   					iridium = {
    77    121   						name = 'Mass Banishment';
    78    122   						desc = 'Use this amulet once to bind it to a particular point in the world, then use it again to seize up everyone surrounding you in the grip of a fearsome magic that will deport them all in the blink of an eye to whatever destination you have chosen';
    79    123   					};
    80    124   				};
    81    125   			};
    82         -			ruby = {
          126  +			ruby = minetest.get_modpath('beds') and {
    83    127   				name = 'Escape';
    84    128   				desc = 'Immediately transport yourself out of a dangerous situation back to the last place you slept';
          129  +				cast = function(ctx)
          130  +					-- if not beds.spawns then beds.read_spawns() end
          131  +					local subjects = {ctx.caster}
          132  +					for _,s in pairs(subjects) do
          133  +						local spp = beds.spawn[ctx.caster:get_player_name()]
          134  +						if spp then
          135  +							local oldpos = s:get_pos()
          136  +							minetest.sound_play('sorcery_splunch', {pos=oldpos}, true)
          137  +							sorcery.vfx.body_sparkle(nil,sorcery.lib.color(244,38,131),2,oldpos)
          138  +							s:set_pos(spp)
          139  +							minetest.sound_play('sorcery_splunch', {pos=spp}, true)
          140  +							sorcery.vfx.body_sparkle(nil,sorcery.lib.color(244,38,89),2,spp)
          141  +						end
          142  +						-- TODO decide what happens to the people who don't have
          143  +						-- respawn points already set
          144  +					end
          145  +				end;
    85    146   				frame = {
    86    147   					cobalt = {
    87    148   						name = 'Vengeful Exit';
    88    149   						desc = 'Translocate away to the safety of your boudoir with a fearsome blast of dangerous radiance that will send bodies flying and deal heavy damage to those nearby';
    89    150   					};
    90    151   					iridium = {
    91    152   						name = 'Mass Escape';
................................................................................
   121    182   		minpower = 1;
   122    183   		rarity = 7;
   123    184   		amulets = {
   124    185   			amethyst = {
   125    186   				name = 'Hurling';
   126    187   				desc = 'Wielding this amulet, a mere flick of your fingers will lift any target of your choice bodily into the air and press upon them with tremendous repulsive force, throwing them like a hapless ragdoll out of your path';
   127    188   			};
   128         -			diamond = {
          189  +			sapphire = {
          190  +				name = 'Flinging';
          191  +				desc = 'Toss an enemy violently into the air, and allow the inevitable impact to do your dirty work for you';
          192  +			};
          193  +			emerald = {
   129    194   				name = 'Shockwave';
          195  +				desc = 'Let loose a stream of concussive force that slams into everything in your path and sends them hurtling away from you';
          196  +			};
          197  +			luxite = {
          198  +				name = 'Repulsive Aura';
          199  +				desc = 'For a period of time, anyone who approaches you will be violently thrust aside';
          200  +			};
          201  +			diamond = {
          202  +				name = 'Blastwave';
   130    203   				desc = 'Unleash a tidal wave of force in every direction, blasting friends and foes alike away from you with enough violence to sprain and fracture bone';
   131    204   			};
   132    205   		};
   133    206   	};
   134    207   	obliterate = {
   135    208   		name = 'Obliterate';
   136    209   		tone = {255,0,10};
................................................................................
   141    214   				name = 'Sapping';
   142    215   				desc = 'Punch a hole in enemy fortifications big enough to slip through but small enough to avoid immediate attention';
   143    216   			};
   144    217   			ruby = {
   145    218   				name = 'Shattering';
   146    219   				desc = 'Tear a violent wound in the earth with the destructive force of this amulet';
   147    220   			};
          221  +			emerald = {
          222  +				name = 'Detonate';
          223  +				desc = 'Wielding this amulet, you can loose an extraordinarily powerful bolt of flame from your fingertips that will explode violently on impact, wreaking total havoc wherever it lands';
          224  +				cast = function(ctx)
          225  +					local speed = 40
          226  +					local radius = math.random(math.floor(ctx.stats.power*0.5),math.ceil(ctx.stats.power))
          227  +					local heading = ctx.heading
          228  +					heading.pos.y = heading.pos.y + heading.eyeheight*0.9
          229  +					local vel = vector.multiply(heading.yaw,speed)
          230  +					local bolt = minetest.add_entity(vector.add(heading.pos,vector.multiply(heading.yaw,2.5)),'sorcery:spell_projectile_flamebolt')
          231  +					bolt:set_rotation(heading.yaw)
          232  +					bolt:get_luaentity()._blastradius = radius
          233  +					bolt:set_velocity(vel)
          234  +				end;
          235  +			};
          236  +			luxite = {
          237  +				name = 'Lethal Aura';
          238  +				desc = 'For a time, anyone who approaches you, whether friend or foe, will suffer immediate retaliation as they are quickly sapped of their life force';
          239  +			};
   148    240   			diamond = {
   149    241   				name = 'Killing';
          242  +				mingrade = 4;
   150    243   				desc = 'Wield this amulet against a foe to instantly snuff the life out of their mortal form, regardless of their physical protections.';
   151    244   				cast = function(ctx)
   152    245   					if not (ctx.target and ctx.target.type == 'object') then return false end
   153    246   					local tgt = ctx.target.ref
   154    247   					if not minetest.is_player(obj) then return false end
   155    248   					local tgth = tgt:get_properties().eye_height
   156    249   					sorcery.vfx.bloodburst(vector.add(tgt:get_pos(),{x=0,y=tgth/2,z=0}),20)
................................................................................
   206    299   			};
   207    300   			diamond = {
   208    301   				name = 'Radiance';
   209    302   				desc = 'Set the air around you alight with a mystic luminance, letting you see clearly a great distance in every direction for several minutes';
   210    303   				frame = {
   211    304   					iridium = {
   212    305   						name = 'Sunshine';
          306  +						mingrade = 5;
   213    307   						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';
   214    308   					};
   215    309   				};
   216    310   			};
   217    311   		};
   218    312   	};
   219    313   	dominate = {
................................................................................
   222    316   		minpower = 4;
   223    317   		rarity = 40;
   224    318   		amulets = {
   225    319   			amethyst = {
   226    320   				name = 'Suffocation';
   227    321   				desc = 'Wrap this spell tightly around your victim\'s throat, cutting off their oxygen until you release them.';
   228    322   			};
          323  +			emerald = {
          324  +				name = 'Caging';
          325  +				desc = 'Trap your victim in an impenetrable field of force, leaving them with no way out but translocation or waiting for the field to release them';
          326  +			};
   229    327   			ruby = {
   230    328   				name = 'Exsanguination';
   231         -				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';
          329  +				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 being';
   232    330   				cast = function(ctx)
   233    331   					if not (ctx.target and ctx.target.type == 'object') then return false end
   234    332   					local tgt = ctx.target.ref
   235    333   					local takefac = math.min(99,50 + (ctx.stats.power * 5)) / 100
   236    334   					local dmg = tgt:get_hp() * takefac
   237    335   					print("!!! dmg calc",takefac,dmg,tgt:get_hp())
   238    336   

Modified data/spells.lua from [151760f504] to [16c84d9b57].

   179    179   		uses = 32;
   180    180   		affinity = {'acacia','blazing'};
   181    181   		leytype = 'praxic';
   182    182   		desc = 'Conjure a gout of fire to scorch your foes with a flick of this wand';
   183    183   		cast = function(ctx)
   184    184   			local speed = 30 -- TODO maybe amethyst tip increases speed?
   185    185   			local radius = math.random(math.max(1,math.floor((ctx.stats.power or 1) - 0.5)), math.ceil((ctx.stats.power or 1)+0.5))
   186         -			print('!! radius',radius)
   187    186   			local heading = ctx.heading
   188    187   			heading.pos.y = heading.pos.y + heading.eyeheight*0.9
   189    188   			local vel = vector.multiply(heading.yaw,speed)
   190    189   			local bolt = minetest.add_entity(vector.add(heading.pos,vector.multiply(heading.yaw,2.5)),'sorcery:spell_projectile_flamebolt')
   191    190   			bolt:set_rotation(heading.yaw)
   192    191   			bolt:get_luaentity()._blastradius = radius
   193    192   			bolt:set_velocity(vel)
................................................................................
   491    490   		cast = function(ctx)
   492    491   			local tgt = target_node(ctx, 'sorcery:enchanter')
   493    492   			if not tgt then return false end
   494    493   
   495    494   			local inv = minetest.get_meta(ctx.target.under):get_inventory()
   496    495   			for _,name in pairs{'foci','item'} do
   497    496   				for i=1,inv:get_size(name) do
          497  +					if inv:get_stack(name,i):is_empty() then goto skip end
   498    498   					local stack = 'sorcery:ash'
   499    499   					if ctx.base.gem == 'sapphire' then
   500    500   						stack = nil
   501    501   					end
   502    502   					inv:set_stack(name,i,ItemStack(stack))
   503         -				end
          503  +				::skip::end
   504    504   			end
   505    505   
   506    506   			enchantment_sparkle(ctx,sorcery.lib.color(255,12,0))
   507    507   			enchantment_sparkle(ctx,sorcery.lib.color(85,18,35))
   508    508   			enchantment_sparkle(ctx,sorcery.lib.color(0,0,0))
   509    509   		end
   510    510   	};

Modified depends.txt from [01a845eb65] to [5b69774a87].

     5      5   basic_materials
     6      6   vessels
     7      7   late
     8      8   instant_ores
     9      9   screwdriver
    10     10   hopper?
    11     11   unifieddyes?
           12  +beds?

Modified gems.lua from [f5bc90eb64] to [d9755d7797].

    61     61   			if not sp or not sp.cast then return nil end
    62     62   			local stats = sorcery.amulet.stats(stack)
    63     63   
    64     64   			local ctx = {
    65     65   				caster = user;
    66     66   				target = target;
    67     67   				stats = stats;
    68         -				sound = "xdecor_enchanting"; --FIXME make own sounds
    69         -				sparkle = true;
    70     68   				amulet = stack;
    71     69   				meta = stack:get_meta(); -- avoid spell boilerplate
    72     70   				color = sorcery.lib.color(sp.tone);
           71  +				today = minetest.get_day_count();
           72  +				heading = {
           73  +					pos   = user:get_pos();
           74  +					yaw   = user:get_look_dir();
           75  +					pitch = user:get_look_vertical();
           76  +					angle = user:get_look_horizontal();
           77  +					eyeheight = user:get_properties().eye_height;
           78  +				};
           79  +
           80  +				sound = "xdecor_enchanting"; --FIXME make own sounds
           81  +				sparkle = true;
    73     82   			}
    74     83   			print('casting')
    75     84   			local res = sp.cast(ctx)
    76     85   
    77     86   			if res == nil or res == true then
    78     87   				minetest.sound_play(ctx.sound, { 
    79     88   					pos = user:get_pos();

Modified portal.lua from [e011f87b11] to [daf126a7a3].

   297    297   				local found = false
   298    298   				for _,u in pairs(dsp.users) do
   299    299   					if u.object:get_player_name() == name then
   300    300   						found = true
   301    301   					end
   302    302   				end
   303    303   				if not found then
          304  +					if user.sound then minetest.sound_fade(user.sound,1,0) end
   304    305   					portal_context.users[name] = nil
   305    306   				end
   306    307   			end
   307    308   		end
   308    309   
   309    310   		-- one user per pad only!
   310    311   		for _,n in pairs(dev.nodes) do
................................................................................
   311    312   			for _,u in pairs(dsp.users) do
   312    313   				if u.slot == n then
   313    314   					local pname = u.object:get_player_name()
   314    315   					if not portal_context.users[pname] then
   315    316   						portal_context.users[pname] = { time = 0, portal = pos } end
   316    317   					local user = portal_context.users[pname]
   317    318   					if not vector.equals(pos,user.portal) then
          319  +						if user.sound then
          320  +							minetest.sound_fade(user.sound,1,0)
          321  +							user.sound = nil
          322  +						end
   318    323   						user.time = 0
   319    324   						user.portal = pos
   320    325   					end
   321    326   					local cap = sorcery.ley.netcaps(pos,delta)
   322    327   					local jc = (constants.portal_jump_cost_local*delta)
   323    328   					if not user.dest and cap.freepower >= jc  then
   324    329   						user.dest = portal_pick_destination(dev,crc,partner)
   325    330   						sorcery.lib.node.preload(user.dest, u.object)
   326    331   					end
   327    332   					if not user.dest then goto skippad end
   328    333   					local fac = math.min(1,(user.time / constants.portal_jump_time))
          334  +					if user.time == 0 then
          335  +						user.sound = minetest.sound_play('sorcery_windup', {pos=pos})
          336  +					end
   329    337   					minetest.add_particlespawner {
   330    338   						time = 1, amount = 100 + (fac * 200);
   331    339   						minsize = 0.2 + fac*0.7, maxsize = 0.4 + fac*0.9;
   332    340   						minvel = {y = 0.2, x=0,z=0}, maxvel = {y = 0.5, x=0,z=0};
   333    341   						minacc = {y = 0.0, x=0,z=0}, maxacc = {y = 0.3, x=0,z=0};
   334    342   						minpos = vector.add(n.pad,{x = -0.5, y = 0.5, z = -0.5});
   335    343   						maxpos = vector.add(n.pad,{x =  0.5, y = 0.5, z =  0.5});
................................................................................
   362    370   								aspect_w = 16, aspect_h = 16;
   363    371   							};
   364    372   						}
   365    373   					end
   366    374   					-- hack to try and swat an unkillable fucking impossibug
   367    375   					if user.time > constants.portal_jump_time * 2 then
   368    376   						user.time = 0
          377  +						if user.sound then
          378  +							minetest.sound_stop(user.sound)
          379  +							user.sound = nil
          380  +						end
   369    381   					elseif user.time >= constants.portal_jump_time then
   370    382   						local dd = portal_disposition(portal_composition(user.dest))
   371    383   						if #dd.freepads > 0 then
   372    384   							local destpad = dd.freepads[math.random(#dd.freepads)].pad
   373    385   							local rng = function(min,max)
   374    386   								return (math.random() * (max - min)) + min
   375    387   							end
................................................................................
   392    404   									glow = 14;
   393    405   									animation = {
   394    406   										type = 'vertical_frames', length = life + 0.1;
   395    407   										aspect_w = 16, aspect_h = 16;
   396    408   									};
   397    409   								}
   398    410   							end
          411  +							if user.sound then
          412  +								minetest.sound_fade(user.sound,1,0)
          413  +								user.sound = nil
          414  +							end
   399    415   							user.dest = nil
   400    416   							user.time = 0
          417  +							user.sound = nil
          418  +							minetest.sound_play('sorcery_zap',{pos=pos},true)
          419  +							minetest.sound_play('sorcery_zap',{pos=destpad},true)
   401    420   							portal_context.users[pname] = nil
   402    421   							u.object:set_pos(vector.add(destpad, {y=0.5,z=0,x=0}))
   403    422   						end
   404    423   					else
   405    424   						user.time = user.time + delta
   406    425   					end
   407    426   

Modified runeforge.lua from [e97e283f3c] to [c8e0c3ac03].

     1      1   -- TODO make some kind of disposable "filter" tool that runeforges require 
     2      2   -- to generate runes and that wears down over time, to make amulets more
     3      3   -- expensive than they currently are? the existing system is neat but
     4      4   -- i think amulets are a little overpowered for something that just
     5      5   -- passively consumes ley-current
     6      6   
     7      7   local constants = {
     8         -	rune_mine_interval = 250;
            8  +	rune_mine_interval = 240;
     9      9   	-- how often a powered forge rolls for new runes
    10     10   
    11     11   	rune_cache_max = 4;
    12     12   	-- how many runes a runeforge can hold at a time
    13     13   	
    14     14   	rune_grades = {'Fragile', 'Weak', 'Ordinary', 'Pristine', 'Sublime'};
    15     15   	-- how many grades of rune quality/power there are
................................................................................
    23     23   		great    = {grade = 3; name = 'Great';    infusion = 'sorcery:powder_gold'};
    24     24   		splendid = {grade = 4; name = 'Splendid'; infusion = 'sorcery:powder_electrum'};
    25     25   		exalted  = {grade = 5; name = 'Exalted';  infusion = 'sorcery:powder_levitanium'};
    26     26   		supreme  = {grade = 6; name = 'Supreme';  infusion = 'sorcery:essence_force'};
    27     27   	};
    28     28   }
    29     29   local calc_phial_props = function(phial) --> mine interval: float, time factor: float
    30         -	local g = phial:get_definition()._proto.grade
           30  +	local g = phial:get_definition()._proto.data.grade
    31     31   	local i = constants.rune_mine_interval 
    32     32   	local fac = (g-1) / 5
    33     33   	return i - ((i*0.5) * fac), 0.5 * fac
    34     34   end
    35     35   sorcery.register.runes.foreach('sorcery:generate',{},function(name,rune)
    36     36   	local id = 'sorcery:rune_' .. name
    37     37   	rune.image = rune.image or string.format('sorcery_rune_%s.png',name)
................................................................................
    47     47   		};
    48     48   		_proto = { id = name, data = rune; };
    49     49   	})
    50     50   end)
    51     51   
    52     52   for name,p in pairs(constants.phial_kinds) do
    53     53   	local f = string.format
    54         -	local color = sorcery.lib.color(255,27,188)
           54  +	local color = sorcery.lib.color(204,38,235)
    55     55   	local fac = p.grade / 6
    56     56   	local id = f('phial_%s', name);
    57     57   	sorcery.register_potion_tbl {
    58     58   		name = id;
    59     59   		label = f('%s Phial',p.name);
    60         -		desc = "A powerful liquid consumed in the operation of a rune forge. Its quality determines how fast new runes can be constructed.";
           60  +		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.";
    61     61   		color = color:brighten(1 + fac*0.5);
    62     62   		imgvariant = (fac >= 5) and 'sparkle' or 'dull';
    63     63   		glow = 5+p.grade;
    64     64   		extra = {
    65     65   			groups = { sorcery_phial = p.grade };
    66     66   			_proto = { id = name, data = p };
    67     67   		};
    68     68   	}
    69     69   	sorcery.register.infusions.link {
    70     70   		infuse = p.infusion;
    71     71   		into = 'sorcery:potion_subtle';
    72         -		output = id;
           72  +		output = 'sorcery:'..id;
    73     73   	}
    74     74   end
    75     75   
    76     76   local register_rune_wrench = function(w)
    77     77   	local mp = sorcery.data.metals[w.metal].parts
    78     78   	minetest.register_tool(w.name, {
    79     79   		description = w.desc;
................................................................................
    89     89   		};
    90     90   	})
    91     91   	minetest.register_craft {
    92     92   		output = w.name;
    93     93   		recipe = {
    94     94   			{'',                        mp.fragment,''};
    95     95   			{'',                        mp.ingot,   mp.fragment};
    96         -			{'sorcery:vidrium_fragment','',         ''};
           96  +			{'sorcery:fragment_vidrium','',         ''};
    97     97   		};
    98     98   	}
    99     99   end
   100    100   
   101    101   register_rune_wrench {
   102    102   	name = 'sorcery:rune_wrench', desc = 'Rune Wrench';
   103    103   	img = 'sorcery_rune_wrench.png', metal = 'brass';
................................................................................
   132    132   	if rune then
   133    133   		local rp = rune:get_definition()._proto
   134    134   		local rg = rune:get_meta():get_int('rune_grade')
   135    135   		m:set_string('amulet_rune', rp.id)
   136    136   		m:set_int('amulet_rune_grade', rg)
   137    137   		local spell = sorcery.amulet.getspell(stack)
   138    138   		if not spell then return nil end
   139         -
   140         -		local name = string.format('Amulet of %s %s', constants.amulet_grades[rg], spell.name)
          139  +		local name
          140  +		if spell.minrune then -- indicating quality makes less sense if it's restricted
          141  +			name = string.format('Amulet of %s', spell.name)
          142  +		else
          143  +			name = string.format('Amulet of %s %s', constants.amulet_grades[rg], spell.name)
          144  +		end
   141    145   		m:set_string('description', sorcery.lib.ui.tooltip {
   142    146   			title = name;
   143    147   			color = spell.tone;
   144    148   			desc = spell.desc;
   145    149   		})
   146    150   
   147    151   		if spell.apply then spell.apply {
................................................................................
   196    200   	local proto = stack:get_definition()._sorcery.amulet
   197    201   	if not m:contains('amulet_rune') then return nil end
   198    202   	local rune = m:get_string('amulet_rune')
   199    203   	local rg = m:get_string('amulet_rune_grade')
   200    204   	local rd = sorcery.data.runes[rune]
   201    205   	local spell = rd.amulets[proto.base]
   202    206   	if not spell then return nil end
   203         -	local title,desc,cast,apply,remove = spell.name, spell.desc, spell.cast, spell.apply, spell.remove -- FIXME in serious need of refactoring
          207  +	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
   204    208   	local base_spell = true
   205    209   
   206    210   	if proto.frame and spell.frame and spell.frame[proto.frame] then
   207    211   		local sp = spell.frame[proto.frame]
   208    212   		title = sp.name or title
   209    213   		desc = sp.desc or desc
   210    214   		cast = sp.desc or cast
   211    215   		apply = sp.apply or apply
   212    216   		remove = sp.remove or remove
          217  +		mingrade = sp.mingrade or remove
   213    218   		base_spell = false
   214    219   	end
   215    220   	
   216    221   	return {
   217         -		rune = rune;
   218         -		grade = rg;
   219         -		spell = spell;
          222  +		rune = rune, grade = rg;
          223  +		spell = spell, mingrade = mingrade;
   220    224   		name = title, desc = desc;
   221    225   		cast = cast, apply = apply, remove = remove;
   222    226   		frame = proto.frame;
   223    227   		framestats = proto.frame and sorcery.data.metals[proto.frame].amulet;
   224    228   		tone = sorcery.lib.color(rd.tone);
   225    229   		base_spell = base_spell;
   226    230   	}
................................................................................
   233    237   	local l = sorcery.ley.netcaps(pos,time or 1)
   234    238   
   235    239   	local pow_min = l.self.powerdraw >= l.self.minpower
   236    240   	local pow_max = l.self.powerdraw >= l.self.maxpower
   237    241   	local has_phial = function() return not i:is_empty('phial') end
   238    242   
   239    243   	if time and has_phial() and pow_min then -- roll for runes
   240         -		local rolls = math.floor(time/calc_phial_props(i:get_stack('phial',1)))
          244  +		local int, powerfac = calc_phial_props(i:get_stack('phial',1))
          245  +		local rolls = math.floor(time/int)
   241    246   		local newrunes = {}
   242    247   		for _=1,rolls do
   243    248   			local choices = {}
   244    249   			for name,rune in pairs(sorcery.data.runes) do
   245         -				if rune.minpower*time <= l.self.powerdraw and math.random(rune.rarity) == 1 then
          250  +				print('considering',name)
          251  +				print('-- power',rune.minpower,(rune.minpower*powerfac)*time,'//',l.self.powerdraw,l.self.powerdraw/time,'free',l.freepower,'max',l.maxpower)
          252  +				if (rune.minpower*powerfac)*time <= l.self.powerdraw and math.random(rune.rarity) == 1 then
   246    253   					local n = ItemStack(rune.item)
   247    254   					choices[#choices + 1] = n
   248    255   				end
   249    256   			end
   250    257   			if #choices > 0 then newrunes[#newrunes + 1] = choices[math.random(#choices)] end
          258  +			print('rune choices:',dump(choices))
          259  +			print('me',dump(l.self))
   251    260   		end
   252    261   
   253    262   		for _,r in pairs(newrunes) do
   254    263   			if i:room_for_item('cache',r) and has_phial() then
   255    264   				local qual = math.random(#constants.rune_grades)
   256    265   				rune_set(r,{grade = qual})
   257    266   				i:add_item('cache',r)
................................................................................
   442    451   	allow_metadata_inventory_move = function(pos, fl,fi, tl,ti, count, user)
   443    452   		local inv = minetest.get_meta(pos):get_inventory()
   444    453   		local wrench if not inv:is_empty('wrench') then
   445    454   			wrench = inv:get_stack('wrench',1):get_definition()._proto
   446    455   		end
   447    456   		if fl == 'cache' then
   448    457   			if tl == 'cache' then return 1 end
   449         -			if tl == 'active' then
          458  +			if tl == 'active' and inv:is_empty('active') then
   450    459   				print(dump(wrench))
   451    460   				if wrench and wrench.powers.imbue and not inv:is_empty('amulet') then
   452    461   					local amulet = inv:get_stack('amulet',1)
   453    462   					local rune = inv:get_stack(fl,fi)
   454         -					if sorcery.data.runes[rune:get_definition()._proto.id].amulets[amulet:get_definition()._sorcery.amulet.base] then
   455         -						return 1
          463  +					local runeid = rune:get_definition()._proto.id
          464  +					local runegrade = rune:get_meta():get_int('rune_grade')
          465  +					if sorcery.data.runes[runeid].amulets[amulet:get_definition()._sorcery.amulet.base] then
          466  +						local spell do -- haaaack
          467  +							local i=ItemStack(amulet:get_name())
          468  +							local im = i:get_meta()
          469  +							im:set_string('amulet_rune',runeid)
          470  +							im:set_int('amulet_rune_grade',runegrade)
          471  +							spell = sorcery.amulet.getspell(i)
          472  +						end
          473  +						if not spell.mingrade or runegrade >= spell.mingrade then
          474  +							return 1
          475  +						end
   456    476   					end
   457    477   				end
   458    478   			end
   459    479   		end
   460    480   		if fl == 'active' then
   461    481   			if tl == 'cache' and wrench and (wrench.powers.extract or wrench.powers.purge) then return 1 end
   462    482   		end

Modified sorcery.md from [06f5d982e8] to [246ca8c1c8].

     6      6   
     7      7   ## first-party
     8      8    * **default**
     9      9    * **stairs** for slabs, used in crafting recipes
    10     10    * **screwdriver** for several crafting recipes
    11     11    * **vessels** for potions, ink bottles, etc.
    12     12    * **tnt** for the flamebolt spell impact effect
           13  + * **beds** for the escape amulet *(optional)*
    13     14   
    14     15   ## third-party
    15     16    * **xdecor** for various tools and ingredients, especially honey and the hammer
    16     17    * **basic_materials** for crafting ingredients
    17     18    * **instant_ores** for ore generation. temporary, will be removed and replaced with home-grown mechanism soon
    18     19    * **farming redo** for potion ingredients
    19     20    * **late** for spell, potion, and gravitator effects

Added sounds/sorcery_splunch.ogg version [1bea3745a1].

cannot compute difference between binary files

Added sounds/sorcery_stutter.ogg version [ef5efc482f].

cannot compute difference between binary files

Added sounds/sorcery_windup.ogg version [060bf562bb].

cannot compute difference between binary files

Added sounds/sorcery_zap.ogg version [0c5f6f503c].

cannot compute difference between binary files

Modified wands.lua from [a021a4fd8f] to [b35b5eeee9].

    37     37   				wandprops = { sturdiness = 1.5 };
    38     38   			};
    39     39   			diamond = {
    40     40   				item = 'sorcery:shard_diamond';
    41     41   				wandprops = { sturdiness = 1.7, reliability = 0.85 };
    42     42   			};
    43     43   			mese = {
    44         -				item = 'default:mese_fragment';
           44  +				item = 'default:mese_crystal_fragment';
    45     45   				wandprops = { generate = 2 };
    46     46   			};
    47     47   			cobalt = {
    48     48   				item = 'sorcery:powder_cobalt';
    49     49   				wandprops = { power = 1.4 };
    50     50   			};
    51     51   			iridium = {