sorcery  Check-in [83426a2748]

Overview
Comment:balance amulets better, add sound effects, add debugging privilege for runes, swat various glitches and bugs
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: 83426a2748e5b3d65016273423dd93acce6a53af54a1f71a97a5625256f9dbd2
User & Date: lexi on 2020-10-22 15:51:39
Other Links: manifest | tags
Context
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
2020-10-21
03:35
add rune forges, runes, amulet frames, write sacrifice spell, touch up amulet graphics, enable enchantment of amulets (though spells cannot yet be cast), defuckulate syncresis core icon, unfuckitize sneaky leycalc bug that's probably been the cause of some long-standing wackiness, add item classes, add some more textures, disbungle various other asstastrophes, remove sneaky old debug code, improve library code, add utility for uploading merge requests check-in: 96c5289a2a user: lexi tags: trunk
Changes

Modified coins.lua from [7e4d0ae5aa] to [d6a069e16f].

179
180
181
182
183
184
185

186
187
188
189
190
191
192
193
194
195
196
197
198
			local inv = meta:get_inventory()
			local reduce_slot = function(slot)
				local i = inv:get_stack(slot,1)
				i:take_item(items_used) inv:set_stack(slot,1,i)
			end
			reduce_slot('ingot')
			if not inv:is_empty('gem') then reduce_slot('gem') end

		end
		update_press_output(meta)
	end;
}) end

minetest.register_craft {
	output = 'sorcery:coin_press';
	recipe = {
		{'group:wood','group:wood','group:wood'};
		{'basic_materials:steel_bar','default:steel_ingot','basic_materials:steel_bar'};
		{'default:copper_ingot','default:stone','default:copper_ingot'};
	};
}







>













179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
			local inv = meta:get_inventory()
			local reduce_slot = function(slot)
				local i = inv:get_stack(slot,1)
				i:take_item(items_used) inv:set_stack(slot,1,i)
			end
			reduce_slot('ingot')
			if not inv:is_empty('gem') then reduce_slot('gem') end
			minetest.sound_play('sorcery_coins', { pos = pos, gain = 0.7 })
		end
		update_press_output(meta)
	end;
}) end

minetest.register_craft {
	output = 'sorcery:coin_press';
	recipe = {
		{'group:wood','group:wood','group:wood'};
		{'basic_materials:steel_bar','default:steel_ingot','basic_materials:steel_bar'};
		{'default:copper_ingot','default:stone','default:copper_ingot'};
	};
}

Modified data/metals.lua from [127702c66e] to [3560b06dd9].

218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
...
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
		slots = {
			{
				affinity = {'counterpraxic'};
				confluence = 0.65;
				interference = {speed = 1};
			};
		};
		amulet = {};
	};
	lithium = {
		tone = {255,252,93}, alpha = 80;
		dye = 'yellow';
		rarity = 13;
		hardness = 2;
		fuel = 80;
................................................................................
		image = {
			block = 'sorcery_metal_iridium_shiny.png';
		};
		slots = {
			{affinity={'counterpraxic','syncretic'}, confluence = 1.1};
			{affinity={'cognic','entropic'}, confluence = 0.8};
		};
		amulet = {};
	};
	duridium = {
		tone = {255,64,175}, alpha = 70;
		cooktime = 120;
		artificial = true;
		durability = 3400;
		speed = 3.1;







|







 







|







218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
...
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
		slots = {
			{
				affinity = {'counterpraxic'};
				confluence = 0.65;
				interference = {speed = 1};
			};
		};
		amulet = { power = 1.5 };
	};
	lithium = {
		tone = {255,252,93}, alpha = 80;
		dye = 'yellow';
		rarity = 13;
		hardness = 2;
		fuel = 80;
................................................................................
		image = {
			block = 'sorcery_metal_iridium_shiny.png';
		};
		slots = {
			{affinity={'counterpraxic','syncretic'}, confluence = 1.1};
			{affinity={'cognic','entropic'}, confluence = 0.8};
		};
		amulet = { power = 1.7 };
	};
	duridium = {
		tone = {255,64,175}, alpha = 70;
		cooktime = 120;
		artificial = true;
		durability = 3400;
		speed = 3.1;

Modified data/oils.lua from [f8f2204848] to [fb12becfb1].

95
96
97
98
99
100
101
102








103


			'sorcery:extract_raspberry';
			'sorcery:extract_raspberry';
			'sorcery:extract_onion';
			'farming:peas';
			'farming:peas';
			'farming:peas';
		};
	};








}










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

Modified data/potions.lua from [eb3ff7cdbe] to [018fbedf8a].

31
32
33
34
35
36
37



38

		color = {119,51,111};
		infusion = 'sorcery:oil_bleak';
	};
	Isolating = {
		color = {188,78,225};
		infusion = 'sorcery:extract_fern';
	};



}








>
>
>
|
>
31
32
33
34
35
36
37
38
39
40
41
42
		color = {119,51,111};
		infusion = 'sorcery:oil_bleak';
	};
	Isolating = {
		color = {188,78,225};
		infusion = 'sorcery:extract_fern';
	};
	Subtle = {
		color = {230,253,150}, glow = 6;
		infusion = 'sorcery:oil_luscious';
	};
}

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

9
10
11
12
13
14
15
16





17
18
19
20
21
22
23
..
27
28
29
30
31
32
33
34

























35
36
37
38
39
40
41
...
114
115
116
117
118
119
120









121
122
123
124
125
126
127
...
179
180
181
182
183
184
185




186
187
188
























































189
190
191
192
193
194
195
		name = 'Translocate';
		tone = {0,235,233};
		minpower = 3;
		rarity = 15;
		amulets = {
			amethyst = {
				name = 'Joining';
				desc = 'Give this amulet to another and they can arrive at your side in a flash from anywhere in the world — though returning whence they came may be a more difficult matter';





				frame = {
					gold = {
						name = 'Exchange';
						desc = 'Give this amulet to another and they will be able to trade places with you no matter where in the world each of you might be.'; 
					};
					cobalt = {
						name = 'Sending';
................................................................................
						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 use it again to return instantly to that point.';

























				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';
					};
				};
			};
................................................................................
			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.';









				frame = {
					iridium = {
						name = 'Massacre';
						desc = "Unleash the dark and wicked force that lurks within this fell amulet to instantaneously slay all those who surround you, friend and foe alike";
					};
				};
			};
................................................................................
	};
	dominate = {
		name = 'Dominate';
		tone = {235,0,228};
		minpower = 4;
		rarity = 40;
		amulets = {




			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';
























































			};
			amethyst = {
				name = 'Disarming';
				desc = 'Wield this amulet against a foe to rip all the weapons in their possession out of their grasp';
				frame = {
					iridium = {
						name = 'Peacemaking';







|
>
>
>
>
>







 







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







 







>
>
>
>
>
>
>
>
>







 







>
>
>
>



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







9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
..
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
...
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
...
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
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
		name = 'Translocate';
		tone = {0,235,233};
		minpower = 3;
		rarity = 15;
		amulets = {
			amethyst = {
				name = 'Joining';
				desc = 'Give this amulet to another and they can arrive safely at your side in a flash from anywhere in the world — though returning whence they came may be a more difficult matter';
				apply = function(ctx)
					local maker = ctx.user:get_player_name()
					ctx.meta:set_string('rune_join_target',maker)
				end;
				remove = function(ctx) ctx.meta:set_string('rune_join_target','') end;
				frame = {
					gold = {
						name = 'Exchange';
						desc = 'Give this amulet to another and they will be able to trade places with you no matter where in the world each of you might be.'; 
					};
					cobalt = {
						name = 'Sending';
................................................................................
						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';
					};
				};
			};
................................................................................
			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)
					minetest.sound_play('sorcery_bloody_burst', { pos = pos, gain = 1.5 })
					tgt:set_hp(0)
				end;
				frame = {
					iridium = {
						name = 'Massacre';
						desc = "Unleash the dark and wicked force that lurks within this fell amulet to instantaneously slay all those who surround you, friend and foe alike";
					};
				};
			};
................................................................................
	};
	dominate = {
		name = 'Dominate';
		tone = {235,0,228};
		minpower = 4;
		rarity = 40;
		amulets = {
			amethyst = {
				name = 'Suffocation';
				desc = 'Wrap this spell tightly around your victim\'s throat, cutting off their oxygen until you release them.';
			};
			ruby = {
				name = 'Exsanguination';
				desc = 'Rip the life force out of another, leaving them on the brink of death, and use it to mend your own wounds and invigorate your own being';
				cast = function(ctx)
					if not (ctx.target and ctx.target.type == 'object') then return false end
					local tgt = ctx.target.ref
					local takefac = math.min(99,50 + (ctx.stats.power * 5)) / 100
					local dmg = tgt:get_hp() * takefac
					print("!!! dmg calc",takefac,dmg,tgt:get_hp())

					local numhits = math.random(6,10+ctx.stats.power/2)
					local function dohit(hitsleft)
						if tgt == nil or tgt:get_properties() == nil then return end
						tgt:punch(ctx.caster, 1, {
							full_punch_interval = 1;
							damage_groups = { fleshy = dmg / numhits }
						})
						local tgth = tgt:get_properties().eye_height
						sorcery.vfx.bloodburst(vector.add(tgt:get_pos(),{x=0,y=tgth/2,z=0}),math.random(10 * takefac, 40 * takefac))
						ctx.caster:set_hp(ctx.caster:get_hp() + math.max(1,(dmg/numhits)*takefac))

						local sound = {'sorcery_bloody_hit','sorcery_crunch',false}
						sound = sound[math.random(#sound)]
						if sound ~= false then
							minetest.sound_play(sound, { pos = pos, gain = math.random(5,15)*0.1 })
						end

						local nexthit = math.random() * 0.4 + 0.1
						local dir = vector.subtract(ctx.caster:get_pos(), tgt:get_pos())
						local spark = sorcery.lib.image('sorcery_spark.png')
						minetest.add_particlespawner {
							amount = math.random(80*takefac,150*takefac);
							texture = spark:blit(spark:multiply(sorcery.lib.color(255,20,10))):render();
							time = nexthit;
							attached = tgt;
							minpos = {x = -0.3, y = -0.5, z = -0.3};
							maxpos = {x =  0.3, y = tgth, z = 0.3};
							minvel = vector.multiply(dir,0.5);
							maxvel = vector.multiply(dir,0.9);
							minacc = vector.multiply(dir,0.1);
							maxacc = vector.multiply(dir,0.2);
							minexptime = nexthit * 1.5;
							maxexptime = nexthit * 2;
							minsize = 0.5;
							maxsize = 5 * takefac;
							glow = 14;
							animation = {
								type = 'vertical_frames';
								aspect_w = 16, aspect_h = 16;
								length = nexthit*2 + 0.1;
							};
						}

						if hitsleft > 0 then
							minetest.after(nexthit, function() dohit(hitsleft-1) end)
						end
					end
					dohit(numhits)
				end;
			};
			amethyst = {
				name = 'Disarming';
				desc = 'Wield this amulet against a foe to rip all the weapons in their possession out of their grasp';
				frame = {
					iridium = {
						name = 'Peacemaking';

Modified enchanter.lua from [bac74a37c3] to [b41a4d81d6].

311
312
313
314
315
316
317


318
319
320
321
322
323
324
		buildable_to = true;
		sunlight_propagates = true;
		light_source = i + 4;
		groups = {
			air = 1, sorcery_air = 1;
			not_in_creative_inventory = 1;
		};


		on_construct = function(pos)
			minetest.get_node_timer(pos):start(0.05)
		end;
		on_timer = function(pos)
			if i <= 2 then minetest.remove_node(pos) else
				minetest.set_node(pos, {name='sorcery:air_flash_1'})
				return true







>
>







311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
		buildable_to = true;
		sunlight_propagates = true;
		light_source = i + 4;
		groups = {
			air = 1, sorcery_air = 1;
			not_in_creative_inventory = 1;
		};
		drop = {max_items = 0, items = {}};
		on_blast = function() end; -- not affected by explosions
		on_construct = function(pos)
			minetest.get_node_timer(pos):start(0.05)
		end;
		on_timer = function(pos)
			if i <= 2 then minetest.remove_node(pos) else
				minetest.set_node(pos, {name='sorcery:air_flash_1'})
				return true

Modified forcefield.lua from [72bf5182e0] to [a330742505].

45
46
47
48
49
50
51


52
53
54
55
56
57
58
	minetest.register_node('sorcery:air_barrier_' .. tostring(i), {
		drawtype = 'glasslike';
		walkable = true;
		pointable = false;
		sunlight_propagates = true;
		paramtype = 'light';
		light_source = i;


		tiles = {'sorcery_transparent.png'};
		groups = {
			air = 1;
			sorcery_air = 1;
			sorcery_force_barrier = i;
		};
		-- _proto = {







>
>







45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
	minetest.register_node('sorcery:air_barrier_' .. tostring(i), {
		drawtype = 'glasslike';
		walkable = true;
		pointable = false;
		sunlight_propagates = true;
		paramtype = 'light';
		light_source = i;
		drop = {max_items = 0, items = {}};
		on_blast = function() end; -- not affected by explosions
		tiles = {'sorcery_transparent.png'};
		groups = {
			air = 1;
			sorcery_air = 1;
			sorcery_force_barrier = i;
		};
		-- _proto = {

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

52
53
54
55
56
57
58



































59
60
61
62
63

64
65
66
67
68
69
70
..
74
75
76
77
78
79
80

81
82
83
84
85
86
87
			};
		})
	end
	if not gem.foreign_amulet then
		local img = sorcery.lib.image
		local img_stone = img('sorcery_amulet.png'):multiply(sorcery.lib.color(gem.tone))
		local img_sparkle = img('sorcery_amulet_sparkle.png')



































		minetest.register_craftitem(amuletname, {
			description = sorcery.lib.str.capitalize(name) .. ' amulet';
			inventory_image = img_sparkle:blit(img_stone):render();
			wield_scale = { x = 0.6, y = 0.6, z = 0.6 };
			groups = { sorcery_amulet = 1 };

			_sorcery = {
				material = {
					gem = true, id = name, data = gem;
					value = (5 * shards_per_gem) + 4;
				};
				amulet = { base = name };
			};
................................................................................
			local framedid = string.format("%s_frame_%s", amuletname, metalid)
			local img_frame = img(string.format('sorcery_amulet_frame_%s.png',metalid))
			minetest.register_craftitem(framedid, {
				description = string.format("%s-framed %s amulet",sorcery.lib.str.capitalize(metalid), name);
				inventory_image = img_sparkle:blit(img_frame):blit(img_stone):render();
				wield_scale = { x = 0.6, y = 0.6, z = 0.6 };
				groups = { sorcery_amulet = 1 };

				_sorcery = {
					amulet = { base = name, frame = metalid };
				};
			})
			local frag = metal.parts.fragment
			minetest.register_craft {
				output = framedid;







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





>







 







>







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
...
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
			};
		})
	end
	if not gem.foreign_amulet then
		local img = sorcery.lib.image
		local img_stone = img('sorcery_amulet.png'):multiply(sorcery.lib.color(gem.tone))
		local img_sparkle = img('sorcery_amulet_sparkle.png')
		local useamulet = function(stack,user,target)
			local sp = sorcery.amulet.getspell(stack)
			if not sp or not sp.cast then return nil end
			local stats = sorcery.amulet.stats(stack)

			local ctx = {
				caster = user;
				target = target;
				stats = stats;
				sound = "xdecor_enchanting"; --FIXME make own sounds
				sparkle = true;
				amulet = stack;
				meta = stack:get_meta(); -- avoid spell boilerplate
				color = sorcery.lib.color(sp.tone);
			}
			print('casting')
			local res = sp.cast(ctx)

			if res == nil or res == true then
				minetest.sound_play(ctx.sound, { 
					pos = user:get_pos();
					gain = 1;
				})
			end
			if ctx.sparkle then
				sorcery.vfx.cast_sparkle(user, ctx.color, stats.power,0.5)
			end
			if res == nil then
				if not minetest.check_player_privs(user, 'sorcery:infinirune') then
					sorcery.amulet.setrune(stack)
				end
			end

			return ctx.amulet
		end;
		minetest.register_craftitem(amuletname, {
			description = sorcery.lib.str.capitalize(name) .. ' amulet';
			inventory_image = img_sparkle:blit(img_stone):render();
			wield_scale = { x = 0.6, y = 0.6, z = 0.6 };
			groups = { sorcery_amulet = 1 };
			on_use = useamulet;
			_sorcery = {
				material = {
					gem = true, id = name, data = gem;
					value = (5 * shards_per_gem) + 4;
				};
				amulet = { base = name };
			};
................................................................................
			local framedid = string.format("%s_frame_%s", amuletname, metalid)
			local img_frame = img(string.format('sorcery_amulet_frame_%s.png',metalid))
			minetest.register_craftitem(framedid, {
				description = string.format("%s-framed %s amulet",sorcery.lib.str.capitalize(metalid), name);
				inventory_image = img_sparkle:blit(img_frame):blit(img_stone):render();
				wield_scale = { x = 0.6, y = 0.6, z = 0.6 };
				groups = { sorcery_amulet = 1 };
				on_use = useamulet;
				_sorcery = {
					amulet = { base = name, frame = metalid };
				};
			})
			local frag = metal.parts.fragment
			minetest.register_craft {
				output = framedid;

Modified init.lua from [f9d7281393] to [e9275c1995].

128
129
130
131
132
133
134
135
136
137
138
139
	'harvester'; 'metallurgy-hot', 'metallurgy-cold';
	'entities'; 'recipes'; 'coins'; 'interop';
	'tnodes'; 'forcefield'; 'farcaster'; 'portal';
	'cookbook', 'writing'; 'disassembly'; 'displacer';
	'gravitator'; 'precipitator'; 'calendar', 'astrolabe';
	'keypunch'; 'runeforge';

	'admin';
} do sorcery.load(u) end
sorcery.stage('finalize')

sorcery.registry.defercheck()







|




128
129
130
131
132
133
134
135
136
137
138
139
	'harvester'; 'metallurgy-hot', 'metallurgy-cold';
	'entities'; 'recipes'; 'coins'; 'interop';
	'tnodes'; 'forcefield'; 'farcaster'; 'portal';
	'cookbook', 'writing'; 'disassembly'; 'displacer';
	'gravitator'; 'precipitator'; 'calendar', 'astrolabe';
	'keypunch'; 'runeforge';

	'privs', 'admin';
} do sorcery.load(u) end
sorcery.stage('finalize')

sorcery.registry.defercheck()

Modified lib/node.lua from [b1f018643d] to [5b83bf6142].

63
64
65
66
67
68
69


















70
71
72
73
74
75
76
	offsets = ofs;
	purge_container = function(...) return purge_container(nil, ...) end;
	purge_only = function(lst)
		return function(...)
			return purge_container(lst, ...)
		end
	end; 



















	amass = function(startpoint,names,directions)
		if not directions then directions = ofs.neighbors end
		local nodes, positions, checked = {},{},{}
		local checkedp = function(pos)
			for _,v in pairs(checked) do
				if vector.equals(pos,v) then return true end







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







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
	offsets = ofs;
	purge_container = function(...) return purge_container(nil, ...) end;
	purge_only = function(lst)
		return function(...)
			return purge_container(lst, ...)
		end
	end; 

	is_air = function(pos)
		local n = sorcery.lib.node.force(pos)
		if n.name == 'air' then return true end
		local d = minetest.registered_nodes[n.name]
		if not d then return false end
		return not d.walkable
	end;

	get_arrival_point = function(pos)
		local air = sorcery.lib.node.is_air
		if air(pos) then
			local n = {x=0,y=1,z=0}
			if air(vector.add(pos,n)) then return pos end
			local down = vector.subtract(pos,n)
			if air(down) then return down end
		else return nil end
	end;

	amass = function(startpoint,names,directions)
		if not directions then directions = ofs.neighbors end
		local nodes, positions, checked = {},{},{}
		local checkedp = function(pos)
			for _,v in pairs(checked) do
				if vector.equals(pos,v) then return true end

Modified potions.lua from [32e1f8328e] to [f87e1ae861].

1
2
3
4



5
6
7
8
9
10
11
..
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
local u = sorcery.lib
sorcery.registry.mk('infusions',false)
sorcery.registry.mk('residue',false)




sorcery.register_potion = function(name,label,desc,color,imgvariant,glow,extra)
	local image = 'sorcery_liquid_'..(imgvariant or 'dull')..'.png' .. 
		'^[multiply:'..tostring(color)..
		'^vessels_glass_bottle.png'

	sorcery.register.residue.link('sorcery:' .. name, 'vessels:glass_bottle')
	local node = {
................................................................................
		);
		short_description = label;
		drawtype = "plantlike";
		tiles = {image};
		inventory_image = image;
		paramtype = "light";
		is_ground_content = false;
		light_source = glow or 0;
		drop = 'sorcery:' .. name;
		preserve_metadata = function(pos,node,meta,newstack)
			newstack[1]:get_meta():from_table(meta)
		end;
		walkable = false;
		selection_box = {
			type = "fixed",




>
>
>







 







|







1
2
3
4
5
6
7
8
9
10
11
12
13
14
..
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
local u = sorcery.lib
sorcery.registry.mk('infusions',false)
sorcery.registry.mk('residue',false)

sorcery.register_potion_tbl = function(tbl) -- :/
	return sorcery.register_potion(tbl.name,tbl.label,tbl.desc,tbl.color,tbl.imgvariant,tbl.glow,tbl.extra)
end
sorcery.register_potion = function(name,label,desc,color,imgvariant,glow,extra)
	local image = 'sorcery_liquid_'..(imgvariant or 'dull')..'.png' .. 
		'^[multiply:'..tostring(color)..
		'^vessels_glass_bottle.png'

	sorcery.register.residue.link('sorcery:' .. name, 'vessels:glass_bottle')
	local node = {
................................................................................
		);
		short_description = label;
		drawtype = "plantlike";
		tiles = {image};
		inventory_image = image;
		paramtype = "light";
		is_ground_content = false;
		light_source = glow and math.min(minetest.LIGHT_MAX,glow) or 0;
		drop = 'sorcery:' .. name;
		preserve_metadata = function(pos,node,meta,newstack)
			newstack[1]:get_meta():from_table(meta)
		end;
		walkable = false;
		selection_box = {
			type = "fixed",

Added privs.lua version [62e9e10513].











>
>
>
>
>
1
2
3
4
5
minetest.register_privilege('sorcery:infinirune', {
	description = "runes don't discharge upon use, for debugging use only";
	give_to_singleplayer = false;
	give_to_admin = false;
})

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







1
2
3
4
5
6
7
8
9
10


















11
12
13
14
15
16
17
..
20
21
22
23
24
25
26































































27
28
29
30
31
32
33
..
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
153
154
155
...
180
181
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
210
211
212
213









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
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265






local constants = {
	rune_mine_interval = 90;
	-- how often a powered forge rolls for new runes

	rune_cache_max = 4;
	-- how many runes a runeforge can hold at a time
	
	rune_grades = {'Fragile', 'Shoddy', 'Ordinary', 'Pristine'};
	-- how many grades of rune quality/power there are
}


















sorcery.register.runes.foreach('sorcery:generate',{},function(name,rune)
	local id = 'sorcery:rune_' .. name
	rune.image = rune.image or string.format('sorcery_rune_%s.png',name)
	rune.item = id
	minetest.register_craftitem(id, {
		description = sorcery.lib.color(rune.tone):readable():fmt(rune.name .. ' Rune');
		short_description = rune.name .. ' Rune';
................................................................................
		groups = {
			sorcery_rune = 1;
			not_in_creative_inventory = 1;
		};
		_proto = { id = name, data = rune; };
	})
end)
































































local rune_set = function(stack,r)
	local m = stack:get_meta()
	local def = stack:get_definition()._proto.data
	local grade
	if r.grade then grade = r.grade
	elseif m:contains('rune_grade') then grade = m:get_int('rune_grade') end
................................................................................
	local title = sorcery.lib.color(def.tone):readable():fmt(string.format('%s %s Rune',qpfx,def.name))

	m:set_int('rune_grade',grade)
	m:set_string('description',title)
end

sorcery.amulet = {}
sorcery.amulet.setrune = function(stack,rune)
	local m = stack:get_meta()
	if rune then
		local rp = rune:get_definition()._proto
		local rg = rune:get_meta():get_int('rune_grade')
		m:set_string('amulet_rune', rp.id)
		m:set_int('amulet_rune_grade', rg)
		local spell = sorcery.amulet.getspell(stack)
		if not spell then return nil end

		local name = string.format('Amulet of %s', spell.name)

		m:set_string('description', sorcery.lib.ui.tooltip {
			title = name;
			color = spell.tone;
			desc = spell.desc;
		})







	else

		m:set_string('description','')
		m:set_string('amulet_rune','')
		m:set_string('amulet_rune_grade','')






	end
	return stack
end

















sorcery.amulet.getrune = function(stack)
	local m = stack:get_meta()
	if not m:contains('amulet_rune') then return nil end
	local rune = m:get_string('amulet_rune')
	local grade = m:get_int('amulet_rune_grade')
	local rs = ItemStack(sorcery.data.runes[rune].item)
................................................................................
	rune_set(rs, {grade = grade})
	return rs
end

sorcery.amulet.getspell = function(stack)
	local m = stack:get_meta()
	local proto = stack:get_definition()._sorcery.amulet

	local rune = m:get_string('amulet_rune')

	local rd = sorcery.data.runes[rune]
	local spell = rd.amulets[proto.base]
	if not spell then return nil end
	local title,desc,cast = spell.name, spell.desc, spell.cast


	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



	end
	
	return {
		rune = rune;

		spell = spell;
		name = title;
		desc = desc;
		cast = cast;


		tone = sorcery.lib.color(rd.tone);

	}
end


local runeforge_update = function(pos,time)
	local m = minetest.get_meta(pos)
	local i = m:get_inventory()
	local l = sorcery.ley.netcaps(pos,time or 1)

	local pow_min = l.self.powerdraw >= l.self.minpower
	local pow_max = l.self.powerdraw >= l.self.maxpower


	if time and pow_min then -- roll for runes
		local rolls = math.floor(time/constants.rune_mine_interval)
		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

		print('rolled for runes, got', dump(newrunes))
		for _,r in pairs(newrunes) do
			if i:room_for_item('cache',r) then
				local qual = math.random(#constants.rune_grades)
				rune_set(r,{grade = qual})
				i:add_item('cache',r)





			end
		end
	end


	local spec = string.format([[
		formspec_version[3] size[10.25,8] real_coordinates[true]
		list[context;cache;%f,0.25;%u,1;]
		list[context;amulet;3.40,1.50;1,1;]
		list[context;active;5.90,1.50;1,1;]





		list[current_player;main;0.25,3;8,4;]

		image[0.25,0.50;1,1;sorcery_statlamp_%s.png]
	]], (10.5 - constants.rune_cache_max*1.25)/2, constants.rune_cache_max,

	    pow_max and 'green' or (pow_min and 'yellow') or 'off')
	











	m:set_string('formspec',spec)


	return true
end

local rfbox = {
	type = 'fixed';
	fixed = {
		-0.5, -0.5, -0.5;
................................................................................
		'default_copper_block.png';
	};
	_sorcery = {
		ley = {
			mode = 'consume';
			affinity = {'praxic'};
			power = function(pos,time)




				local max,min = 0
				for _,r in pairs(sorcery.data.runes) do
					if r.minpower > max then max = r.minpower end
					if min == nil or r.minpower < min then min = r.minpower end
				end



				return min*time,max*time
			end;
		};
		on_leychange = runeforge_update;
		recipe = {
			note = 'Periodically creates runes when sufficiently powered and can be used to imbue them into an amulet, giving it a powerful magical effect';
		};
	};
	on_construct = function(pos)
		local m = minetest.get_meta(pos)
		local i = m:get_inventory()
		i:set_size('cache',constants.rune_cache_max)
		i:set_size('amulet',1)
		i:set_size('active',1)
		m:set_string('infotext','Rune Forge')
		runeforge_update(pos)
		minetest.get_node_timer(pos):start(constants.rune_mine_interval)
	end;
	after_dig_node = sorcery.lib.node.purge_only {'amulet'};
	on_timer = runeforge_update;
	on_metadata_inventory_move = function(pos, fl,fi, tl,ti, count, user)
		local inv = minetest.get_meta(pos):get_inventory()









		if fl == 'active' then
			inv:set_stack('amulet',1,sorcery.amulet.setrune(inv:get_stack('amulet',1)))







		elseif tl == 'active' then
			inv:set_stack('amulet',1,sorcery.amulet.setrune(inv:get_stack('amulet',1), inv:get_stack(tl,ti)))








		end



	end;
	on_metadata_inventory_put = function(pos, list, idx, stack, user)
		if list == 'amulet' then
			local inv = minetest.get_meta(pos):get_inventory()

			inv:set_stack('active',1,ItemStack(sorcery.amulet.getrune(stack)))
		end




	end;
	on_metadata_inventory_take = function(pos, list, idx, stack, user)
		if list == 'amulet' then
			minetest.get_meta(pos):get_inventory():set_stack('active',1,ItemStack())
		end

	end;
	allow_metadata_inventory_put = function(pos,list,idx,stack,user)
		if list == 'amulet' then
			if minetest.get_item_group(stack:get_name(), 'sorcery_amulet') ~= 0 then
				return 1
			end










		end
		return 0
	end;
	allow_metadata_inventory_take = function(pos,list,idx,stack,user)
		if list == 'amulet' then return 1 end

		return 0
	end;
	allow_metadata_inventory_move = function(pos, fl,fi, tl,ti, count, user)




		if fl == 'cache' then
			if tl == 'cache' then return 1 end
			if tl == 'active' then
				local inv = minetest.get_meta(pos):get_inventory()
				if not inv:is_empty('amulet') then
					local amulet = inv:get_stack('amulet',1)
					local rune = inv:get_stack(fl,fi)
					if sorcery.data.runes[rune:get_definition()._proto.id].amulets[amulet:get_definition()._sorcery.amulet.base] then
						return 1
					end
				end
			end
		end
		if fl == 'active' then
			if tl == 'cache' then return 1 end
		end
		return 0
	end;
})

do local m = sorcery.data.metals
	-- temporary recipe until a fancier multi-part crafting path can be come up with
>
>
>
>
>
>

|





|

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







 







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







 







|









|
<





>
>
>
>
>
>
>

>



>
>
>
>
>
>



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







 







>

>



|
>






>
>
>




>

|
<
|
>
>

>











>

|
|












<

|



>
>
>
>
>
|



>





>
>
>
>
>




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

>
>







 







>
>
>
>





>
>
>












|
|


<





>
>
>
>
>
>
>
>
>

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

>
>
>


<
|
>


>
>
>
>





>






>
>
>
>
>
>
>
>
>
>




|
>



>
>
>
>



|
|









|







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
..
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
...
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
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
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
210
211
212
213
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
250
251
252

253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
...
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364

365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403

404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
-- 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
	
	amulet_grades = {'Slight', 'Minor', 'Major', 'Grand', 'Ultimate' };
	-- what kind of amulet each rune grade translates to
	
	phial_kinds = {
		lesser   = {grade = 1; name = 'Lesser';   infusion = 'sorcery:powder_brass'};
		simple   = {grade = 2; name = 'Simple';   infusion = 'sorcery:powder_silver'};
		great    = {grade = 3; name = 'Great';    infusion = 'sorcery:powder_gold'};
		splendid = {grade = 4; name = 'Splendid'; infusion = 'sorcery:powder_electrum'};
		exalted  = {grade = 5; name = 'Exalted';  infusion = 'sorcery:powder_levitanium'};
		supreme  = {grade = 6; name = 'Supreme';  infusion = 'sorcery:essence_force'};
	};
}
local calc_phial_props = function(phial) --> mine interval: float, time factor: float
	local g = phial:get_definition()._proto.grade
	local i = constants.rune_mine_interval 
	local fac = (g-1) / 5
	return i - ((i*0.5) * fac), 0.5 * fac
end
sorcery.register.runes.foreach('sorcery:generate',{},function(name,rune)
	local id = 'sorcery:rune_' .. name
	rune.image = rune.image or string.format('sorcery_rune_%s.png',name)
	rune.item = id
	minetest.register_craftitem(id, {
		description = sorcery.lib.color(rune.tone):readable():fmt(rune.name .. ' Rune');
		short_description = rune.name .. ' Rune';
................................................................................
		groups = {
			sorcery_rune = 1;
			not_in_creative_inventory = 1;
		};
		_proto = { id = name, data = rune; };
	})
end)

for name,p in pairs(constants.phial_kinds) do
	local f = string.format
	local color = sorcery.lib.color(255,27,188)
	local fac = p.grade / 6
	local id = f('phial_%s', name);
	sorcery.register_potion_tbl {
		name = id;
		label = f('%s Phial',p.name);
		desc = "A powerful liquid consumed in the operation of a rune forge. Its quality determines how fast new runes can be constructed.";
		color = color:brighten(1 + fac*0.5);
		imgvariant = (fac >= 5) and 'sparkle' or 'dull';
		glow = 5+p.grade;
		extra = {
			groups = { sorcery_phial = p.grade };
			_proto = { id = name, data = p };
		};
	}
	sorcery.register.infusions.link {
		infuse = p.infusion;
		into = 'sorcery:potion_subtle';
		output = id;
	}
end

local register_rune_wrench = function(w)
	local mp = sorcery.data.metals[w.metal].parts
	minetest.register_tool(w.name, {
		description = w.desc;
		inventory_image = w.img;
		groups = {
			sorcery_magitech = 1;
			sorcery_rune_wrench = 1;
			crafttool = 50;
		};
		_proto = w;
		_sorcery = {
			recipe = { note = w.note };
		};
	})
	minetest.register_craft {
		output = w.name;
		recipe = {
			{'',                        mp.fragment,''};
			{'',                        mp.ingot,   mp.fragment};
			{'sorcery:vidrium_fragment','',         ''};
		};
	}
end

register_rune_wrench {
	name = 'sorcery:rune_wrench', desc = 'Rune Wrench';
	img = 'sorcery_rune_wrench.png', metal = 'brass';
	powers = { imbue = 30 };
	note = 'A runeworking tool used to imbue amulets with enchantments';
}

register_rune_wrench {
	name = 'sorcery:rune_wrench_iridium', desc = 'Iridium Rune Wrench';
	img = 'sorcery_rune_wrench_iridium.png', metal = 'iridium';
	powers = { imbue = 80, extract = 40 };
	note = 'A rare and powerful runeworking tool used to imbue amulets with enchantments, or extract runes intact from enchanted amulets';
}

local rune_set = function(stack,r)
	local m = stack:get_meta()
	local def = stack:get_definition()._proto.data
	local grade
	if r.grade then grade = r.grade
	elseif m:contains('rune_grade') then grade = m:get_int('rune_grade') end
................................................................................
	local title = sorcery.lib.color(def.tone):readable():fmt(string.format('%s %s Rune',qpfx,def.name))

	m:set_int('rune_grade',grade)
	m:set_string('description',title)
end

sorcery.amulet = {}
sorcery.amulet.setrune = function(stack,rune,user)
	local m = stack:get_meta()
	if rune then
		local rp = rune:get_definition()._proto
		local rg = rune:get_meta():get_int('rune_grade')
		m:set_string('amulet_rune', rp.id)
		m:set_int('amulet_rune_grade', rg)
		local spell = sorcery.amulet.getspell(stack)
		if not spell then return nil end

		local name = string.format('Amulet of %s %s', constants.amulet_grades[rg], spell.name)

		m:set_string('description', sorcery.lib.ui.tooltip {
			title = name;
			color = spell.tone;
			desc = spell.desc;
		})

		if spell.apply then spell.apply {
			stack = stack;
			meta = m;
			user = user;
			self = spell;
		} end
	else
		local spell = sorcery.amulet.getspell(stack)
		m:set_string('description','')
		m:set_string('amulet_rune','')
		m:set_string('amulet_rune_grade','')
		if spell and spell.remove then spell.remove {
			stack = stack;
			meta = m;
			user = user;
			self = spell;
		} end
	end
	return stack
end

sorcery.amulet.stats = function(stack)
	local spell = sorcery.amulet.getspell(stack)
	if not spell then return nil end
	local power = spell.grade
	
	if spell.base_spell then
		-- only consider the default effect of the frame metal
		-- if the frame doesn't totally override the spell
		power = power * (spell.framestats and spell.framestats.power or 1)
	end

	return {
		power = power;
	}
end

sorcery.amulet.getrune = function(stack)
	local m = stack:get_meta()
	if not m:contains('amulet_rune') then return nil end
	local rune = m:get_string('amulet_rune')
	local grade = m:get_int('amulet_rune_grade')
	local rs = ItemStack(sorcery.data.runes[rune].item)
................................................................................
	rune_set(rs, {grade = grade})
	return rs
end

sorcery.amulet.getspell = function(stack)
	local m = stack:get_meta()
	local proto = stack:get_definition()._sorcery.amulet
	if not m:contains('amulet_rune') then return nil end
	local rune = m:get_string('amulet_rune')
	local rg = m:get_string('amulet_rune_grade')
	local rd = sorcery.data.runes[rune]
	local spell = rd.amulets[proto.base]
	if not spell then return nil end
	local title,desc,cast,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;
	}
end


local runeforge_update = function(pos,time)
	local m = minetest.get_meta(pos)
	local i = m:get_inventory()
	local l = sorcery.ley.netcaps(pos,time or 1)

	local pow_min = l.self.powerdraw >= l.self.minpower
	local pow_max = l.self.powerdraw >= l.self.maxpower
	local has_phial = function() return not i:is_empty('phial') end

	if time and 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)
				-- consume a phial
				local ph = i:get_stack('phial',1)
				local n = ph:get_name()
				ph:take_item(1) i:set_stack('phial',1,ph)
				minetest.add_item(pos,i:add_item('refuse',ItemStack(sorcery.register.residue.db[n])))
			else break end
		end
	end

	has_phial = has_phial()
	local spec = string.format([[
		formspec_version[3] size[10.25,8] real_coordinates[true]
		list[context;cache;%f,0.25;%u,1;]
		list[context;amulet;3.40,1.50;1,1;]
		list[context;active;5.90,1.50;1,1;]

		list[context;wrench;1.25,1.75;1,1;]
		list[context;phial;7.25,1.75;1,1;]
		list[context;refuse;8.50,1.75;1,1;]

		list[current_player;main;0.25,3;8,4;]

		image[0.25,0.50;1,1;sorcery_statlamp_%s.png]
	]], (10.5 - constants.rune_cache_max*1.25)/2, constants.rune_cache_max,
	    ((has_phial and pow_max) and 'green' ) or
		((has_phial and pow_min) and 'yellow') or 'off')

	local ghost = function(slot,x,y,img)
		if i:is_empty(slot) then spec = spec .. string.format([[
			image[%f,%f;1,1;%s.png]
		]], x,y,img) end
	end

	ghost('active',5.90,1.50,'sorcery_ui_ghost_rune')
	ghost('amulet',3.40,1.50,'sorcery_ui_ghost_amulet')
	ghost('wrench',1.25,1.75,'sorcery_ui_ghost_rune_wrench')
	ghost('phial',7.25,1.75,'vessels_shelf_slot')
	
	m:set_string('formspec',spec)

	if i:is_empty('phial') then return false end
	return true
end

local rfbox = {
	type = 'fixed';
	fixed = {
		-0.5, -0.5, -0.5;
................................................................................
		'default_copper_block.png';
	};
	_sorcery = {
		ley = {
			mode = 'consume';
			affinity = {'praxic'};
			power = function(pos,time)
				local i = minetest.get_meta(pos):get_inventory()
				if i:is_empty('phial') then return 0 end
				local phial = i:get_stack('phial',1)

				local max,min = 0
				for _,r in pairs(sorcery.data.runes) do
					if r.minpower > max then max = r.minpower end
					if min == nil or r.minpower < min then min = r.minpower end
				end
				-- high-quality phials reduce power usage
				local fac = select(2, calc_phial_props(phial))
				min = min * fac  max = max * fac
				return min*time,max*time
			end;
		};
		on_leychange = runeforge_update;
		recipe = {
			note = 'Periodically creates runes when sufficiently powered and can be used to imbue them into an amulet, giving it a powerful magical effect';
		};
	};
	on_construct = function(pos)
		local m = minetest.get_meta(pos)
		local i = m:get_inventory()
		i:set_size('cache',constants.rune_cache_max)
		i:set_size('wrench',1) i:set_size('phial',1) i:set_size('refuse',1)
		i:set_size('amulet',1) i:set_size('active',1)
		m:set_string('infotext','Rune Forge')
		runeforge_update(pos)

	end;
	after_dig_node = sorcery.lib.node.purge_only {'amulet'};
	on_timer = runeforge_update;
	on_metadata_inventory_move = function(pos, fl,fi, tl,ti, count, user)
		local inv = minetest.get_meta(pos):get_inventory()
		local wrench if not inv:is_empty('wrench') then
			wrench = inv:get_stack('wrench',1):get_definition()._proto
		end
		local wwear = function(cap)
			local s = inv:get_stack('wrench',1)
			local wear = 65535 / wrench.powers[cap]
			s:add_wear(wear)
			inv:set_stack('wrench',1,s)
		end
		if fl == 'active' then
			inv:set_stack('amulet',1,sorcery.amulet.setrune(inv:get_stack('amulet',1),nil,user))
			-- only special wrenches can extract runes intact
			if wrench.powers.extract then wwear('extract')
				minetest.sound_play('sorcery_chime', { pos = pos, gain = 0.5 })
			elseif wrench.powers.purge then wwear('purge')
				inv:set_stack(tl,ti,ItemStack(nil))
				minetest.sound_play('sorcery_disjoin', { pos = pos, gain = 0.5 })
			end
		elseif tl == 'active' and wrench.powers.imbue then
			local amulet = sorcery.amulet.setrune(inv:get_stack('amulet',1), inv:get_stack(tl,ti), user)
			local spell = sorcery.amulet.getspell(amulet)
			sorcery.vfx.enchantment_sparkle({
				under = pos;
				above = vector.add(pos,{x=0,y=1,z=0});
			}, spell.tone:brighten(1.2):hex())
			minetest.sound_play('xdecor_enchanting', { pos = pos, gain = 0.5 })
			inv:set_stack('amulet',1,amulet)
			wwear('imbue')
		end
		-- trigger the update early to clean up the ghost image :/
		-- minetest needs a cleaner way to handle these
		runeforge_update(pos)
	end;
	on_metadata_inventory_put = function(pos, list, idx, stack, user)

		local inv = minetest.get_meta(pos):get_inventory()
		if list == 'amulet' then
			inv:set_stack('active',1,ItemStack(sorcery.amulet.getrune(stack)))
		end
		runeforge_update(pos)
		if not inv:is_empty('phial') then
			minetest.get_node_timer(pos):start(calc_phial_props(inv:get_stack('phial',1)))
		end
	end;
	on_metadata_inventory_take = function(pos, list, idx, stack, user)
		if list == 'amulet' then
			minetest.get_meta(pos):get_inventory():set_stack('active',1,ItemStack())
		end
		runeforge_update(pos)
	end;
	allow_metadata_inventory_put = function(pos,list,idx,stack,user)
		if list == 'amulet' then
			if minetest.get_item_group(stack:get_name(), 'sorcery_amulet') ~= 0 then
				return 1
			end
		end
		if list == 'phial' then
			if minetest.get_item_group(stack:get_name(), 'sorcery_phial') ~= 0 then
				return stack:get_count()
			end
		end
		if list == 'wrench' then
			if minetest.get_item_group(stack:get_name(), 'sorcery_rune_wrench') ~= 0 then
				return 1
			end
		end
		return 0
	end;
	allow_metadata_inventory_take = function(pos,list,idx,stack,user)
		if list == 'amulet' or list == 'wrench' then return 1 end
		if list == 'phial' or list == 'refuse' then return stack:get_count() end
		return 0
	end;
	allow_metadata_inventory_move = function(pos, fl,fi, tl,ti, count, user)
		local inv = minetest.get_meta(pos):get_inventory()
		local wrench if not inv:is_empty('wrench') then
			wrench = inv:get_stack('wrench',1):get_definition()._proto
		end
		if fl == 'cache' then
			if tl == 'cache' then return 1 end
			if tl == 'active' then
				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
		return 0
	end;
})

do local m = sorcery.data.metals
	-- temporary recipe until a fancier multi-part crafting path can be come up with

Added sounds/sorcery_bloody_burst.ogg version [86beb5bbfe].

cannot compute difference between binary files

Added sounds/sorcery_bloody_hit.1.ogg version [4a47968931].

cannot compute difference between binary files

Added sounds/sorcery_bloody_hit.2.ogg version [6bea57cc69].

cannot compute difference between binary files

Added sounds/sorcery_bloody_hit.3.ogg version [3d0dd5fb33].

cannot compute difference between binary files

Added sounds/sorcery_chime.1.ogg version [f5f3fa1f0b].

cannot compute difference between binary files

Added sounds/sorcery_chime.2.ogg version [e2c798dcb3].

cannot compute difference between binary files

Added sounds/sorcery_coins.ogg version [24495c2ec0].

cannot compute difference between binary files

Added sounds/sorcery_crunch.1.ogg version [bd7af2ce27].

cannot compute difference between binary files

Added sounds/sorcery_crunch.2.ogg version [436bf86ffa].

cannot compute difference between binary files

Added sounds/sorcery_crunch.3.ogg version [b858a7a30d].

cannot compute difference between binary files

Added sounds/sorcery_crunch.4.ogg version [5a749d38d7].

cannot compute difference between binary files

Added sounds/sorcery_disjoin.1.ogg version [bafe4e15de].

cannot compute difference between binary files

Added sounds/sorcery_disjoin.2.ogg version [72809eab09].

cannot compute difference between binary files

Added textures/sorcery_rune_wrench.png version [c39260a038].

cannot compute difference between binary files

Added textures/sorcery_rune_wrench_iridium.png version [076b434e79].

cannot compute difference between binary files

Added textures/sorcery_ui_ghost_amulet.png version [1e1e56f3c1].

cannot compute difference between binary files

Added textures/sorcery_ui_ghost_rune.png version [8e47e791d3].

cannot compute difference between binary files

Added textures/sorcery_ui_ghost_rune_wrench.png version [1bd60d3986].

cannot compute difference between binary files

Modified tnodes.lua from [1f5f95dd4b] to [4064266f0d].

3
4
5
6
7
8
9


10
11
12
13
14
15
16
		drawtype = 'airlike';
		light_source = 5 + math.ceil(i * (11/minetest.LIGHT_MAX));
		sunlight_propagates = true;
		buildable_to = true;
		pointable = false;
		walkable = false;
		floodable = true;


		groups = { air = 1; sorcery_air = 1; not_in_creative_inventory = 1; };
		on_construct = function(pos)
			local meta = minetest.get_meta(pos)
			meta:set_float('duration',10)
			meta:set_float('timeleft',10)
			meta:set_int('power',minetest.LIGHT_MAX)
			minetest.get_node_timer(pos):start(1)







>
>







3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
		drawtype = 'airlike';
		light_source = 5 + math.ceil(i * (11/minetest.LIGHT_MAX));
		sunlight_propagates = true;
		buildable_to = true;
		pointable = false;
		walkable = false;
		floodable = true;
		drop = {max_items = 0, items = {}};
		on_blast = function() end; -- not affected by explosions
		groups = { air = 1; sorcery_air = 1; not_in_creative_inventory = 1; };
		on_construct = function(pos)
			local meta = minetest.get_meta(pos)
			meta:set_float('duration',10)
			meta:set_float('timeleft',10)
			meta:set_int('power',minetest.LIGHT_MAX)
			minetest.get_node_timer(pos):start(1)

Modified vfx.lua from [291a2c52f8] to [343a5ccf55].

1
2
3




4
5
6
7
8
9
10
11
12
13
14
15
16
17
..
19
20
21
22
23
24
25





























26
27
28
29
30
31
32
sorcery.vfx = {}

sorcery.vfx.cast_sparkle = function(caster,color,strength,duration)




	minetest.add_particlespawner {
		amount = 70 * strength;
		time = duration or 1.5;
		attached = caster;
		texture = sorcery.lib.image('sorcery_spark.png'):multiply(color):render();
		minpos = { x = -0.1, z =  0.5, y =  1.2}; 
		maxpos = { x =  0.1, z =  0.3, y =  1.6}; 
		minvel = { x = -0.5, z = -0.5, y = -0.5};
		maxvel = { x =  0.5, z =  0.5, y =  0.5};
		minacc = { x =  0.0, z =  0.0, y =  0.5};
		maxacc = { x =  0.0, z =  0.0, y =  0.5};
		minsize = 0.4, maxsize = 0.8;
		minexptime = 1, maxexptime = 1;
		glow = 14;
................................................................................
			type = 'vertical_frames';
			aspect_w = 16;
			aspect_h = 16;
			length = 1.1;
		};
	}
end






























sorcery.vfx.enchantment_sparkle = function(tgt,color)
	local minvel, maxvel
	if minetest.get_node(vector.add(tgt.under,{y=1,z=0,x=0})).name == 'air' then
		minvel = {x=0,z=0,y= 0.3}  maxvel = {x=0,z=0,y= 1.5};
	else
		local dir = vector.subtract(tgt.under,tgt.above)


|
>
>
>
>





|
|







 







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







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
..
23
24
25
26
27
28
29
30
31
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
sorcery.vfx = {}

sorcery.vfx.cast_sparkle = function(caster,color,strength,duration,pos)
	local ofs = pos
		and function(x) return vector.add(pos,x) end
		or  function(x) return x end
	local height = caster:get_properties().eye_height
	minetest.add_particlespawner {
		amount = 70 * strength;
		time = duration or 1.5;
		attached = caster;
		texture = sorcery.lib.image('sorcery_spark.png'):multiply(color):render();
		minpos = ofs({ x =  0.0, z =  0.6, y =  height*0.7});
		maxpos = ofs({ x =  0.4, z =  0.2, y =  height*1.1});
		minvel = { x = -0.5, z = -0.5, y = -0.5};
		maxvel = { x =  0.5, z =  0.5, y =  0.5};
		minacc = { x =  0.0, z =  0.0, y =  0.5};
		maxacc = { x =  0.0, z =  0.0, y =  0.5};
		minsize = 0.4, maxsize = 0.8;
		minexptime = 1, maxexptime = 1;
		glow = 14;
................................................................................
			type = 'vertical_frames';
			aspect_w = 16;
			aspect_h = 16;
			length = 1.1;
		};
	}
end

sorcery.vfx.body_sparkle = function(body,color,str,pos)
	local img = sorcery.lib.image
	local tex = img('sorcery_spark.png')
	local pi = tex:blit(tex:multiply(color)):render()
	local ofs = pos
		and function(x) return vector.add(pos,x) end
		or  function(x) return x end
	return minetest.add_particlespawner {
		amount = 25 * str;
		time = 0.5;
		attached = body;
		minpos = ofs{x = -0.5, y = -0.5, z = -0.5};
		maxpos = ofs{x =  0.5, y =  1.5, z =  0.5};
		minacc = {x = -0.3, y =  0.0, z =  0.3};
		maxacc = {x = -0.3, y =  0.0, z =  0.3};
		minvel = {x = -0.6, y = -0.2, z =  0.6};
		maxvel = {x = -0.6, y =  0.2, z =  0.6};
		minexptime = 1.0;
		maxexptime = 1.5;
		texture = pi;
		glow = 14;
		animation = {
			type = 'vertical_frames';
			aspect_w = 16, aspect_h = 16;
			length = 1.6;
		};
	}
end

sorcery.vfx.enchantment_sparkle = function(tgt,color)
	local minvel, maxvel
	if minetest.get_node(vector.add(tgt.under,{y=1,z=0,x=0})).name == 'air' then
		minvel = {x=0,z=0,y= 0.3}  maxvel = {x=0,z=0,y= 1.5};
	else
		local dir = vector.subtract(tgt.under,tgt.above)

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

91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
				-- but power levels are unpredictable
				tone = u.color(255,117,40);
				tex = u.image('default_copper_block.png');
				wandprops = { flux = 0.7, chargetime = 0.5 };
			};
			silver = {
				tone = u.color(215,238,241);
				tex = u.image('default_gold_block'):colorize(u.color(255,238,241), 255);
				wandprops = {};
			};
			steel = {
				tone = u.color(255,255,255);
				tex = u.image('default_steel_block');
				wandprops = {};
			};
		};
		gem = sorcery.data.gems;
	};
	util = {
		baseid = function(wand)







|




|







91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
				-- but power levels are unpredictable
				tone = u.color(255,117,40);
				tex = u.image('default_copper_block.png');
				wandprops = { flux = 0.7, chargetime = 0.5 };
			};
			silver = {
				tone = u.color(215,238,241);
				tex = u.image('default_gold_block.png'):colorize(u.color(255,238,241), 255);
				wandprops = {};
			};
			steel = {
				tone = u.color(255,255,255);
				tex = u.image('default_steel_block.png');
				wandprops = {};
			};
		};
		gem = sorcery.data.gems;
	};
	util = {
		baseid = function(wand)