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
2
3

4
5
6
7
8
9
10
...
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
local altar_item_offset = {
	x = 0, y = -0.3, z = 0
}


local range = function(min, max)
	local span = max - min
	local val = math.random() * span
	return val + min
end

................................................................................
					-- we pick a random gift and roll against its rarity
					-- to determine if the god is feeling generous
					local gift = sorcery.lib.tbl.pick(god.gifts)
					local data = god.gifts[gift]
					local value, rarity = data[1], data[2]
					if value <= divine_favor and math.random(rarity) == 1 then
						bestow(gift)
						print(god.name .. ' has produced ' .. gift .. ' upon an altar as a gift')
						if math.random(god.generosity) == 1 then
							-- unappreciated gifts may incur divine
							-- irritation
							divine_favor = divine_favor - 1
						end
					end
				end



>







 







|







1
2
3
4
5
6
7
8
9
10
11
...
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
local altar_item_offset = {
	x = 0, y = -0.3, z = 0
}
local log = function(...) sorcery.log('altar',...) end

local range = function(min, max)
	local span = max - min
	local val = math.random() * span
	return val + min
end

................................................................................
					-- we pick a random gift and roll against its rarity
					-- to determine if the god is feeling generous
					local gift = sorcery.lib.tbl.pick(god.gifts)
					local data = god.gifts[gift]
					local value, rarity = data[1], data[2]
					if value <= divine_favor and math.random(rarity) == 1 then
						bestow(gift)
						log(god.name .. ' has produced ' .. gift .. ' upon an altar as a gift')
						if math.random(god.generosity) == 1 then
							-- unappreciated gifts may incur divine
							-- irritation
							divine_favor = divine_favor - 1
						end
					end
				end

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

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







|







 







<
|
<







 







|


|









|






|







82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
...
108
109
110
111
112
113
114

115

116
117
118
119
120
121
122
...
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
	end
	return names[math.random(#names)]
end end
local find_builtin = function(method,kind)
	return function(out)
		local rec = {}
		local crec = sorcery.lib.tbl.walk(minetest.registered_items[out],{'_sorcery','recipe','canonical',kind})
		local w, lst = 0
		if crec then
			lst = {}
			for i,v in pairs(crec) do
				if #v > w then w = #v end
				for j,n in pairs(v) do
					lst[#lst+1] = n
				end
................................................................................
			w = (i.width == 0) and 3 or i.width
			lst = i.items
		end
		-- for j=1,#i.items do
		for j,item in pairs(lst) do
			local row = math.floor((j-1) / w)
			local col = (j-1) % w

			if item then rec[1 + (row * 3) + col] = item end

		end
		return rec
	end
end
local function group_eval(i)
	if string.sub(i,1,6) == 'group:' then
		local g = string.sub(i,7)
................................................................................
		chance = 4;
		slots = {
			{0,0};
			{0,1};
		};
		pick = function(restrict)
			-- TODO make sure affinity restrictions match
			return sorcery.register.infusions.db[math.random(#sorcery.register.infusions.db)].output
		end;
		title = function(output)
			for _,i in pairs(sorcery.register.infusions.db) do
				if i.output == output then
					if i._proto and i._proto.name
						then return i._proto.name
						else break end
				end
			end
			return 'Mysterious Potion'
		end;
		find = function(out)
			for _,i in pairs(sorcery.register.infusions.db) do
				if i.output == out then
					return { i.infuse, i.into }
				end
			end
		end;
		props = function(out)
			for _,i in pairs(sorcery.register.infusions.db) do
				if i.output == out then
					if i.recipe then return i.recipe else return {} end
				end
			end
		end;
	};
	grind = {

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

13
14
15
16
17
18
19

20

21
	raspberry = {"group:food_raspberries", {228,51,210}};
	chili = {"farming:chili_pepper", {243,75,49}};
	pine = {"default:pine_sapling", {41,166,80}};
	cocoa = {"farming:cocoa_beans", {146,38,0}};
	grape = {"farming:grapes", {206,56,214}};
	kelp = {"default:sand_with_kelp", {109,185,145}};
	fern = {"default:fern_1", {164,238,47}};

	marram = {"default:marram_grass_1", {127,255,210}};

};







>

>

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

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

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







|







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

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

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

	meld = {
		{







|







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

	meld = {
		{

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

32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
..
47
48
49
50
51
52
53


54
55
56
57
58









































59
60


61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84

















85
86
87
88
89
90
91
...
121
122
123
124
125
126
127




128
129








130
131
132
133
134
135
136
...
141
142
143
144
145
146
147



















148
149

150
151
152
153
154
155
156
...
206
207
208
209
210
211
212

213
214
215
216
217
218
219
...
222
223
224
225
226
227
228




229
230
231
232
233
234
235
236
237
238
						name = 'Arrival';
						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";
					};
				};
			};
			sapphire = {
				name = 'Return';
				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';
					};
				};
			};
			emerald = {
				name = 'Banishment';
				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';
				frame = {
					iridium = {
						name = 'Mass Banishment';
						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';
					};
				};
			};
			ruby = {
				name = 'Escape';
				desc = 'Immediately transport yourself out of a dangerous situation back to the last place you slept';

















				frame = {
					cobalt = {
						name = 'Vengeful Exit';
						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';
					};
					iridium = {
						name = 'Mass Escape';
................................................................................
		minpower = 1;
		rarity = 7;
		amulets = {
			amethyst = {
				name = 'Hurling';
				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';
			};




			diamond = {
				name = 'Shockwave';








				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';
			};
		};
	};
	obliterate = {
		name = 'Obliterate';
		tone = {255,0,10};
................................................................................
				name = 'Sapping';
				desc = 'Punch a hole in enemy fortifications big enough to slip through but small enough to avoid immediate attention';
			};
			ruby = {
				name = 'Shattering';
				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)
................................................................................
			};
			diamond = {
				name = 'Radiance';
				desc = 'Set the air around you alight with a mystic luminance, letting you see clearly a great distance in every direction for several minutes';
				frame = {
					iridium = {
						name = 'Sunshine';

						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';
					};
				};
			};
		};
	};
	dominate = {
................................................................................
		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())








|







 







>
>




<
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
|
>
>







|













|


>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







 







>
>
>
>
|

>
>
>
>
>
>
>
>







 







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>


>







 







>







 







>
>
>
>


|







32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
..
47
48
49
50
51
52
53
54
55
56
57
58
59

60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
...
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
...
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
...
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
...
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
						name = 'Arrival';
						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";
					};
				};
			};
			sapphire = {
				name = 'Return';
				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.';
				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
						local delay = math.max(3,10 - ctx.stats.power) + 3*(math.random()*2-1)
						print('teledelay',delay,ctx.stats.power)
						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

								minetest.sound_play('sorcery_stutter', {
									object = s, gain = 0.8;
								},true)
								local windup = minetest.sound_play('sorcery_windup',{
									object = s, gain = 0.4;
								})
								local mydelay = delay + math.random(-10,10)*.1;
								local spark = sorcery.lib.image('sorcery_spark.png')
								local sh = s:get_properties().eye_height
								local sparkle = function(amt,time,minsize,maxsize)
									minetest.add_particlespawner {
										amount = amt, time = time, attached = s;
										minpos = { x = -0.3, y = -0.5, z = -0.3 };
										maxpos = { x =  0.3, y = sh*1.1, z = 0.3 };
										minvel = { x = -0.4, y = -0.2, z = -0.4 };
										maxvel = { x =  0.4, y =  0.2, z =  0.4 };
										minacc = { x = -0.5, y = -0.4, z = -0.5 };
										maxacc = { x =  0.5, y =  0.4, z =  0.5 };
										minexptime = 1.0, maxexptime = 2.0;
										minsize = minsize, maxsize = maxsize, glow = 14;
										texture = spark:blit(spark:multiply(sorcery.lib.color(29,205,247))):render();
										animation = {
											type = 'vertical_frames';
											aspect_w = 16, aspect_h = 16;
										};
									}
								end
								sparkle(mydelay*100,mydelay,0.3,1.3)
								minetest.after(mydelay*0.4, function()
									local timeleft = mydelay - (mydelay*0.4)
									sparkle(timeleft*150, timeleft, 0.6,1.8)
								end)
								minetest.after(mydelay*0.7, function()
									local timeleft = mydelay - (mydelay*0.7)
									sparkle(timeleft*80, timeleft, 2,4)
								end)
								sorcery.lib.node.preload(pt,s)
								minetest.after(mydelay, function()
									minetest.sound_stop(windup)
									minetest.sound_play('sorcery_zap', { pos = pt, gain = 0.4 },true)
									minetest.sound_play('sorcery_zap', { pos = s:get_pos(), gain = 0.4 },true)
									sorcery.vfx.body_sparkle(nil,sorcery.lib.color(20,255,120),2,s:get_pos())
									s:set_pos(pt)
									sorcery.vfx.body_sparkle(s,sorcery.lib.color(20,120,255),2)
								end)
							end
						end
					end
				end;
				frame = {
					iridium = {
						name = 'Mass Return';
						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';
					};
				};
			};
			emerald = {
				name = 'Banishment';
				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';
				frame = {
					iridium = {
						name = 'Mass Banishment';
						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';
					};
				};
			};
			ruby = minetest.get_modpath('beds') and {
				name = 'Escape';
				desc = 'Immediately transport yourself out of a dangerous situation back to the last place you slept';
				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()]
						if spp then
							local oldpos = s:get_pos()
							minetest.sound_play('sorcery_splunch', {pos=oldpos}, true)
							sorcery.vfx.body_sparkle(nil,sorcery.lib.color(244,38,131),2,oldpos)
							s:set_pos(spp)
							minetest.sound_play('sorcery_splunch', {pos=spp}, true)
							sorcery.vfx.body_sparkle(nil,sorcery.lib.color(244,38,89),2,spp)
						end
						-- TODO decide what happens to the people who don't have
						-- respawn points already set
					end
				end;
				frame = {
					cobalt = {
						name = 'Vengeful Exit';
						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';
					};
					iridium = {
						name = 'Mass Escape';
................................................................................
		minpower = 1;
		rarity = 7;
		amulets = {
			amethyst = {
				name = 'Hurling';
				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';
			};
			sapphire = {
				name = 'Flinging';
				desc = 'Toss an enemy violently into the air, and allow the inevitable impact to do your dirty work for you';
			};
			emerald = {
				name = 'Shockwave';
				desc = 'Let loose a stream of concussive force that slams into everything in your path and sends them hurtling away from you';
			};
			luxite = {
				name = 'Repulsive Aura';
				desc = 'For a period of time, anyone who approaches you will be violently thrust aside';
			};
			diamond = {
				name = 'Blastwave';
				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';
			};
		};
	};
	obliterate = {
		name = 'Obliterate';
		tone = {255,0,10};
................................................................................
				name = 'Sapping';
				desc = 'Punch a hole in enemy fortifications big enough to slip through but small enough to avoid immediate attention';
			};
			ruby = {
				name = 'Shattering';
				desc = 'Tear a violent wound in the earth with the destructive force of this amulet';
			};
			emerald = {
				name = 'Detonate';
				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';
				cast = function(ctx)
					local speed = 40
					local radius = math.random(math.floor(ctx.stats.power*0.5),math.ceil(ctx.stats.power))
					local heading = ctx.heading
					heading.pos.y = heading.pos.y + heading.eyeheight*0.9
					local vel = vector.multiply(heading.yaw,speed)
					local bolt = minetest.add_entity(vector.add(heading.pos,vector.multiply(heading.yaw,2.5)),'sorcery:spell_projectile_flamebolt')
					bolt:set_rotation(heading.yaw)
					bolt:get_luaentity()._blastradius = radius
					bolt:set_velocity(vel)
				end;
			};
			luxite = {
				name = 'Lethal Aura';
				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';
			};
			diamond = {
				name = 'Killing';
				mingrade = 4;
				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)
................................................................................
			};
			diamond = {
				name = 'Radiance';
				desc = 'Set the air around you alight with a mystic luminance, letting you see clearly a great distance in every direction for several minutes';
				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';
					};
				};
			};
		};
	};
	dominate = {
................................................................................
		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.';
			};
			emerald = {
				name = 'Caging';
				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';
			};
			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 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())

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

179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
...
491
492
493
494
495
496
497

498
499
500
501
502
503
504
505
506
507
508
509
510
		uses = 32;
		affinity = {'acacia','blazing'};
		leytype = 'praxic';
		desc = 'Conjure a gout of fire to scorch your foes with a flick of this wand';
		cast = function(ctx)
			local speed = 30 -- TODO maybe amethyst tip increases speed?
			local radius = math.random(math.max(1,math.floor((ctx.stats.power or 1) - 0.5)), math.ceil((ctx.stats.power or 1)+0.5))
			print('!! radius',radius)
			local heading = ctx.heading
			heading.pos.y = heading.pos.y + heading.eyeheight*0.9
			local vel = vector.multiply(heading.yaw,speed)
			local bolt = minetest.add_entity(vector.add(heading.pos,vector.multiply(heading.yaw,2.5)),'sorcery:spell_projectile_flamebolt')
			bolt:set_rotation(heading.yaw)
			bolt:get_luaentity()._blastradius = radius
			bolt:set_velocity(vel)
................................................................................
		cast = function(ctx)
			local tgt = target_node(ctx, 'sorcery:enchanter')
			if not tgt then return false end

			local inv = minetest.get_meta(ctx.target.under):get_inventory()
			for _,name in pairs{'foci','item'} do
				for i=1,inv:get_size(name) do

					local stack = 'sorcery:ash'
					if ctx.base.gem == 'sapphire' then
						stack = nil
					end
					inv:set_stack(name,i,ItemStack(stack))
				end
			end

			enchantment_sparkle(ctx,sorcery.lib.color(255,12,0))
			enchantment_sparkle(ctx,sorcery.lib.color(85,18,35))
			enchantment_sparkle(ctx,sorcery.lib.color(0,0,0))
		end
	};







<







 







>





|







179
180
181
182
183
184
185

186
187
188
189
190
191
192
...
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
		uses = 32;
		affinity = {'acacia','blazing'};
		leytype = 'praxic';
		desc = 'Conjure a gout of fire to scorch your foes with a flick of this wand';
		cast = function(ctx)
			local speed = 30 -- TODO maybe amethyst tip increases speed?
			local radius = math.random(math.max(1,math.floor((ctx.stats.power or 1) - 0.5)), math.ceil((ctx.stats.power or 1)+0.5))

			local heading = ctx.heading
			heading.pos.y = heading.pos.y + heading.eyeheight*0.9
			local vel = vector.multiply(heading.yaw,speed)
			local bolt = minetest.add_entity(vector.add(heading.pos,vector.multiply(heading.yaw,2.5)),'sorcery:spell_projectile_flamebolt')
			bolt:set_rotation(heading.yaw)
			bolt:get_luaentity()._blastradius = radius
			bolt:set_velocity(vel)
................................................................................
		cast = function(ctx)
			local tgt = target_node(ctx, 'sorcery:enchanter')
			if not tgt then return false end

			local inv = minetest.get_meta(ctx.target.under):get_inventory()
			for _,name in pairs{'foci','item'} do
				for i=1,inv:get_size(name) do
					if inv:get_stack(name,i):is_empty() then goto skip end
					local stack = 'sorcery:ash'
					if ctx.base.gem == 'sapphire' then
						stack = nil
					end
					inv:set_stack(name,i,ItemStack(stack))
				::skip::end
			end

			enchantment_sparkle(ctx,sorcery.lib.color(255,12,0))
			enchantment_sparkle(ctx,sorcery.lib.color(85,18,35))
			enchantment_sparkle(ctx,sorcery.lib.color(0,0,0))
		end
	};

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

5
6
7
8
9
10
11

basic_materials
vessels
late
instant_ores
screwdriver
hopper?
unifieddyes?








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

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

61
62
63
64
65
66
67
68
69
70
71
72











73
74
75
76
77
78
79
			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();







<
<



>
>
>
>
>
>
>
>
>
>
>







61
62
63
64
65
66
67


68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
			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;


				amulet = stack;
				meta = stack:get_meta(); -- avoid spell boilerplate
				color = sorcery.lib.color(sp.tone);
				today = minetest.get_day_count();
				heading = {
					pos   = user:get_pos();
					yaw   = user:get_look_dir();
					pitch = user:get_look_vertical();
					angle = user:get_look_horizontal();
					eyeheight = user:get_properties().eye_height;
				};

				sound = "xdecor_enchanting"; --FIXME make own sounds
				sparkle = true;
			}
			print('casting')
			local res = sp.cast(ctx)

			if res == nil or res == true then
				minetest.sound_play(ctx.sound, { 
					pos = user:get_pos();

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

297
298
299
300
301
302
303

304
305
306
307
308
309
310
...
311
312
313
314
315
316
317




318
319
320
321
322
323
324
325
326
327
328



329
330
331
332
333
334
335
...
362
363
364
365
366
367
368




369
370
371
372
373
374
375
...
392
393
394
395
396
397
398




399
400



401
402
403
404
405
406
407
				local found = false
				for _,u in pairs(dsp.users) do
					if u.object:get_player_name() == name then
						found = true
					end
				end
				if not found then

					portal_context.users[name] = nil
				end
			end
		end

		-- one user per pad only!
		for _,n in pairs(dev.nodes) do
................................................................................
			for _,u in pairs(dsp.users) do
				if u.slot == n then
					local pname = u.object:get_player_name()
					if not portal_context.users[pname] then
						portal_context.users[pname] = { time = 0, portal = pos } end
					local user = portal_context.users[pname]
					if not vector.equals(pos,user.portal) then




						user.time = 0
						user.portal = pos
					end
					local cap = sorcery.ley.netcaps(pos,delta)
					local jc = (constants.portal_jump_cost_local*delta)
					if not user.dest and cap.freepower >= jc  then
						user.dest = portal_pick_destination(dev,crc,partner)
						sorcery.lib.node.preload(user.dest, u.object)
					end
					if not user.dest then goto skippad end
					local fac = math.min(1,(user.time / constants.portal_jump_time))



					minetest.add_particlespawner {
						time = 1, amount = 100 + (fac * 200);
						minsize = 0.2 + fac*0.7, maxsize = 0.4 + fac*0.9;
						minvel = {y = 0.2, x=0,z=0}, maxvel = {y = 0.5, x=0,z=0};
						minacc = {y = 0.0, x=0,z=0}, maxacc = {y = 0.3, x=0,z=0};
						minpos = vector.add(n.pad,{x = -0.5, y = 0.5, z = -0.5});
						maxpos = vector.add(n.pad,{x =  0.5, y = 0.5, z =  0.5});
................................................................................
								aspect_w = 16, aspect_h = 16;
							};
						}
					end
					-- hack to try and swat an unkillable fucking impossibug
					if user.time > constants.portal_jump_time * 2 then
						user.time = 0




					elseif user.time >= constants.portal_jump_time then
						local dd = portal_disposition(portal_composition(user.dest))
						if #dd.freepads > 0 then
							local destpad = dd.freepads[math.random(#dd.freepads)].pad
							local rng = function(min,max)
								return (math.random() * (max - min)) + min
							end
................................................................................
									glow = 14;
									animation = {
										type = 'vertical_frames', length = life + 0.1;
										aspect_w = 16, aspect_h = 16;
									};
								}
							end




							user.dest = nil
							user.time = 0



							portal_context.users[pname] = nil
							u.object:set_pos(vector.add(destpad, {y=0.5,z=0,x=0}))
						end
					else
						user.time = user.time + delta
					end








>







 







>
>
>
>











>
>
>







 







>
>
>
>







 







>
>
>
>


>
>
>







297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
...
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
...
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
...
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
				local found = false
				for _,u in pairs(dsp.users) do
					if u.object:get_player_name() == name then
						found = true
					end
				end
				if not found then
					if user.sound then minetest.sound_fade(user.sound,1,0) end
					portal_context.users[name] = nil
				end
			end
		end

		-- one user per pad only!
		for _,n in pairs(dev.nodes) do
................................................................................
			for _,u in pairs(dsp.users) do
				if u.slot == n then
					local pname = u.object:get_player_name()
					if not portal_context.users[pname] then
						portal_context.users[pname] = { time = 0, portal = pos } end
					local user = portal_context.users[pname]
					if not vector.equals(pos,user.portal) then
						if user.sound then
							minetest.sound_fade(user.sound,1,0)
							user.sound = nil
						end
						user.time = 0
						user.portal = pos
					end
					local cap = sorcery.ley.netcaps(pos,delta)
					local jc = (constants.portal_jump_cost_local*delta)
					if not user.dest and cap.freepower >= jc  then
						user.dest = portal_pick_destination(dev,crc,partner)
						sorcery.lib.node.preload(user.dest, u.object)
					end
					if not user.dest then goto skippad end
					local fac = math.min(1,(user.time / constants.portal_jump_time))
					if user.time == 0 then
						user.sound = minetest.sound_play('sorcery_windup', {pos=pos})
					end
					minetest.add_particlespawner {
						time = 1, amount = 100 + (fac * 200);
						minsize = 0.2 + fac*0.7, maxsize = 0.4 + fac*0.9;
						minvel = {y = 0.2, x=0,z=0}, maxvel = {y = 0.5, x=0,z=0};
						minacc = {y = 0.0, x=0,z=0}, maxacc = {y = 0.3, x=0,z=0};
						minpos = vector.add(n.pad,{x = -0.5, y = 0.5, z = -0.5});
						maxpos = vector.add(n.pad,{x =  0.5, y = 0.5, z =  0.5});
................................................................................
								aspect_w = 16, aspect_h = 16;
							};
						}
					end
					-- hack to try and swat an unkillable fucking impossibug
					if user.time > constants.portal_jump_time * 2 then
						user.time = 0
						if user.sound then
							minetest.sound_stop(user.sound)
							user.sound = nil
						end
					elseif user.time >= constants.portal_jump_time then
						local dd = portal_disposition(portal_composition(user.dest))
						if #dd.freepads > 0 then
							local destpad = dd.freepads[math.random(#dd.freepads)].pad
							local rng = function(min,max)
								return (math.random() * (max - min)) + min
							end
................................................................................
									glow = 14;
									animation = {
										type = 'vertical_frames', length = life + 0.1;
										aspect_w = 16, aspect_h = 16;
									};
								}
							end
							if user.sound then
								minetest.sound_fade(user.sound,1,0)
								user.sound = nil
							end
							user.dest = nil
							user.time = 0
							user.sound = nil
							minetest.sound_play('sorcery_zap',{pos=pos},true)
							minetest.sound_play('sorcery_zap',{pos=destpad},true)
							portal_context.users[pname] = nil
							u.object:set_pos(vector.add(destpad, {y=0.5,z=0,x=0}))
						end
					else
						user.time = user.time + delta
					end

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
..
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
..
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
..
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
...
132
133
134
135
136
137
138
139




140

141
142
143
144
145
146
147
...
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212

213
214
215
216
217
218
219
220
221
222
223
224
225
226
...
233
234
235
236
237
238
239

240
241
242
243
244


245
246
247
248
249
250


251
252
253
254
255
256
257
...
442
443
444
445
446
447
448
449
450
451
452
453


454








455

456
457
458
459
460
461
462
-- 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 = 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', 'Weak', 'Ordinary', 'Pristine', 'Sublime'};
	-- how many grades of rune quality/power there are
................................................................................
		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)
................................................................................
		};
		_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;
................................................................................
		};
	})
	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';
................................................................................
	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 %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 {
................................................................................
	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,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, 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;
	}
................................................................................
	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 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
					local n = ItemStack(rune.item)
					choices[#choices + 1] = n
				end
			end
			if #choices > 0 then newrunes[#newrunes + 1] = choices[math.random(#choices)] end


		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)
				rune_set(r,{grade = qual})
				i:add_item('cache',r)
................................................................................
	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
				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' and wrench and (wrench.powers.extract or wrench.powers.purge) then return 1 end
		end







|







 







|







 







|





|











|







 







|







 







<
>
>
>
>
|
>







 







|









>




<
|
|







 







>
|




>
>
|





>
>







 







|




>
>
|
>
>
>
>
>
>
>
>
|
>







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
..
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
..
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
..
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
...
132
133
134
135
136
137
138

139
140
141
142
143
144
145
146
147
148
149
150
151
...
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221

222
223
224
225
226
227
228
229
230
...
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
...
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
-- 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 = 240;
	-- 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', 'Weak', 'Ordinary', 'Pristine', 'Sublime'};
	-- how many grades of rune quality/power there are
................................................................................
		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.data.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)
................................................................................
		};
		_proto = { id = name, data = rune; };
	})
end)

for name,p in pairs(constants.phial_kinds) do
	local f = string.format
	local color = sorcery.lib.color(204,38,235)
	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.";
		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 = 'sorcery:'..id;
	}
end

local register_rune_wrench = function(w)
	local mp = sorcery.data.metals[w.metal].parts
	minetest.register_tool(w.name, {
		description = w.desc;
................................................................................
		};
	})
	minetest.register_craft {
		output = w.name;
		recipe = {
			{'',                        mp.fragment,''};
			{'',                        mp.ingot,   mp.fragment};
			{'sorcery:fragment_vidrium','',         ''};
		};
	}
end

register_rune_wrench {
	name = 'sorcery:rune_wrench', desc = 'Rune Wrench';
	img = 'sorcery_rune_wrench.png', metal = 'brass';
................................................................................
	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
		if spell.minrune then -- indicating quality makes less sense if it's restricted
			name = string.format('Amulet of %s', spell.name)
		else
			name = string.format('Amulet of %s %s', constants.amulet_grades[rg], spell.name)
		end
		m:set_string('description', sorcery.lib.ui.tooltip {
			title = name;
			color = spell.tone;
			desc = spell.desc;
		})

		if spell.apply then spell.apply {
................................................................................
	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,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
	end
	
	return {

		rune = rune, grade = rg;
		spell = spell, mingrade = mingrade;
		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;
	}
................................................................................
	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 has_phial() and pow_min then -- roll for runes
		local int, powerfac = calc_phial_props(i:get_stack('phial',1))
		local rolls = math.floor(time/int)
		local newrunes = {}
		for _=1,rolls do
			local choices = {}
			for name,rune in pairs(sorcery.data.runes) do
				print('considering',name)
				print('-- power',rune.minpower,(rune.minpower*powerfac)*time,'//',l.self.powerdraw,l.self.powerdraw/time,'free',l.freepower,'max',l.maxpower)
				if (rune.minpower*powerfac)*time <= l.self.powerdraw and math.random(rune.rarity) == 1 then
					local n = ItemStack(rune.item)
					choices[#choices + 1] = n
				end
			end
			if #choices > 0 then newrunes[#newrunes + 1] = choices[math.random(#choices)] end
			print('rune choices:',dump(choices))
			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)
				rune_set(r,{grade = qual})
				i:add_item('cache',r)
................................................................................
	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' and inv:is_empty('active') 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)
					local runeid = rune:get_definition()._proto.id
					local runegrade = rune:get_meta():get_int('rune_grade')
					if sorcery.data.runes[runeid].amulets[amulet:get_definition()._sorcery.amulet.base] then
						local spell do -- haaaack
							local i=ItemStack(amulet:get_name())
							local im = i:get_meta()
							im:set_string('amulet_rune',runeid)
							im:set_int('amulet_rune_grade',runegrade)
							spell = sorcery.amulet.getspell(i)
						end
						if not spell.mingrade or runegrade >= spell.mingrade then
							return 1
						end
					end
				end
			end
		end
		if fl == 'active' then
			if tl == 'cache' and wrench and (wrench.powers.extract or wrench.powers.purge) then return 1 end
		end

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

6
7
8
9
10
11
12

13
14
15
16
17
18
19

## first-party
 * **default**
 * **stairs** for slabs, used in crafting recipes
 * **screwdriver** for several crafting recipes
 * **vessels** for potions, ink bottles, etc.
 * **tnt** for the flamebolt spell impact effect


## third-party
 * **xdecor** for various tools and ingredients, especially honey and the hammer
 * **basic_materials** for crafting ingredients
 * **instant_ores** for ore generation. temporary, will be removed and replaced with home-grown mechanism soon
 * **farming redo** for potion ingredients
 * **late** for spell, potion, and gravitator effects







>







6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

## first-party
 * **default**
 * **stairs** for slabs, used in crafting recipes
 * **screwdriver** for several crafting recipes
 * **vessels** for potions, ink bottles, etc.
 * **tnt** for the flamebolt spell impact effect
 * **beds** for the escape amulet *(optional)*

## third-party
 * **xdecor** for various tools and ingredients, especially honey and the hammer
 * **basic_materials** for crafting ingredients
 * **instant_ores** for ore generation. temporary, will be removed and replaced with home-grown mechanism soon
 * **farming redo** for potion ingredients
 * **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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
				wandprops = { sturdiness = 1.5 };
			};
			diamond = {
				item = 'sorcery:shard_diamond';
				wandprops = { sturdiness = 1.7, reliability = 0.85 };
			};
			mese = {
				item = 'default:mese_fragment';
				wandprops = { generate = 2 };
			};
			cobalt = {
				item = 'sorcery:powder_cobalt';
				wandprops = { power = 1.4 };
			};
			iridium = {







|







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