sorcery  Check-in [93f944b581]

Overview
Comment:add displacers; add item class mechanism; various tweaks, enhancements, and bugfixes esp. for books and paper
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: 93f944b581af9d3a2f8c5ef7d0a27fa6d4a99247fc85eecc94dcf49100b42fc0
User & Date: lexi on 2020-08-30 14:45:03
Other Links: manifest | tags
Context
2020-08-30
14:46
rename improperly namespaced files check-in: 76581a64d9 user: lexi tags: trunk
14:45
add displacers; add item class mechanism; various tweaks, enhancements, and bugfixes esp. for books and paper check-in: 93f944b581 user: lexi tags: trunk
2020-08-28
14:08
add recipes, cookbooks, disassembly (to create recipes from items), attunement, farcasters, and portals; various edits for bug fixes and improvements check-in: 9ef6cbcf31 user: lexi tags: trunk
Changes

Modified compat.lua from [e7cd5fcdf6] to [b94ab7bfcd].

36
37
38
39
40
41
42
43
44
	})
	minetest.register_alias('new_campfire:ash', 'sorcery:ash')
end

return {
	defp = function(name)
		return minetest.registered_items[name] or minetest.registered_aliases[name]
	end
}







|

36
37
38
39
40
41
42
43
44
	})
	minetest.register_alias('new_campfire:ash', 'sorcery:ash')
end

return {
	defp = function(name)
		return minetest.registered_items[name] or minetest.registered_aliases[name]
	end;
}

Modified cookbook.lua from [e0a46e6a8e] to [b21ad56f71].

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
..
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
..
77
78
79
80
81
82
83

















84






85
86
87
88
89
90
91
92
...
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
...
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
...
459
460
461
462
463
464
465

466
467
468
469
470


471
472
473

474











475
476
477
478
479
480
481
...
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509

sorcery.cookbook = {}
local constants = {
	-- do not show recipes for items in these groups
	exclude_groups = {
	};
	exclude_names = {
		'_stairs';
		'_slab';
		'slope_';
	};
	-- do not show recipes from this namespace
	blacklist_mods = {
		'group'; -- WHY IS THIS NECESSARY
		'moreblocks'; -- too much noise
	};

	recipes_per_cookbook_page = 3;














}

local slot3x3 = {
	{0,0}, {1,0}, {2,0};
	{0,1}, {1,1}, {2,1};
	{0,2}, {1,2}, {2,2};
}
................................................................................
	-- ow ow ow ow ow ow ow
	local names = {}
	for k in pairs(minetest.registered_items) do
		local rec = minetest.get_craft_recipe(k)
		if rec.items ~= nil and (rec.method == kind or (rec.method == 'shapeless' and kind == 'normal')) then -- is this last bit necessary?
			local excluded = false
			for _,n in pairs(constants.exclude_names) do
				if string.find(p,n) then
					excluded = true break end
			end
			if not excluded then for _,g in pairs(constants.exclude_groups) do
				if minetest.get_item_group(p, g) > 0 then
					excluded = true break end
			end end
			local props = minetest.registered_items[k]._sorcery
			local module = modofname(k)
			if not (excluded
				or sorcery.lib.tbl.has(constants.blacklist_mods,module)
				or (props and props.recipe and props.recipe.secret)
................................................................................
		local col = (j-1) % w
		if i.items[j] then
			rec[1 + (row * 3) + col] = i.items[j]
		end
	end
	return rec
end

















local desc_builtin = function(i)






	local desc = minetest.registered_items[i].description
	if not desc then return 'Peculiar Item' end

	local eol = string.find(desc,'\n')
	if not eol then return desc else return string.sub(desc,1,eol-1) end
end;

local bookadjs = { -- sets are in reverse order!
................................................................................
		local x, y = k.slots[i][1], k.slots[i][2]
		if ingredients[i] and ingredients[i] ~= '' then
			local tt
			if k.indesc then tt = k.indesc(ingredients[i]) else tt = desc_builtin(ingredients[i]) end
			t = t .. string.format([[
				item_image[%f,%f;1,1;%s]
				tooltip[%f,%f;1,1;%s]
			]], x,y, minetest.formspec_escape(ingredients[i]),
			    x,y, minetest.formspec_escape(tt))
		else
			if k.drawslots == nil or k.drawslots then
				t = string.format('box[%f,%f;0.1,0.1;#00000060]',x+0.45,y+0.45) .. t
			end
		end
	end
................................................................................

dungeon_loot.register {
	name = 'sorcery:recipe';
	chance = 0.9;
	count = {1,7};
}

minetest.register_craft { type = 'fuel', recipe = 'sorcery:recipe', burntime = 3 }
minetest.register_craft {
	type = 'cooking';
	recipe = 'sorcery:recipe';
	output = 'sorcery:ash';
	cooktime = 3;
}

default.register_craft_metadata_copy('default:paper','sorcery:recipe')


-- default.register_craft_metadata_copy('default:book','sorcery:cookbook')

for i=1,8 do
	local rcp = {}
	for i=1,i do rcp[i] = 'sorcery:recipe' end
	rcp[#rcp+1]='default:book' minetest.register_craft {
		type = 'shapeless', recipe = rcp, output = 'sorcery:cookbook';
	}
	rcp[#rcp]='sorcery:cookbook' minetest.register_craft {
		type = 'shapeless', recipe = rcp, output = 'sorcery:cookbook';
	}
end










local m = sorcery.lib.marshal
local encbook, decbook = m.transcoder {
	pages = m.g.array(8, m.g.struct {
		kind = m.t.str;
		name = m.t.str;
	})
................................................................................
	end

	uinv:set_stack('main',idx,stack)
	bookform(stack,user)
end)

minetest.register_on_craft(function(stack,player,grid,inv)

	if stack:get_name() ~= 'sorcery:cookbook' then return nil end

	local oldbook
	local topic, onetopic = nil, true
	local recipes = {}


	for _,s in pairs(grid) do
		if s:get_name() == 'sorcery:recipe' then
			recipes[#recipes+1] = s

		elseif s:get_name() == 'sorcery:cookbook' then oldbook = s end











	end

	oldbook = oldbook or stack
	local bookmeta = oldbook:get_meta()
	local newbook = not bookmeta:contains('cookbook')
	local book = bookprops(oldbook)

................................................................................

	if topic and newbook then
		if not onetopic then topic = nil end
		bookmeta:set_string('description',namebook(topic,player:get_player_name()))
		bookmeta:set_string('owner',player:get_player_name())
	end

	print('new book',bookmeta:get_string('description'))
	print('new book',dump(book))
	bookmeta:set_string('cookbook', sorcery.lib.str.meta_armor(encbook(book),true))
	return oldbook
end)

if minetest.get_modpath('books') then
	-- make our own placeable cookbook somehow
end







|
|
|








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







 







|



|







 







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







 







|







 







<
<
<
<
<
<
<
<

>
>













>
>
>
>
>
>
>
>







 







>





>
>
|


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







 







<
<







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
42
43
..
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
..
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
...
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
...
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
...
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
...
548
549
550
551
552
553
554


555
556
557
558
559
560
561

sorcery.cookbook = {}
local constants = {
	-- do not show recipes for items in these groups
	exclude_groups = {
	};
	exclude_names = {
		'stairs';
		'slab';
		'slope';
	};
	-- do not show recipes from this namespace
	blacklist_mods = {
		'group'; -- WHY IS THIS NECESSARY
		'moreblocks'; -- too much noise
	};

	recipes_per_cookbook_page = 3;

	group_ids = {
		wood   = { caption = 'Any Wood',   cnitem = 'default:wood'   };
		tree   = { caption = 'Any Tree',   cnitem = 'default:tree'   };
		leaves = { caption = 'Any Leaves', cnitem = 'default:leaves' };
		stone  = { caption = 'Any Stone',  cnitem = 'default:stone'  };
		dye    = { caption = 'Any Dye',    cnitem = 'dye:black'      };
		bone   = { caption = 'Any Bone',   cnitem = 'bonemeal:bone'  };
		vessel = { caption = 'Any Bottle', cnitem = 'vessels:glass_bottle' };
		flower = { caption = 'Any Flower', cnitem = 'flowers:rose' };
		mushroom = { caption = 'Any Mushroom', cnitem = 'flowers:mushroom_brown' };
		water_bucket = { caption = 'Water Bucket', cnitem = 'bucket:bucket_water' };
		sorcery_ley_cable = { caption = 'Cable', cnitem = 'sorcery:cable_vidrium' };
	};
}

local slot3x3 = {
	{0,0}, {1,0}, {2,0};
	{0,1}, {1,1}, {2,1};
	{0,2}, {1,2}, {2,2};
}
................................................................................
	-- ow ow ow ow ow ow ow
	local names = {}
	for k in pairs(minetest.registered_items) do
		local rec = minetest.get_craft_recipe(k)
		if rec.items ~= nil and (rec.method == kind or (rec.method == 'shapeless' and kind == 'normal')) then -- is this last bit necessary?
			local excluded = false
			for _,n in pairs(constants.exclude_names) do
				if string.find(k,n) ~= nil then
					excluded = true break end
			end
			if not excluded then for _,g in pairs(constants.exclude_groups) do
				if minetest.get_item_group(k, g) > 0 then
					excluded = true break end
			end end
			local props = minetest.registered_items[k]._sorcery
			local module = modofname(k)
			if not (excluded
				or sorcery.lib.tbl.has(constants.blacklist_mods,module)
				or (props and props.recipe and props.recipe.secret)
................................................................................
		local col = (j-1) % w
		if i.items[j] then
			rec[1 + (row * 3) + col] = i.items[j]
		end
	end
	return rec
end
local function group_eval(i)
	if string.sub(i,1,6) == 'group:' then
		local g = string.sub(i,7)
		if constants.group_ids[g] then
			return constants.group_ids[g].cnitem,
			       constants.group_ids[g].caption
		end
		for i,v in pairs(minetest.registered_items) do
			if minetest.get_item_group(i, g) > 0 then
				return i, v.description
			end
		end
		return i
	end
	return i
end
local function desc_builtin(i)
	local desc
	i, desc = group_eval(i)
	-- print('describing ',i,dump(minetest.registered_items[i]))
	if not minetest.registered_items[i] then
		minetest.log('WARNING: unknown item in recipe ' .. i)
		return 'Unknown Item'
	end
	if not desc then desc = minetest.registered_items[i].description end
	if not desc then return 'Peculiar Item' end

	local eol = string.find(desc,'\n')
	if not eol then return desc else return string.sub(desc,1,eol-1) end
end;

local bookadjs = { -- sets are in reverse order!
................................................................................
		local x, y = k.slots[i][1], k.slots[i][2]
		if ingredients[i] and ingredients[i] ~= '' then
			local tt
			if k.indesc then tt = k.indesc(ingredients[i]) else tt = desc_builtin(ingredients[i]) end
			t = t .. string.format([[
				item_image[%f,%f;1,1;%s]
				tooltip[%f,%f;1,1;%s]
			]], x,y, minetest.formspec_escape(group_eval(ingredients[i])),
			    x,y, minetest.formspec_escape(tt))
		else
			if k.drawslots == nil or k.drawslots then
				t = string.format('box[%f,%f;0.1,0.1;#00000060]',x+0.45,y+0.45) .. t
			end
		end
	end
................................................................................

dungeon_loot.register {
	name = 'sorcery:recipe';
	chance = 0.9;
	count = {1,7};
}









default.register_craft_metadata_copy('default:paper','sorcery:recipe')
-- this seems bugged; it doesn't like it when its item shows up in another
-- recipe. so we'll do it manually :/
-- default.register_craft_metadata_copy('default:book','sorcery:cookbook')

for i=1,8 do
	local rcp = {}
	for i=1,i do rcp[i] = 'sorcery:recipe' end
	rcp[#rcp+1]='default:book' minetest.register_craft {
		type = 'shapeless', recipe = rcp, output = 'sorcery:cookbook';
	}
	rcp[#rcp]='sorcery:cookbook' minetest.register_craft {
		type = 'shapeless', recipe = rcp, output = 'sorcery:cookbook';
	}
end

minetest.register_craft {
	type = 'shapeless';
	recipe = {
		'sorcery:cookbook';
		'default:book';
	};
	output = 'sorcery:cookbook';
};

local m = sorcery.lib.marshal
local encbook, decbook = m.transcoder {
	pages = m.g.array(8, m.g.struct {
		kind = m.t.str;
		name = m.t.str;
	})
................................................................................
	end

	uinv:set_stack('main',idx,stack)
	bookform(stack,user)
end)

minetest.register_on_craft(function(stack,player,grid,inv)
	-- god this is messy. i'm sorry. minetest made me do it
	if stack:get_name() ~= 'sorcery:cookbook' then return nil end

	local oldbook
	local topic, onetopic = nil, true
	local recipes = {}
	local copybook = false
	local obindex
	for i,s in pairs(grid) do
		if s:get_name() == 'sorcery:recipe' then
			recipes[#recipes+1] = s
		elseif s:get_name() == 'default:book' then copybook = true
		elseif s:get_name() == 'sorcery:cookbook' then oldbook = s obindex = i end
	end

	if #recipes == 0 and copybook and oldbook then
		inv:set_stack('craft',obindex,oldbook)
		local newmeta = stack:get_meta()
		local copy = function(field)
			newmeta:set_string(field,oldbook:get_meta():get_string(field))
		end
		copy('cookbook') copy('description')
		newmeta:set_string('owner',player:get_player_name())
		return stack
	end

	oldbook = oldbook or stack
	local bookmeta = oldbook:get_meta()
	local newbook = not bookmeta:contains('cookbook')
	local book = bookprops(oldbook)

................................................................................

	if topic and newbook then
		if not onetopic then topic = nil end
		bookmeta:set_string('description',namebook(topic,player:get_player_name()))
		bookmeta:set_string('owner',player:get_player_name())
	end



	bookmeta:set_string('cookbook', sorcery.lib.str.meta_armor(encbook(book),true))
	return oldbook
end)

if minetest.get_modpath('books') then
	-- make our own placeable cookbook somehow
end

Modified data/compat.lua from [04ce335701] to [d448ba2bd2].









1
2
3
4
5
6
7
..
18
19
20
21
22
23
24
25




26


















local grain = {
	hardness = 1;
	value = 1;
	powder = 'farming:flour';
	grindcost = 4;
}
return {
................................................................................
		}
	};
	ley = {
		['default:mese'] = {
			power = 0.25;
			mode = 'produce';
		};
	};




}










>
>
>
>
>
>
>
>







 








>
>
>
>
|
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
..
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
-- compatibility tables
-- this file is used to hold information that would normally
-- be tagged in the _sorcery or _proto fields of an item's
-- definition, but cannot be placed there because the item
-- is outside the control of the author and its module does
-- not cooperate with sorcery. it is used by itemclass.lua
-- to seamlessly locate the material properties and
-- capabilities of an item.
local grain = {
	hardness = 1;
	value = 1;
	powder = 'farming:flour';
	grindcost = 4;
}
return {
................................................................................
		}
	};
	ley = {
		['default:mese'] = {
			power = 0.25;
			mode = 'produce';
		};
	};
	gems = {
		['default:mese_crystal'] = {
			id = 'mese', gem = true;
			value = 9, raw = true;
		};
		['default:mese_crystal_fragment'] = {
			id = 'mese', gem = true;
			value = 1, raw = true;
		};
		['default:diamond'] = {
			id = 'diamond', gem = true;
			value = 9, raw = true;
		};
	};
}

Modified data/spells.lua from [26201c3a3e] to [9b1dfc7187].

281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
...
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
	};
	dowse = {
		name = 'dowsing';
		leytype = 'cognic';
		color = {65,116,255};
		affinity = {'acacia','dark','silent'};
		uses = 176;
		desc = 'Send up sparks of radia to indicate nearness or absence of attuned blocks';
	};
	verdant = {
		name = 'verdant';
		color = {16,29,255};
		uses = 48;
		leytype = 'imperic';
		desc = 'Pour life-energy into the soil, causing flowers and trees to spring up at your command';
................................................................................
			if rec then
				local data = decpos(sorcery.lib.str.meta_dearmor(rec,true))
				local srcpos = {x=data.x,y=data.y,z=data.z}
				local srcnode = minetest.get_node(srcpos)
				local srcdef = minetest.registered_nodes[srcnode.name]
				if srcdef and srcdef._sorcery and srcdef._sorcery.attune then
					if sorcery.attunement.nodeid(srcpos) == data.id then
						-- check for ink
						src = { 
							pos = srcpos;
							props = srcdef._sorcery.attune;
						}
					end
				end
			end







|







 







<







281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
...
339
340
341
342
343
344
345

346
347
348
349
350
351
352
	};
	dowse = {
		name = 'dowsing';
		leytype = 'cognic';
		color = {65,116,255};
		affinity = {'acacia','dark','silent'};
		uses = 176;
		desc = 'Send up sparks of radia to indicate nearness or absence of the blocks whose presence the wand is attuned to';
	};
	verdant = {
		name = 'verdant';
		color = {16,29,255};
		uses = 48;
		leytype = 'imperic';
		desc = 'Pour life-energy into the soil, causing flowers and trees to spring up at your command';
................................................................................
			if rec then
				local data = decpos(sorcery.lib.str.meta_dearmor(rec,true))
				local srcpos = {x=data.x,y=data.y,z=data.z}
				local srcnode = minetest.get_node(srcpos)
				local srcdef = minetest.registered_nodes[srcnode.name]
				if srcdef and srcdef._sorcery and srcdef._sorcery.attune then
					if sorcery.attunement.nodeid(srcpos) == data.id then

						src = { 
							pos = srcpos;
							props = srcdef._sorcery.attune;
						}
					end
				end
			end

Added displacer.lua version [54869bc440].























































































































































































































































































































































































































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
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
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
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
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
local constants = {
	xmit_wattage = 0.4;
	-- the amount of power per second needed to transmit an item from
	-- one displacer to another

	rcpt_wattage = 0.15;
	-- the amount of power needed to broadcast a receptor's availability
}

local gettxr = function(pos)
	local txrcomps = {
		'sorcery:displacer';
		'sorcery:displacer_transmit_gem';
		'sorcery:displacer_transmit_attune';
		'sorcery:displacer_receive_gem';
		'sorcery:displacer_receive_attune';
	}

	local devs = sorcery.lib.node.amass(pos,txrcomps,sorcery.lib.node.offsets.neighbors)
	local r = {
		receptacles = {};
		connections = {};
		counts = {
			receptacles = 0;
			transmitters = 0;
			receptors = 0;
		};
	}
	local getcode = function(pos)
		local inv = minetest.get_meta(pos):get_inventory()
		local code = {}
		local empty = true
		for i=1,inv:get_size('code') do
			if not inv:get_stack('code',i):is_empty()
				then empty = false end

			code[i] = inv:get_stack('code',i):get_name()
		end
		if empty then return nil
		         else return code end
	end
	for pos, dev in pairs(devs) do
		if dev == 'sorcery:displacer_receive_gem' then
			r.counts.receptors = r.counts.receptors + 1
			r.connections[#r.connections+1] = {
				pos = pos; mode = 'receive';
				code = getcode(pos); -- TODO retrieve code
			}
		elseif dev == 'sorcery:displacer_receive_attune' then
			local tune = sorcery.attunement.verify(pos)
			if tune then
				r.counts.receptors = r.counts.receptors + 1
				r.connections[#r.connections+1] = {
					pos = pos; mode = 'receive';
					partner = tune.partner;
				}
			end
		elseif dev == 'sorcery:displacer_transmit_gem' then
			r.counts.transmitters = r.counts.transmitters + 1
			r.connections[#r.connections+1] = {
				pos = pos; mode = 'transmit';
				code = getcode(pos); -- TODO retrieve code
			}
		elseif dev == 'sorcery:displacer_transmit_attune' then
			local tune = sorcery.attunement.verify(pos)
			if tune then
				r.counts.transmitters = r.counts.transmitters + 1
				r.connections[#r.connections+1] = {
					pos = pos; mode = 'transmit';
					partner = tune.partner;
				}
			end
		elseif dev == 'sorcery:displacer' then
			r.counts.receptacles = r.counts.receptacles + 1
			r.receptacles[#r.receptacles+1] = pos
		end
	end
	return r
end

local autoselect = function(pos)
	local dev = gettxr(pos)
	local active
	if dev.counts.receptors == 0 and dev.counts.transmitters == 1 then
		active = dev.connections[1].pos
	end
	for _,rcp in pairs(dev.receptacles) do
		local meta = minetest.get_meta(rcp)
		meta:set_string('active-device',active and minetest.pos_to_string(active) or '')
	end
	return active ~= nil
end

minetest.register_node('sorcery:displacer', {
	description = 'Displacer Receptacle';
	paramtype2 = 'facedir';
	on_construct = function(pos)
		local meta = minetest.get_meta(pos)
		local inv = meta:get_inventory()
		minetest.get_node_timer(pos):start(1)
		inv:set_size('cache', 6)
		meta:set_string('infotext','displacer')
		meta:set_string('active-device','')
		meta:set_string('formspec', [[
			formspec_version[3] size[10.25,8]
			list[context;cache;3.125,0.25;3,2]
			list[current_player;main;0.25,3;8,4]
			listring[]
		]])
	end;

	-- vararg wrapping necessary to discard the return value,
	-- as a return value of true will prevent the item from
	-- being removed from the user's inventory after placement
	after_place_node = function(...) autoselect(...) end;
	after_dig_node = function(...)
		autoselect(...)
		sorcery.lib.node.purge_container(...)
	end;
	on_metadata_inventory_put = function(pos)
		minetest.get_node_timer(pos):start(1)
	end;
	on_timer = function(pos,delta)
		local meta = minetest.get_meta(pos)
		if not meta:contains('active-device') then return false end

		local inv = meta:get_inventory()
		if inv:is_empty('cache') then return false end

		local dev = gettxr(pos)
		local active = minetest.string_to_pos(meta:get_string('active-device'))

		local ad
		for _,d in pairs(dev.connections) do
			if vector.equals(d.pos, active) then ad = d break end
		end
		if not ad then
			meta:set_string('active-device','')
			return false
		end

		local remote
		if ad.partner then
			remote = gettxr(ad.partner)
		elseif ad.code then
			local net = sorcery.farcaster.junction(pos,constants.xmit_wattage)
			for _,n in pairs(net) do
				for _,d in pairs(n.caps.net.devices.consume) do
					if d.id == 'sorcery:displacer' then
						local t = gettxr(d.pos)
						for _,d in pairs(t.connections) do
							if d.mode == 'receive' and d.code then
								local match = true
								for i=1,#d.code do
									if d.code[i] ~= ad.code[i] then
										match = false break
									end
								end
								if match then
									remote = t
									break
								end
							end
						end
					end
					if remote then break end
				end
				if remote then break end
			end
		end

		if not remote then return false end


		local n = sorcery.ley.netcaps(pos,delta,nil,constants.xmit_wattage)
		if n.self.powerdraw == n.self.maxpower then
			-- fully powered for transmission; find an object to transmit
			local transmission
			for i=1,inv:get_size('cache') do
				local s = inv:get_stack('cache',i)
				if not (s:is_empty() or minetest.get_item_group(s:get_name(), 'sorcery_nontranslocatable') ~= 0) then
					local quantity = 1
					local tq = minetest.get_item_group(s:get_name(), 'sorcery_translocate_pack')
					if tq ~= 0 then quantity = math.min(tq, s:get_count()) end
					transmission = s:take_item(quantity)
					inv:set_stack('cache',i,s)
					break
				end
			end
			if not transmission then return false end
			-- iterate through available receptacles and see if there's room
			-- in any of them. otherwise, fail
			for _,r in pairs(remote.receptacles) do
				local i = minetest.get_meta(r):get_inventory()
				transmission = i:add_item('cache',transmission)
				if transmission:is_empty() then break end
			end
			if not transmission:is_empty() then inv:add_item('cache',transmission) end
			return true
		elseif n.maxpower >= n.self.maxpower then
			-- other devices are currently drawing power and might stop,
			-- making enough available for us; keep iterating just in case
			return true
		else
			-- the system does not have the capability to generate
			-- sufficient power, no point in continuing to fuck around
			return false
		end
	end;
	groups = {
		cracky = 2;
		sorcery_ley_device = 1;
		sorcery_magitech = 1;
	};
	tiles = {
		'sorcery_displacer_top.png';
		'sorcery_displacer_top.png';
		'sorcery_displacer_side.png';
		'sorcery_displacer_side.png';
		'sorcery_displacer_side.png';
		'sorcery_displacer_front.png';
	};

	_sorcery = {
		on_leychange = function(pos)
			minetest.get_node_timer(pos):start(1)
		end;
		ley = {
			mode = 'consume', affinity = {'mandatic'};
			power = function(pos,time)
				local meta = minetest.get_meta(pos)
				local power = 0
				if meta:contains('active-device') then
					power = constants.xmit_wattage
				end

				local dev = gettxr(pos)
				power = power + constants.rcpt_wattage * dev.counts.receptors

				return (power / dev.counts.receptacles) * time
			end;
		};
	};
})

for mode,m in pairs {
	gem={
		desc = 'Gem-Coded';
		construct = function(pos)
			local meta = minetest.get_meta(pos)
			local inv = meta:get_inventory()
			inv:set_size('code',6)
			meta:set_string('formspec', [[
				formspec_version[3] size[10.25,7]
				list[context;code;1.5,0.25;6,1]
				list[current_player;main;0.25,1.75;8,4]
				listring[]
			]])
		end;
		allowput = function(pos,list,idx,stack)
			if list == 'code' then
				if sorcery.itemclass.get(stack:get_name(),'gem') then return 1 end
			end
			return 0
		end;
	};
	attune={
		desc = 'Attuned';
	};
} do for kind,n in pairs {
		transmit = {
			name = 'Transmission';
			button = function(pos)
				minetest.sound_play('doors_steel_door_open', {pos = pos})
				local n = minetest.get_node(pos)
				local dev = gettxr(pos)
				if dev.counts.receptacles > 0 then
					for _,r in pairs(dev.receptacles) do
						local m = minetest.get_meta(r)
						m:set_string('active-device',minetest.pos_to_string(pos))
						minetest.get_node_timer(r):start(1)
					end
				end
			end;
			attune = {
				target = true, accepts = 'sorcery:displacer';
				reciprocal = true;
			};
		};
		receive = {
			name = 'Reception';
			attune = {
				source = true, class = 'sorcery:displacer';
				reciprocal = true;
			}
		};
	} do local id = 'sorcery:displacer_' .. kind .. '_' .. mode
		minetest.register_node(id, {
			description = m.desc .. ' ' .. n.name .. ' Module';
			paramtype2 = 'facedir';
			tiles = {
				'sorcery_displacer_top.png';
				'sorcery_displacer_top.png';
				'sorcery_displacer_side.png';
				'sorcery_displacer_side.png';
				'sorcery_displacer_side.png';
				'sorcery_displacer_module_' .. kind .. '.png';
			};
			on_construct = m.construct;
			on_rightclick = mode ~= 'gem' and n.button or nil;
			on_punch = n.button and function(pos,node,puncher)
				if puncher and puncher:get_wielded_item():is_empty() then
					n.button(pos)
				end
			end or nil;
			after_place_node = function(...) autoselect(...) end;
			allow_metadata_inventory_put = m.allowput;
			_sorcery = {
				attune = (mode == 'attune') and n.attune or nil;
			};
			after_dig_node = function(...)
				autoselect(...)
				sorcery.lib.node.purge_container(...)
			end;
			groups = {
				cracky = 2;
				sorcery_magitech = 1;
			};
		})
	end
end

Modified farcaster.lua from [12914a5106] to [6f8ef41220].

22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
		attune = {
			class = 'sorcery:raycaster', accepts = 'sorcery:raycaster';
			source = true, target = true, reciprocal = true;
		};
		farcaster = {
			partner = function(pos)
				local tune = sorcery.attunement.verify(pos)
				print('   *!* verifying farcaster tuning',tune)
				if not tune then return nil end
				minetest.load_area(tune.partner)
				local vis = false
				local ignored
				repeat
					ignored = false
					for _,p in pairs(sorcery.lib.node.offsets.neighbors) do







<







22
23
24
25
26
27
28

29
30
31
32
33
34
35
		attune = {
			class = 'sorcery:raycaster', accepts = 'sorcery:raycaster';
			source = true, target = true, reciprocal = true;
		};
		farcaster = {
			partner = function(pos)
				local tune = sorcery.attunement.verify(pos)

				if not tune then return nil end
				minetest.load_area(tune.partner)
				local vis = false
				local ignored
				repeat
					ignored = false
					for _,p in pairs(sorcery.lib.node.offsets.neighbors) do

Modified gems.lua from [2f2f7ff693] to [533039aa40].

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
...
125
126
127
128
129
130
131
132
133





134
135
136
137
138
139
140

	if gem.foreign_shard then
		minetest.clear_craft {output=shardname}
	else
		minetest.register_craftitem(shardname, {
			description = sorcery.lib.str.capitalize(name) .. ' shard';
			inventory_image = 'sorcery_gem_' .. name .. '_shard.png';
			groups = { sorcery_shard = 1; };
			_proto = gem;






		})
	end
	if not gem.foreign_amulet then
		minetest.register_craftitem(amuletname, {
			description = sorcery.lib.str.capitalize(name) .. ' amulet';
			inventory_image = sorcery.lib.image('sorcery_amulet.png'):multiply(sorcery.lib.color(gem.tone)):render();
			_proto = {
				id = name;
				data = gem;


			};
		}) 
	end
	minetest.register_craft {
		type = 'shapeless';
		recipe = (minetest.get_modpath('xdecor') and {
			'xdecor:hammer', itemname;
................................................................................
		}
	end

	if gem.foreign then return false end
	minetest.register_craftitem(itemname, {
		description = sorcery.lib.str.capitalize(name);
		inventory_image = 'sorcery_gem_' .. name .. '.png';
		groups = { sorcery_gem = 1; };
		_proto = gem;





	})
	local tools = gem.tools
	if tools == nil then tools = {
		'group:pickaxe';
		'group:pick'; -- FUCK YOU INSTANT_ORES
		'~default:enchanted_pick_'; -- FUCK YOU XDECOR
	} end







|
|
>
>
>
>
>
>






|
|
|
>
>







 







|
|
>
>
>
>
>







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
...
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153

	if gem.foreign_shard then
		minetest.clear_craft {output=shardname}
	else
		minetest.register_craftitem(shardname, {
			description = sorcery.lib.str.capitalize(name) .. ' shard';
			inventory_image = 'sorcery_gem_' .. name .. '_shard.png';
			groups = { gemshard = 1; crystalshard = 1; sorcery_shard = 1; };
			_sorcery = {
				material = {
					gem = true;
					id = name, data = gem;
					raw = true, value = 1;
				};
			};
		})
	end
	if not gem.foreign_amulet then
		minetest.register_craftitem(amuletname, {
			description = sorcery.lib.str.capitalize(name) .. ' amulet';
			inventory_image = sorcery.lib.image('sorcery_amulet.png'):multiply(sorcery.lib.color(gem.tone)):render();
			_sorcery = {
				material = {
					gem = true, id = name, data = gem;
					value = (5 * shards_per_gem) + 4;
				};
			};
		}) 
	end
	minetest.register_craft {
		type = 'shapeless';
		recipe = (minetest.get_modpath('xdecor') and {
			'xdecor:hammer', itemname;
................................................................................
		}
	end

	if gem.foreign then return false end
	minetest.register_craftitem(itemname, {
		description = sorcery.lib.str.capitalize(name);
		inventory_image = 'sorcery_gem_' .. name .. '.png';
		groups = { gem = 1; crystal = 1; sorcery_gem = 1; };
		_sorcery = {
			material = {
				id = name, data = gem;
				raw = true, value = shards_per_gem;
			};
		};
	})
	local tools = gem.tools
	if tools == nil then tools = {
		'group:pickaxe';
		'group:pick'; -- FUCK YOU INSTANT_ORES
		'~default:enchanted_pick_'; -- FUCK YOU XDECOR
	} end

Modified init.lua from [73a80c3c4b] to [97099862fc].

49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
	'potions', 'oils', 'greases',
		'draughts', 'elixirs',
		'philters', 'extracts';
	'register';
}

for _,u in pairs {
	'attunement'; 'ores'; 'gems'; 'leylines';
	'potions'; 'infuser'; 'altar'; 'wands';
	'tools'; 'crafttools'; 'enchanter'; 'harvester';
	'metallurgy-hot', 'metallurgy-cold';
	'entities'; 'recipes'; 'coins';
	'interop'; 'tnodes'; 'forcefield';
	'farcaster'; 'portal'; 'cookbook'; 'disassembly';
} do sorcery.load(u) end







|
|
|
|
|
|
|

49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
	'potions', 'oils', 'greases',
		'draughts', 'elixirs',
		'philters', 'extracts';
	'register';
}

for _,u in pairs {
	'attunement'; 'metal', 'gems'; 'itemclass';
	'leylines'; 'potions', 'infuser'; 'altar';
	'wands'; 'tools', 'crafttools'; 'enchanter';
	'harvester'; 'metallurgy-hot', 'metallurgy-cold';
	'entities'; 'recipes'; 'coins'; 'interop';
	'tnodes'; 'forcefield'; 'farcaster'; 'portal';
	'cookbook', 'writing'; 'disassembly'; 'displacer';
} do sorcery.load(u) end

Added itemclass.lua version [bee797c513].













































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
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
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
-- in theory, minetest groups are supposed to allow us to
-- give consistent, cross-mod classes to items, and easily
-- detect whether items fit into a particular class. unfortunately,
-- they don't really work for this purpose because we often
-- need to attach additional data to items that are outside
-- of our control (and the default mod's authors are amazingly
-- lax in grouping items; for instance, diamonds and mese
-- crystals aren't even part of a 'gem' or 'crystal' group!)
-- this module allows us to consistently classify items, and
-- easily maintain complex hierarchies of subclasses. whether
-- an item belongs to a class can be determined by checking
-- its groups, consulting compat tables, calling a custom
-- predicate function (possibly to check for a _sorcery
-- defprop), or recursing through a list of subclasses.
-- this also means that matters of identity are all controlled
-- from a central location.
sorcery.itemclass = {
	classes = {
		-- gem/crystalline and metal/metallic differentiate
		-- between crafting materials (i.e. gems or ingots
		-- themselves) and items crafted from those materials.
		-- the former includes only crafting materials, the
		-- latter includes both.
		gem = {
			compat = 'gems';
			groups = { 'gem', 'crystal'; };
			predicate = function(name)
				if minetest.get_item_group(name, 'sorcery_gem') ~= 0 
				or minetest.get_item_group(name, 'sorcery_shard') ~= 0 then
					return minetest.registered_items[name]._sorcery.material;
				end
			end;
		};
		crystalline = {
			subclass = {'gem'};
			predicate = function(name)
				local mat = sorcery.matreg.lookup[name]
				if mat and mat.gem then return mat end
			end;
		};
		grindable = {
			compat = 'grindables';
			subclass = {'metallic'};
			predicate = function(name)
				local def = minetest.registered_items[name]._sorcery
				if not def then return nil end
				def = def.material
				if def and def.grindvalue then
					return def end
			end;
		};
		metal = {
			predicate = function(name)
				-- metallookup is a table of 'primary' metal
				-- items, like ingots, fragments, and powders
				return sorcery.data.metallookup[name]
			end;
		};
		metallic = {
			subclass = {'metal'};
			predicate = function(name)
				-- matreg is a registry binding crafted items,
				-- like armors and tools, to the material they
				-- are made out of
				local mat = sorcery.matreg.lookup[name]
				if mat and mat.metal then return mat end
			end;
		};
	};
	get = function(name,class)
		local c = sorcery.itemclass.classes[class]
		local o
		if not c then return false end

		if c.predicate then 
			o = c.predicate(name)
			if o then return o end
		end

		if c.compat then
			o = sorcery.data.compat[c.compat][name]
			if o then return o end
		end

		if c.subclass then
			for _,s in pairs(c.subclass) do
				o = sorcery.itemclass.get(name,s)
				if o then return o end
			end
		end

		if c.groups then
			for _,g in pairs(c.groups) do
				o = minetest.get_item_group(name,g) 
				if o > 0 then return { kind = o } end
			end
			o = nil
		end

		return false
	end;
}

Modified leylines.lua from [c6bfabb8a9] to [a383f4a3c9].

216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
...
311
312
313
314
315
316
317





318
319
320
321
322
323
324
...
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
			node_box = {
				type = 'connected';
				disconnected   = { -0.05, -0.35, -0.40; 0.05, -0.25, 0.40 };
				connect_front  = { -0.05, -0.35, -0.50; 0.05, -0.25, 0.05 };
				connect_back   = { -0.05, -0.35, -0.05; 0.05, -0.25, 0.50 };
				connect_right  = { -0.05, -0.35, -0.05; 0.50, -0.25, 0.05 };
				connect_left   = { -0.50, -0.35, -0.05; 0.05, -0.25, 0.05 };
				connect_top    = { -0.05, -0.25, -0.05; 0.05,  0.50, 0.05 };
				connect_bottom = { -0.05, -0.50, -0.05; 0.05, -0.35, 0.05 };
			};
			connects_to = { 'group:sorcery_ley_device', 'default:mese' };
			-- harcoding mese is kind of cheating -- figure out a
			-- better way to do this for the longterm
			paramtype = 'light';
			-- paramtype2 = 'facedir';
................................................................................
			sorcery_ley_device = 1;
			sorcery_magitech = 1;
		};
		on_construct = function(pos)
			local meta = minetest.get_meta(pos)
			meta:set_string('infotext','Condenser')
		end;





		_sorcery = {
			ley = { mode = 'produce';
				power = function(pos,time)
					return sorcery.ley.field_to_current(sorcery.ley.estimate(pos).force, time);
				end;
				affinity = function(pos)
					return sorcery.ley.estimate(pos).aff
................................................................................
				local sum = vector.add(pos,p)
				if not foundp(sum) then
					checked[#checked + 1] = sum
					local nodename = minetest.get_node(sum).name
					if nodename == 'ignore' then
						minetest.load_area(sum)
						nodename = minetest.get_node(sum).name
						print('**** ignorenode, loaded',nodename)
					end
					if minetest.get_item_group(nodename,'sorcery_ley_device') ~= 0
					   or sorcery.data.compat.ley[nodename] then
						local d = sorcery.ley.sample(pos,1,nodename,{query={mode=true}})
						assert(d.mode == 'signal'
						    or d.mode == 'consume'
						    or d.mode == 'produce')







|







 







>
>
>
>
>







 







<







216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
...
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
...
380
381
382
383
384
385
386

387
388
389
390
391
392
393
			node_box = {
				type = 'connected';
				disconnected   = { -0.05, -0.35, -0.40; 0.05, -0.25, 0.40 };
				connect_front  = { -0.05, -0.35, -0.50; 0.05, -0.25, 0.05 };
				connect_back   = { -0.05, -0.35, -0.05; 0.05, -0.25, 0.50 };
				connect_right  = { -0.05, -0.35, -0.05; 0.50, -0.25, 0.05 };
				connect_left   = { -0.50, -0.35, -0.05; 0.05, -0.25, 0.05 };
				connect_top    = { -0.05, -0.35, -0.05; 0.05,  0.50, 0.05 };
				connect_bottom = { -0.05, -0.50, -0.05; 0.05, -0.35, 0.05 };
			};
			connects_to = { 'group:sorcery_ley_device', 'default:mese' };
			-- harcoding mese is kind of cheating -- figure out a
			-- better way to do this for the longterm
			paramtype = 'light';
			-- paramtype2 = 'facedir';
................................................................................
			sorcery_ley_device = 1;
			sorcery_magitech = 1;
		};
		on_construct = function(pos)
			local meta = minetest.get_meta(pos)
			meta:set_string('infotext','Condenser')
		end;
		on_rightclick = function(pos)
			local c = sorcery.ley.netcaps(pos,1)
			c.net.devices.signal = nil
			print('LEYNET', dump(c))
		end;
		_sorcery = {
			ley = { mode = 'produce';
				power = function(pos,time)
					return sorcery.ley.field_to_current(sorcery.ley.estimate(pos).force, time);
				end;
				affinity = function(pos)
					return sorcery.ley.estimate(pos).aff
................................................................................
				local sum = vector.add(pos,p)
				if not foundp(sum) then
					checked[#checked + 1] = sum
					local nodename = minetest.get_node(sum).name
					if nodename == 'ignore' then
						minetest.load_area(sum)
						nodename = minetest.get_node(sum).name

					end
					if minetest.get_item_group(nodename,'sorcery_ley_device') ~= 0
					   or sorcery.data.compat.ley[nodename] then
						local d = sorcery.ley.sample(pos,1,nodename,{query={mode=true}})
						assert(d.mode == 'signal'
						    or d.mode == 'consume'
						    or d.mode == 'produce')

Name change from ores.lua to metal.lua.

Modified metallurgy-cold.lua from [3d415e629d] to [a94be8d51a].

85
86
87
88
89
90
91
92

93
94
95
96
97
98
99
...
103
104
105
106
107
108
109

110
111
112
113
114
115
116
117
	elseif slot == 'input' then
		local metal = sorcery.data.metallookup[item:get_name()]
		local mat = sorcery.matreg.lookup[item:get_name()]
		local comp = sorcery.data.compat.grindables[item:get_name()]
		if metal or (mat and mat.metal) or comp then
			return item:get_count()
		else
			mat = item:get_definition()._matprop

			if mat and mat.grindvalue then
				return item:get_count() 
			end
		end
	end
	return 0
end
................................................................................
		-- allow grinding of armor and tools back to their
		-- original components
		local mat = sorcery.matreg.lookup[item:get_name()]
		if mat and mat.metal then
			metal = mat
		end
	end

	local mp = item:get_definition()._matprop
		or sorcery.data.compat.grindables[item:get_name()]
		or {}

	if metal then mp = {
		hardness = mp.hardness or metal.data.hardness;
		grindvalue = ((mp.grindvalue or metal.value) or (metal and constants.metal_grindvalue));
		powder = mp.powder or metal.data.parts.powder;







|
>







 







>
|







85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
...
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
	elseif slot == 'input' then
		local metal = sorcery.data.metallookup[item:get_name()]
		local mat = sorcery.matreg.lookup[item:get_name()]
		local comp = sorcery.data.compat.grindables[item:get_name()]
		if metal or (mat and mat.metal) or comp then
			return item:get_count()
		else
			mat = item:get_definition()._sorcery and
			      item:get_definition()._sorcery.material
			if mat and mat.grindvalue then
				return item:get_count() 
			end
		end
	end
	return 0
end
................................................................................
		-- allow grinding of armor and tools back to their
		-- original components
		local mat = sorcery.matreg.lookup[item:get_name()]
		if mat and mat.metal then
			metal = mat
		end
	end
	local mp = (item:get_definition()._sorcery and
		item:get_definition()._sorcery.material)
		or sorcery.data.compat.grindables[item:get_name()]
		or {}

	if metal then mp = {
		hardness = mp.hardness or metal.data.hardness;
		grindvalue = ((mp.grindvalue or metal.value) or (metal and constants.metal_grindvalue));
		powder = mp.powder or metal.data.parts.powder;

Modified portal.lua from [021fb6f980] to [2352fa593a].

58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
...
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
...
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
...
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
...
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
	minetest.load_area(
		vector.add(pos, {x =  5, y =  1 + constants.portal_max_height, z =  5}),
		vector.add(pos, {x = -5, y = -1 - constants.portal_max_height, z = -5})
	)
	-- starting at a portal node, search connected blocks
	-- recursively to locate portal pads and their paired
	-- partners. return a table characterizing the portal
	-- return false if no portal found
	
	-- first search immediate neighbors. the portal node
	-- can be connected to either reflectors or pads
	local startpoint, startwithpads
	for _, d in pairs(sorcery.lib.node.offsets.neighbors) do
		local sum = vector.add(pos,d)
		local name = minetest.get_node(sum).name
................................................................................
		for _, d in pairs(n.caps.net.devices.consume) do
			if d.id == 'sorcery:portal_node' and portal_composition(d.pos) then
				circuit[#circuit+1] = {
					pos = d.pos;
					hops = n.hops;
					route = n.route;
				}
				print(' ! found portal node',d.pos)
			else
				print('   -- skipping node',d.pos,d.id)
			end
		end
	end
	return circuit
end

local portal_disposition = function(dev)
................................................................................
		local tune = sorcery.attunement.verify(pos)
		local partner -- raw position of partner node, if any
		if tune and tune.partner then
			minetest.load_area(tune.partner)
			-- we are attuned to a partner, but is it in the circuit?
			for _,v in pairs(crc) do
				if vector.equals(v.pos,tune.partner) then
					print('found partner in circuit')
					partner = tune.partner
					break
				end
			end
		end

		print("power reqs",cap.self.minpower,cap.self.powerdraw)
		if cap.self.minpower ~= cap.self.powerdraw then
			print("not enough power")
			return true
		end

		-- clean out user table
		for name,user in pairs(portal_context.users) do
................................................................................
					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)
					print('free power',cap.freepower,jc)
					if not user.dest and cap.freepower >= jc  then
						user.dest = portal_pick_destination(dev,crc,partner)
					else
						print('powerdraw',cap.self.powerdraw)
					end
					if not user.dest then goto skippad else
						minetest.load_area(user.dest)
					end
					local fac = (user.time / constants.portal_jump_time);
					minetest.add_particlespawner {
						time = 1, amount = 100 + (fac * 200);
................................................................................
			mode = 'consume', affinity = {'mandatic'};
			power = function(pos,delta)
				-- return power use if device is currently in process
				-- of teleporting a player; otherwise, return 0
				local cost = constants.portal_node_power
				for _,u in pairs(portal_context.users) do
					if u.dest and vector.equals(u.portal, pos) then
						print('user is on pad',dump(pos))
						cost = cost + constants.portal_jump_cost_local
					end
				end
				return cost * delta
			end;
		};
		







|







 







<
<
<







 







<






<







 







<


<
<







 







<







58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
...
158
159
160
161
162
163
164



165
166
167
168
169
170
171
...
285
286
287
288
289
290
291

292
293
294
295
296
297

298
299
300
301
302
303
304
...
325
326
327
328
329
330
331

332
333


334
335
336
337
338
339
340
...
436
437
438
439
440
441
442

443
444
445
446
447
448
449
	minetest.load_area(
		vector.add(pos, {x =  5, y =  1 + constants.portal_max_height, z =  5}),
		vector.add(pos, {x = -5, y = -1 - constants.portal_max_height, z = -5})
	)
	-- starting at a portal node, search connected blocks
	-- recursively to locate portal pads and their paired
	-- partners. return a table characterizing the portal
	-- or return false if no portal found
	
	-- first search immediate neighbors. the portal node
	-- can be connected to either reflectors or pads
	local startpoint, startwithpads
	for _, d in pairs(sorcery.lib.node.offsets.neighbors) do
		local sum = vector.add(pos,d)
		local name = minetest.get_node(sum).name
................................................................................
		for _, d in pairs(n.caps.net.devices.consume) do
			if d.id == 'sorcery:portal_node' and portal_composition(d.pos) then
				circuit[#circuit+1] = {
					pos = d.pos;
					hops = n.hops;
					route = n.route;
				}



			end
		end
	end
	return circuit
end

local portal_disposition = function(dev)
................................................................................
		local tune = sorcery.attunement.verify(pos)
		local partner -- raw position of partner node, if any
		if tune and tune.partner then
			minetest.load_area(tune.partner)
			-- we are attuned to a partner, but is it in the circuit?
			for _,v in pairs(crc) do
				if vector.equals(v.pos,tune.partner) then

					partner = tune.partner
					break
				end
			end
		end


		if cap.self.minpower ~= cap.self.powerdraw then
			print("not enough power")
			return true
		end

		-- clean out user table
		for name,user in pairs(portal_context.users) do
................................................................................
					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)


					end
					if not user.dest then goto skippad else
						minetest.load_area(user.dest)
					end
					local fac = (user.time / constants.portal_jump_time);
					minetest.add_particlespawner {
						time = 1, amount = 100 + (fac * 200);
................................................................................
			mode = 'consume', affinity = {'mandatic'};
			power = function(pos,delta)
				-- return power use if device is currently in process
				-- of teleporting a player; otherwise, return 0
				local cost = constants.portal_node_power
				for _,u in pairs(portal_context.users) do
					if u.dest and vector.equals(u.portal, pos) then

						cost = cost + constants.portal_jump_cost_local
					end
				end
				return cost * delta
			end;
		};
		

Modified recipes.lua from [e76efbfb0c] to [7adbea13be].

7
8
9
10
11
12
13











14
15
16
17
18
19
20
...
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
...
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
...
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
		"vessels:glass_bottle"
	};
	output = "sorcery:potion_water 3";
	replacements = {
		{ "group:water_bucket", "bucket:bucket_empty" }
	};
}












minetest.register_craft {
	type = "shapeless";
	recipe = {
		"bucket:bucket_empty";
		"sorcery:potion_water";
		"sorcery:potion_water";
................................................................................
	};
	output = "sorcery:infuser";
}

minetest.register_craft {
	output = "sorcery:displacer";
	recipe = {
		{'sorcery:platinum_ingot','sorcery:leyline_stabilizer','sorcery:platinum_ingot'};
		{'sorcery:inverter_coil','sorcery:core_syncretic','sorcery:inverter_coil'};
		{'sorcery:platinum_ingot','default:chest','sorcery:platinum_ingot'};
	};
}

minetest.register_craft {
	output = "sorcery:displacement_node";
	recipe = {
		{'sorcery:platinum_ingot','sorcery:screw_tungsten','sorcery:platinum_ingot'};
		{'basic_materials:copper_wire','sorcery:core_syncretic','doors:trapdoor_steel'};
		{'sorcery:platinum_ingot','sorcery:screw_tungsten','sorcery:platinum_ingot'};
	};
	replacements = {
		{'basic_materials:copper_wire','basic_materials:empty_spool'};
	};
}





































minetest.register_craft {
	output = 'sorcery:raycaster';
	recipe = {
		{'sorcery:gem_amethyst', 'sorcery:gem_amethyst', 'sorcery:gem_amethyst'};
		{'default:gold_ingot','sorcery:beam_generator','default:gold_ingot'};
		{'sorcery:gem_amethyst', 'sorcery:gem_amethyst', 'sorcery:gem_amethyst'};
................................................................................
	groups = {
		sorcery_magitech = 1; metal = 1; sorcery_magitech_core = 1;
	};
});

minetest.register_craftitem('sorcery:core_syncretic',{
	description = 'Syncresis Core';
	inventory_image = 'sorcery_core_sycretic.png';
	groups = {
		sorcery_magitech = 1; metal = 1; sorcery_magitech_core = 1;
	};
});

minetest.register_craftitem('sorcery:suppression_matrix',{
	description = 'Suppression Matrix';
................................................................................
	};
}

minetest.register_craft {
	output = 'sorcery:core_syncretic';
	recipe = {
		{'sorcery:gem_sapphire_shard','default:gold_ingot','sorcery:gem_sapphire_shard'};
		{'default:gold_ingot','sorcery:gem_diamond','default:gold_ingot'};
		{'sorcery:gem_sapphire_shard','default:gold_ingot','sorcery:gem_sapphire_shard'};
	};
}

minetest.register_craft {
	output = 'sorcery:core_mandatic';
	recipe = {







>
>
>
>
>
>
>
>
>
>
>







 







<
<
<
<
<
<
<
<
<








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







 







|







 







|







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
...
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
306
307
308
309
310
311
312
313
314
315
316
317
318
...
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
...
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
		"vessels:glass_bottle"
	};
	output = "sorcery:potion_water 3";
	replacements = {
		{ "group:water_bucket", "bucket:bucket_empty" }
	};
}
minetest.register_craft {
	output = 'dye:white 4';
	recipe = {
		{'',           'sorcery:ash',             ''};
		{'sorcery:ash','basic_materials:paraffin','sorcery:ash'};
		{'',           'bucket:bucket_water',     ''};
	};
	replacements = {
		{'bucket:bucket_water', 'bucket:bucket_empty'};
	};
};

minetest.register_craft {
	type = "shapeless";
	recipe = {
		"bucket:bucket_empty";
		"sorcery:potion_water";
		"sorcery:potion_water";
................................................................................
	};
	output = "sorcery:infuser";
}

minetest.register_craft {
	output = "sorcery:displacer";
	recipe = {









		{'sorcery:platinum_ingot','sorcery:screw_tungsten','sorcery:platinum_ingot'};
		{'basic_materials:copper_wire','sorcery:core_syncretic','doors:trapdoor_steel'};
		{'sorcery:platinum_ingot','sorcery:screw_tungsten','sorcery:platinum_ingot'};
	};
	replacements = {
		{'basic_materials:copper_wire','basic_materials:empty_spool'};
	};
}

minetest.register_craft {
	output = "sorcery:displacer_transmit_attune";
	recipe = {
		{'sorcery:platinum_ingot','sorcery:screw_tungsten','sorcery:platinum_ingot'};
		{'sorcery:leyline_stabilizer','sorcery:core_mandatic','sorcery:tuning_disc'};
		{'sorcery:platinum_ingot','sorcery:screw_tungsten','sorcery:platinum_ingot'};
	};
}

minetest.register_craft {
	output = "sorcery:displacer_transmit_gem";
	recipe = {
		{'sorcery:platinum_ingot','sorcery:screw_tungsten','sorcery:platinum_ingot'};
		{'sorcery:leyline_stabilizer','sorcery:core_mandatic','sorcery:gem_ruby'};
		{'sorcery:platinum_ingot','sorcery:screw_tungsten','sorcery:platinum_ingot'};
	};
}

minetest.register_craft {
	output = "sorcery:displacer_receive_attune";
	recipe = {
		{'sorcery:platinum_ingot','sorcery:screw_tungsten','sorcery:platinum_ingot'};
		{'sorcery:inverter_coil','sorcery:core_mandatic','sorcery:tuning_disc'};
		{'sorcery:platinum_ingot','sorcery:screw_tungsten','sorcery:platinum_ingot'};
	};
}

minetest.register_craft {
	output = "sorcery:displacer_receive_gem";
	recipe = {
		{'sorcery:platinum_ingot','sorcery:screw_tungsten','sorcery:platinum_ingot'};
		{'sorcery:inverter_coil','sorcery:core_mandatic','sorcery:gem_ruby'};
		{'sorcery:platinum_ingot','sorcery:screw_tungsten','sorcery:platinum_ingot'};
	};
}

minetest.register_craft {
	output = 'sorcery:raycaster';
	recipe = {
		{'sorcery:gem_amethyst', 'sorcery:gem_amethyst', 'sorcery:gem_amethyst'};
		{'default:gold_ingot','sorcery:beam_generator','default:gold_ingot'};
		{'sorcery:gem_amethyst', 'sorcery:gem_amethyst', 'sorcery:gem_amethyst'};
................................................................................
	groups = {
		sorcery_magitech = 1; metal = 1; sorcery_magitech_core = 1;
	};
});

minetest.register_craftitem('sorcery:core_syncretic',{
	description = 'Syncresis Core';
	inventory_image = 'sorcery_core_syncretic.png';
	groups = {
		sorcery_magitech = 1; metal = 1; sorcery_magitech_core = 1;
	};
});

minetest.register_craftitem('sorcery:suppression_matrix',{
	description = 'Suppression Matrix';
................................................................................
	};
}

minetest.register_craft {
	output = 'sorcery:core_syncretic';
	recipe = {
		{'sorcery:gem_sapphire_shard','default:gold_ingot','sorcery:gem_sapphire_shard'};
		{'default:gold_ingot','default:diamond','default:gold_ingot'};
		{'sorcery:gem_sapphire_shard','default:gold_ingot','sorcery:gem_sapphire_shard'};
	};
}

minetest.register_craft {
	output = 'sorcery:core_mandatic';
	recipe = {

Modified textures/sorcery_core_syncretic.png from [086b79dff1] to [08f74a08b5].

cannot compute difference between binary files

Added textures/sorcery_displacer_front.png version [51a09bc368].

cannot compute difference between binary files

Added textures/sorcery_displacer_module_receive.png version [99916d60d5].

cannot compute difference between binary files

Added textures/sorcery_displacer_module_transmit.png version [ee8e01ea67].

cannot compute difference between binary files

Added textures/sorcery_displacer_side.png version [fe755fceaa].

cannot compute difference between binary files

Added textures/sorcery_displacer_top.png version [e8a57f9374].

cannot compute difference between binary files

Modified textures/sorcery_tuning_disc.png from [babfdd1af1] to [4820ec8236].

cannot compute difference between binary files

Deleted textures/sorcery_wandworking_station_top.png~ version [197c24c8ba].

cannot compute difference between binary files

Added writing.lua version [d9df5661ad].



































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
-- this file contains a few enhancements to the normal book and
-- paper functionality. it allows authors to disavow their books,
-- making them appear as by an "unknown author", by smudging out
-- the byline with black dye. it also allows written books to be
-- soaked in a bucket of water to wash out the ink and return
-- them to a virginal, unwritten state. finally, it makes it so
-- that when a book (or any owned item, for that matter) is
-- copied, the owner of the new copy is set to the user who
-- copied it, allowing users to collaborate on books.

local paperburn = function(item,value)
	minetest.register_craft { type = 'fuel', recipe = item, burntime = 3 * value }
	minetest.register_craft {
		type = 'cooking';
		recipe = item;
		output = 'sorcery:ash ' .. tostring(value);
		cooktime = 3 * value;
	}
end

paperburn('default:paper',1) paperburn('sorcery:recipe',1)
paperburn('default:book',3) paperburn('sorcery:cookbook',3)
paperburn('default:book_written',3)

minetest.register_craft {
	type = "shapeless";
	recipe = {"default:book_written", "bucket:bucket_water"};
	output = "default:book";
	replacements = {
		{"bucket:bucket_water", "bucket:bucket_empty"}
	}
}

minetest.register_craft {
	type = 'shapeless';
	recipe = {"default:book_written", "dye:black"};
	output = 'default:book_written';
}

minetest.register_on_craft(function(itemstack,player,recipe,pinv)
	local meta = itemstack:get_meta()
	if not meta:contains('owner') then return nil end
	local pname = player:get_player_name()
	if meta:get_string('owner') ~= pname then
		meta:set_string('owner', pname)
	end

	if itemstack:get_name() == 'default:book_written' then
		local found_book, found_dye, book_idx = false, false, 0
		for i,v in pairs(recipe) do
			if v:get_name() == 'dye:black' and not found_dye
				then found_dye = true
			elseif v:get_name() == 'default:book_written' and not found_book
				then found_book = v book_idx = i
			elseif not v:is_empty() then found_book = false break end
		end
		if found_book and found_dye then
			meta:from_table(found_book:get_meta():to_table())
			meta:set_string('owner','unknown author')
			meta:set_string('description','"'..meta:get_string('title')..'"')
			pinv:set_stack('craft',book_idx,ItemStack())
		end
	end
	return itemstack
end)