sorcery  Diff

Differences From Artifact [b4ccf2cc5a]:

  • File runeforge.lua — part of check-in [96c5289a2a] at 2020-10-21 03:35:35 on branch trunk — 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 (user: lexi, size: 8121) [annotate] [blame] [check-ins using]

To Artifact [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