sorcery  Diff

Differences From Artifact [112e3b2a50]:

To Artifact [8532b8e5fe]:


71
72
73
74
75
76
77






























































































78























79
80


















































81
82
83




84
85


















86
87
88
89
90
91
92
...
102
103
104
105
106
107
108

109
110
111
112
113
114
115
...
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
...
216
217
218
219
220
221
222

223
224
225
226
227
228
229
...
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

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
















































































































































	output = 'sorcery:conduit 4';
	recipe = {
		{'default:copper_ingot', 'default:copper_ingot',  'default:copper_ingot'};
		{'default:copper_ingot', 'sorcery:electrumblock', 'default:copper_ingot'};
		{'default:copper_ingot', 'default:copper_ingot',  'default:copper_ingot'};
	};
};






























































































minetest.register_craft {























	output = 'sorcery:wire 4';
	recipe = {


















































		{'', 'basic_materials:copper_wire',''};
		{'', 'sorcery:fragment_electrum',  ''};
		{'', 'basic_materials:copper_wire',''};




	}
};



















sorcery.ley.field_to_current = function(strength,time)
	local ley_factor = 0.25
	-- a ley harvester will produce this much current with
	-- access to a full-strength leyline
	
	return strength * ley_factor * time;
................................................................................
			-0.5, -0.5, -0.5;
			 0.5,  1.2,  0.5;
		};
	};
	minetest.register_node('sorcery:condenser', {
		description = 'Condenser';
		drawtype = 'mesh';

		mesh = 'sorcery-condenser.obj';
		selection_box = box;
		collision_box = box;
		tiles = {
			amethyst:render();
			'sorcery_condenser.png';
			'default_tin_block.png';
................................................................................
			sorcery_ley_device = 1;
		};
		on_construct = function(pos)
			local meta = minetest.get_meta(pos)
			meta:set_string('infotext','Condenser')
		end;
		_sorcery = {
			ley = { mode = 'produce' };
			on_leycalc = function(pos,time)
				local l = sorcery.ley.estimate(pos)
				return {
					power = sorcery.ley.field_to_current(l.force, time);
					affinity = l.aff;
				}
			end;
		};
	})
end

minetest.register_craft {
	output = 'sorcery:condenser';
	recipe = {
		{'sorcery:accumulator'};
		{'sorcery:conduit'};
	};
}









sorcery.ley.mapnet = function(startpos,power)
	-- this function returns a list of all the nodes accessible from
	-- a ley network and their associated positions
	local net = {}
	power = power or 0
	
	local devices = {
		consume = {};
		produce = {};
		signal = {};
	}
	local numfound = 0
	local maxconduct = 0
	local minconduct

	local foundp = function(p)
		for k in pairs(net) do
			if vector.equals(p,k) then return true end
		end
		return false
	end
	-- we're implementing this with a recursive function to start with
	-- but this could rapidly lead to stack overflows so we should
	-- replace it with a linear one at some point
	local function find(positions)
		local searchnext = {}
		for _,pos in pairs(positions) do
			for _,p in pairs {
				{x =  0, z =  0, y =  0};
				{x = -1, z =  0, y =  0};
				{x =  1, z =  0, y =  0};
				{x =  0, z = -1, y =  0};
				{x =  0, z =  1, y =  0};
				{x =  0, z =  0, y = -1};
				{x =  0, z =  0, y =  1};
			} do local sum = vector.add(pos,p)
				if not foundp(sum) then

					local nodename = minetest.get_node(sum).name




					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)
						assert(d.mode == 'signal'
						    or d.mode == 'consume'
						    or d.mode == 'produce')
						devices[d.mode][#(devices[d.mode]) + 1] = {
							id = nodename; pos = sum;
						}
						if d.mode == 'signal' then

							if d.power > power then
								if minconduct then
									if d.power < minconduct then
										minconduct = d.power
									end
								else minconduct = d.power end
								if d.power > maxconduct then
									maxconduct = d.power
								end
							end
						end
						numfound = numfound + 1;
						net[sum] = nodename;





						searchnext[#searchnext + 1] = sum;
					end
				end
			end
		end
		if #searchnext > 0 then find(searchnext) end
	end
................................................................................
	find{startpos}

	if numfound > 0 then
		return {
			count = numfound;
			map = net;
			devices = devices;

			conduct = {
				min = minconduct;
				max = maxconduct;
			};
		}
	else return nil end
end
................................................................................
		[3] = 'signal';
	}
	for i=1,#afftbl  do afftbl [afftbl [i]] = i end
	for i=1,#modetbl do modetbl[modetbl[i]] = i end
	local m = sorcery.lib.marshal
	local enc, dec = m.transcoder {
		mode = m.t.u8;
		power = m.t.u32; -- power generated/consumed * 10,000

		affinity = m.g.array(m.t.u8); -- indexes into afftbl
	}
	sorcery.ley.encode = function(l)
		local idxs = {}
		for _,k in pairs(l.affinity) do
			idxs[#idxs+1] = afftbl[k]
		end
		return meta_armor(enc {
			mode = modetbl[l.mode];
			power = l.power * 10000;

			affinity = idxs;
		}, true)
	end
	sorcery.ley.decode = function(str)
		local obj = dec(meta_dearmor(str,true))
		local affs = {}
		for _,k in pairs(obj.affinity) do
			affs[#affs+1] = afftbl[k]
		end
		return {
			mode = modetbl[obj.mode];
			power = obj.power / 10000.0;


			affinity = affs;
		}
	end
end
sorcery.ley.setnode = function(pos,l)
	local meta = minetest.get_node(pos)
	meta:set_string('sorcery:ley',sorcery.ley.encode(l))
end

sorcery.ley.sample = function(pos,timespan,name)
	-- returns how much ley-force can be transmitted by a
	-- device over timespan

	name = name or minetest.get_node(pos).name





	local props = minetest.registered_nodes[name]._sorcery
	local callback = props and props.on_leycalc or nil
	local p,a,m
	if callback then
		local gen = callback(pos,timespan)
		p = gen.power
		a = gen.affinity
		m = gen.mode





	end

	if not (p and a and m) then

		local nm = minetest.get_meta(pos)
		if nm:contains('sorcery:ley') then
			local l = sorcery.ley.decode(nm:get_string('sorcery:ley'))
			p = p or sorcery.ley.field_to_current(l.power,timespan)
			a = a or l.affinity
			m = m or l.mode
		end
	end

	if (not (p and a and m)) and props and props.ley then
		p = p or sorcery.ley.field_to_current(props.ley.power,timespan)
		a = a or props.ley.affinity
		m = m or props.ley.mode
	end




	if (not (p and a and m)) then
		local compat = sorcery.data.compat.ley[name] 
		if compat then
			p = p or sorcery.ley.field_to_current(compat.power,timespan)
			a = a or compat.affinity
			m = m or compat.mode


		end














	end























	return {
		power = p or 0;
		mode = m or 'none';
		affinity = a or {};
	}
end

sorcery.ley.netcaps = function(pos,timespan,exclude)
	local net = sorcery.ley.mapnet(pos)
	local maxpower = 0
	local freepower = 0
	local affs,usedaffs = {},{}


	for _,n in pairs(net.devices.produce) do

		if not exclude or not vector.equals(n.pos,exclude) then
			local ln = sorcery.ley.sample(n.pos,timespan,n.id)


			maxpower = maxpower + ln.power


			for _,a in pairs(ln.affinity) do
				affs[a] = (affs[a] or 0) + 1
			end
		end
	end
	freepower = maxpower;
	for _,n in pairs(net.devices.consume) do

		if not exclude or not vector.equals(n.pos,exclude) then
			local ln = sorcery.ley.sample(n.pos,timespan,n.id)



			freepower = freepower - ln.power







			for _,a in pairs(ln.affinity) do
				usedaffs[a] = (usedaffs[a] or 0) + 1
			end
		end
	end




























	
	return {
		net = net;
		freepower = freepower;
		maxpower = maxpower;
		affinity = affs;
		affinity_balance = usedaffs;

	}
end























































































































































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

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







 







>







 







|
|
|
|
|
|
|
|











>
>
>
>
>
>
>
>
>



|










>

|










|
<
<
<
<
<
<
<
|

>

>
>
>
>


|







>













>
>
>
>
>







 







>







 







|
>









|
>











|
>
>









|


>

>
>
>
>
>

<
<
<
<
<
<
<
>
>
>
>
>


<
>


|
<
<
<



<
|
<
<
<

>
>
>
|
|
|
|
|
|
>
>

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

>
>
>
>
|
<
<
<
<







>
>

>


>
>

>
>







>

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





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







>


>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
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
...
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
...
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
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
...
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
...
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
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
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565




566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
	output = 'sorcery:conduit 4';
	recipe = {
		{'default:copper_ingot', 'default:copper_ingot',  'default:copper_ingot'};
		{'default:copper_ingot', 'sorcery:electrumblock', 'default:copper_ingot'};
		{'default:copper_ingot', 'default:copper_ingot',  'default:copper_ingot'};
	};
};

local makeswitch = function(switch, desc, tex, tiles, power)
	for _,active in pairs{true,false} do
		local turn = function(pos)
			local n = minetest.get_node(pos)
			minetest.sound_play('doors_steel_door_open', {
				gain = 0.7;
				pos = pos;
			}, true)
			local leymap = active and sorcery.ley.mapnet(pos) or nil
			minetest.swap_node(pos, {
				name = active and (switch .. '_off')
							   or  switch;
				param1 = n.param1;
				param2 = n.param2;
			})
			if active then
				-- if we're turning it off, use the old map,
				-- because post-swap the network will be
				-- broken and notify won't reach everyone
				leymap.map[leymap.startpos] = nil
				sorcery.ley.notifymap(leymap.map)
			else sorcery.ley.notify(pos) end
		end
		local tl = table.copy(tiles)
		tl[6] = tex .. '^sorcery_ley_switch_panel.png^sorcery_ley_switch_' .. (active and 'down' or 'up') .. '.png';
		minetest.register_node(switch .. (active and '' or '_off'), {
			description = desc;
			drop = switch;
			tiles = tl;
			paramtype2 = 'facedir';
			groups = {
				cracky = 2; choppy = 1;
				punch_operable = 1;
				sorcery_ley_device = active and 1 or 0;
			};
			_sorcery = {
				ley = active and {
					mode = 'signal'; power = power;
				} or nil;
			};
			on_punch = function(pos,node,puncher,point)
				if puncher ~= nil then
					if puncher:get_wielded_item():is_empty() then
						turn(pos)
					end
				end
				return minetest.node_punch(pos,node,puncher,point)
			end;
			on_rightclick = turn;
		})
	end
end

for _,b in pairs {
	{'Applewood', 'wood', 'default_wood.png'};
	{'Junglewood', 'junglewood', 'default_junglewood.png'};
	{'Pine', 'pine_wood', 'default_pine_wood.png'};
	{'Acacia', 'acacia_wood', 'default_pine_wood.png'};
	{'Aspen', 'aspen_wood', 'default_aspen_wood.png'};
	{'Stone', 'stone', 'default_stone.png'};
	{'Cobblestone', 'cobble', 'default_cobble.png'};
	{'Stone Brick', 'stonebrick', 'default_stone_brick.png'};
	{'Brick', 'brick', 'default_brick.png'};
} do
	local id = 'sorcery:conduit_half_' .. b[2]
	local switch = 'sorcery:conduit_switch_' .. b[2]
	local item = (b[4] or 'default') .. ':' .. b[2]
	local tex = b[3]
	local mod = '^[lowpart:50:'
	local sidemod = '^[transformR270^[lowpart:50:'
	local unflip = '^[transformR90'
	local tiles = {
		'sorcery_conduit_copper_top.png'..mod..tex; -- top
		tex..mod..'sorcery_conduit_copper_top.png';
		tex .. sidemod .. 'sorcery_conduit_copper_side.png' .. unflip; -- side
		'sorcery_conduit_copper_side.png' .. sidemod .. tex .. unflip; -- side
		'sorcery_conduit_copper_side.png'; -- back
		tex; -- front
	}
	minetest.register_node(id, {
		description = 'Half-' .. b[1] .. ' Conduit';
		paramtype2 = 'facedir';
		groups = {
			cracky = 2;
			choppy = 1;
			sorcery_ley_device = 1;
			sorcery_ley_conduit = 1;
		};
		_sorcery = {
			ley = { mode = 'signal'; power = 5; }
		};
		tiles = tiles;
	})
	minetest.register_craft {
		output = id .. ' 4';
		recipe = {
			{item, 'sorcery:conduit'};
			{item, 'sorcery:conduit'};
		};
	};
	makeswitch(switch, b[1] .. " Conduit Switch", tex, tiles, 5)
	minetest.register_craft {
		output = switch;
		recipe = {
			{'xdecor:lever_off',id};
		};
	}
end
makeswitch('sorcery:conduit_switch', "Conduit Switch", 'sorcery_conduit_copper_side.png', {
	'sorcery_conduit_copper_top.png';
	'sorcery_conduit_copper_top.png';
	'sorcery_conduit_copper_side.png';
	'sorcery_conduit_copper_side.png';
	'sorcery_conduit_copper_side.png';
	'sorcery_conduit_copper_side.png';
}, 10)
minetest.register_craft {
	output = 'sorcery:conduit_switch';
	recipe = {
		{'xdecor:lever_off','sorcery:conduit'};
	};
}

for name,metal in pairs(sorcery.data.metals) do
	if metal.conduct then
		local cable = 'sorcery:cable_' .. name
		minetest.register_node(cable, {
			description = sorcery.lib.str.capitalize(name) .. " Cable";
			drawtype = 'nodebox';
			groups = {
				sorcery_ley_device = 1; snappy = 3; attached = 1;
				sorcery_ley_cable = 1;
			};
			_sorcery = {
				ley = { mode = 'signal', power = metal.conduct };
			};
			sunlight_propagates = true;
			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';
			after_place_node = function(pos, placer, stack, point)
				local vec = vector.subtract(point.under, pos)
				local n = minetest.get_node(pos)
				n.param2 = minetest.dir_to_facedir(vec)
				minetest.swap_node(pos,n)
			end;
			tiles = { 'sorcery_ley_plug.png' };
		})

		minetest.register_craft {
			output = cable .. ' 8';
			recipe = {
				{'basic_materials:copper_wire','basic_materials:copper_wire','basic_materials:copper_wire'};
				{ metal.parts.fragment, metal.parts.fragment, metal.parts.fragment };
				{'basic_materials:copper_wire','basic_materials:copper_wire','basic_materials:copper_wire'};
			};
			replacements = {
				{'basic_materials:copper_wire', 'basic_materials:empty_spool'};

				{'basic_materials:copper_wire', 'basic_materials:empty_spool'};
				{'basic_materials:copper_wire', 'basic_materials:empty_spool'};
				{'basic_materials:copper_wire', 'basic_materials:empty_spool'};
				{'basic_materials:copper_wire', 'basic_materials:empty_spool'};
				{'basic_materials:copper_wire', 'basic_materials:empty_spool'};
			};
		};
	end
end

-- ley.notify will normally be called automatically, but if a
-- ley-producer or consume has fluctuating levels of energy
-- consumption, it should call this function when levels change
sorcery.ley.notifymap = function(map)
	for pos,name in pairs(map) do
		local props = minetest.registered_nodes[name]._sorcery
		if props and props.on_leychange then
			props.on_leychange(pos)
		end
	end
end
sorcery.ley.notify = function(pos)
	local n = sorcery.ley.mapnet(pos)
	sorcery.ley.notifymap(n.map)
end

sorcery.ley.field_to_current = function(strength,time)
	local ley_factor = 0.25
	-- a ley harvester will produce this much current with
	-- access to a full-strength leyline
	
	return strength * ley_factor * time;
................................................................................
			-0.5, -0.5, -0.5;
			 0.5,  1.2,  0.5;
		};
	};
	minetest.register_node('sorcery:condenser', {
		description = 'Condenser';
		drawtype = 'mesh';
		paramtype2 = 'facedir';
		mesh = 'sorcery-condenser.obj';
		selection_box = box;
		collision_box = box;
		tiles = {
			amethyst:render();
			'sorcery_condenser.png';
			'default_tin_block.png';
................................................................................
			sorcery_ley_device = 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
				end;
			};
		};
	})
end

minetest.register_craft {
	output = 'sorcery:condenser';
	recipe = {
		{'sorcery:accumulator'};
		{'sorcery:conduit'};
	};
}
sorcery.ley.txofs = {
	{x =  0, z =  0, y =  0};
	{x = -1, z =  0, y =  0};
	{x =  1, z =  0, y =  0};
	{x =  0, z = -1, y =  0};
	{x =  0, z =  1, y =  0};
	{x =  0, z =  0, y = -1};
	{x =  0, z =  0, y =  1};
}
sorcery.ley.mapnet = function(startpos,power)
	-- this function returns a list of all the nodes accessible from
	-- a ley network and their associated positions
	local net,checked = {},{}
	power = power or 0
	
	local devices = {
		consume = {};
		produce = {};
		signal = {};
	}
	local numfound = 0
	local maxconduct = 0
	local minconduct
	local startkey
	local foundp = function(p)
		for _,k in pairs(checked) do
			if vector.equals(p,k) then return true end
		end
		return false
	end
	-- we're implementing this with a recursive function to start with
	-- but this could rapidly lead to stack overflows so we should
	-- replace it with a linear one at some point
	local function find(positions)
		local searchnext = {}
		for _,pos in pairs(positions) do
			for _,p in pairs(sorcery.ley.txofs) do







				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')
						devices[d.mode][#(devices[d.mode]) + 1] = {
							id = nodename; pos = sum;
						}
						if d.mode == 'signal' then
							d.power = sorcery.ley.sample(pos,1,nodename,{query={power=true}}).power
							if d.power > power then
								if minconduct then
									if d.power < minconduct then
										minconduct = d.power
									end
								else minconduct = d.power end
								if d.power > maxconduct then
									maxconduct = d.power
								end
							end
						end
						numfound = numfound + 1;
						net[sum] = nodename;
						if not startkey then
							if vector.equals(startpos,sum) then
								startkey = sum
							end
						end
						searchnext[#searchnext + 1] = sum;
					end
				end
			end
		end
		if #searchnext > 0 then find(searchnext) end
	end
................................................................................
	find{startpos}

	if numfound > 0 then
		return {
			count = numfound;
			map = net;
			devices = devices;
			startpos = startkey;
			conduct = {
				min = minconduct;
				max = maxconduct;
			};
		}
	else return nil end
end
................................................................................
		[3] = 'signal';
	}
	for i=1,#afftbl  do afftbl [afftbl [i]] = i end
	for i=1,#modetbl do modetbl[modetbl[i]] = i end
	local m = sorcery.lib.marshal
	local enc, dec = m.transcoder {
		mode = m.t.u8;
		minpower = m.t.u32; -- min power generated/consumed * 10,000
		maxpower = m.t.u32; -- max power generated/consumed * 10,000
		affinity = m.g.array(m.t.u8); -- indexes into afftbl
	}
	sorcery.ley.encode = function(l)
		local idxs = {}
		for _,k in pairs(l.affinity) do
			idxs[#idxs+1] = afftbl[k]
		end
		return meta_armor(enc {
			mode = modetbl[l.mode];
			minpower = l.minpower * 10000;
			maxpower = l.maxpower * 10000;
			affinity = idxs;
		}, true)
	end
	sorcery.ley.decode = function(str)
		local obj = dec(meta_dearmor(str,true))
		local affs = {}
		for _,k in pairs(obj.affinity) do
			affs[#affs+1] = afftbl[k]
		end
		return {
			mode = modetbl[obj.mode];
			minpower = obj.minpower / 10000.0;
			maxpower = obj.maxpower / 10000.0;
			power = (obj.minpower == obj.maxpower) and obj.minpower or nil;
			affinity = affs;
		}
	end
end
sorcery.ley.setnode = function(pos,l)
	local meta = minetest.get_node(pos)
	meta:set_string('sorcery:ley',sorcery.ley.encode(l))
end

sorcery.ley.sample = function(pos,timespan,name,flags)
	-- returns how much ley-force can be transmitted by a
	-- device over timespan
	local ret = {}
	name = name or minetest.get_node(pos).name
	flags = flags or {}
	flags.query = flags.query or {
		mode = true; power = true; affinity = true;
		minpower = true; maxpower = true;
	}
	local props = minetest.registered_nodes[name]._sorcery








	local evaluate = function(v)
		if type(v) == 'function' then
			return v(pos)
		else return v end
	end


	local leymeta do
		local nm = minetest.get_meta(pos)
		if nm:contains('sorcery:ley') then
			leymeta = sorcery.ley.decode(nm:get_string('sorcery:ley'))



		end
	end


	local compat = sorcery.data.compat.ley[name] 




	local lookup = function(k,default)
		if leymeta and leymeta[k] then return leymeta[k]
		elseif props and props.ley and props.ley[k] then return props.ley[k]
		elseif compat and compat[k] then return compat[k]
		else return default end 
	end
	if flags.query.mode     then ret.mode     = evaluate(lookup('mode','none')) end
	if flags.query.affinity then ret.affinity = evaluate(lookup('affinity',{})) end
	if flags.query.minpower or flags.query.maxpower or flags.query.power then
		local condset = function(name,var)
			if flags.query[name] then ret[name] = var end
		end
		local p = lookup('power')
		if p then
			if type(p) == 'function' then
			-- we have a single function to calculate power usage; we need to
			-- check whether it returns min,max or a constant
				local min, max = p(pos,timespan)
				if (not max) or min == max then
					ret.power = min
					condset('power',min)
					condset('minpower',min)
					condset('maxpower',min)
				else
					condset('minpower',min)
					condset('maxpower',max)
				end
			else -- power usage is simply a constant
				condset('power',p)
				condset('minpower',p)
				condset('maxpower',p)
			end
		else
			local feval = function(v)
				if type(v) == 'function' then
					return v(pos,timespan)
				else return v * timespan end
			end
			local min = feval(lookup('minpower'))
			local max = feval(lookup('maxpower'))
			condset('minpower',min)
			condset('maxpower',max)
			if min == max then condset('power',min) end
		end
	end

	if ret.power then
		if flags.query.minpower and not ret.minpower then ret.minpower = power end
		if flags.query.maxpower and not ret.maxpower then ret.maxpower = power end
	end
	return ret




end

sorcery.ley.netcaps = function(pos,timespan,exclude)
	local net = sorcery.ley.mapnet(pos)
	local maxpower = 0
	local freepower = 0
	local affs,usedaffs = {},{}
	local flexpowerdevs = {}
	local devself
	for _,n in pairs(net.devices.produce) do
		if vector.equals(pos,n.pos) then devself = n end
		if not exclude or not vector.equals(n.pos,exclude) then
			local ln = sorcery.ley.sample(n.pos,timespan,n.id)
			n.powersupply = ln.power
			n.affinity = ln.affinity
			maxpower = maxpower + ln.power
			-- production power does not vary, tho at some point it
			-- might be useful to enable some kind of power scaling
			for _,a in pairs(ln.affinity) do
				affs[a] = (affs[a] or 0) + 1
			end
		end
	end
	freepower = maxpower;
	for _,n in pairs(net.devices.consume) do
		if vector.equals(pos,n.pos) then devself = n end
		if not exclude or not vector.equals(n.pos,exclude) then
			local ln = sorcery.ley.sample(n.pos,timespan,n.id, {
				query = { power = true; minpower = true; maxpower = true; affinity = true; };
			})
			n.powerdraw = (ln.minpower <= freepower) and ln.minpower or 0
			freepower = freepower - n.powerdraw
			-- merge in sample data and return it along with the map
			n.minpower = ln.minpower
			n.maxpower = ln.maxpower
			n.affinity = ln.affinity
			if ln.maxpower > ln.minpower then
				flexpowerdevs[#flexpowerdevs+1] = n
			end
			for _,a in pairs(ln.affinity) do
				usedaffs[a] = (usedaffs[a] or 0) + 1
			end
		end
	end

	-- now we know the following: all devices; if possible, have been
	-- given the minimum amount of power they need to run. if freepower
	-- < 0 then the network is overloaded and inoperable. if freepower>0,
	-- we now need to distribute the remaining power to devices that
	-- have a variable power consumption. there's no clean way of doing
	-- this, so we use the following algorithm:
	--   1. take a list of devices that want more power
	--   2. divide the amount of free power by the number of such devices
	--      to derive the maximum power that can be allocated to any device
	--   3. iterate through the devices. increase their power consumption by
	--      the maximum term. any device that is satiated can be removed from
	--      the list.
	--   4. if there is still power remaining, repeat until there is not.

	while freepower > 0 and #flexpowerdevs > 0 do
		local nextiter = {}
		local maxgive = freepower / #flexpowerdevs
		for _,d in pairs(flexpowerdevs) do
			local give = math.min(maxgive,d.maxpower - d.powerdraw)
			freepower = freepower - give
			d.powerdraw = d.powerdraw + give
			if d.powerdraw < d.maxpower then
				nextiter[#nextiter+1] = d
			end
		end
		flexpowerdevs = nextiter
	end
	
	return {
		net = net;
		freepower = freepower;
		maxpower = maxpower;
		affinity = affs;
		affinity_balance = usedaffs;
		self = devself;
	}
end

minetest.register_on_placenode(function(pos, node)
	if minetest.get_item_group(node.name, 'sorcery_ley_device') ~= 0 then
		sorcery.ley.notify(pos)
	end
end)

local constants = {
	generator_max_energy_output = 5;
	-- how much energy a generator makes after

	generator_time_to_max_energy = 150;
	-- seconds of activity
	
	generator_power_drain_speed = 0.1;
	-- points of energy output drained per second of no fuel
}
local update_generator = function(pos)
	minetest.get_node_timer(pos):start(1)
end
local generator_update_formspec = function(pos)
	local meta = minetest.get_meta(pos)
	local burnprog = math.min(1,meta:get_float('burnleft') / meta:get_float('burntime'))
	local power = meta:get_float('power')
	local inv = meta:get_inventory()
	local lamps = ''
	for i=0,4 do 
		local color
		if power - i >= 1 then
			color = 'red'
		elseif power - i > 0 then
			color = 'yellow'
		else
			color = 'off'
		end
		lamps = lamps .. string.format([[
			image[%f,0.5;1,1;sorcery_statlamp_%s.png]
		]], 2.5 + i, color)
	end
	meta:set_string('formspec', string.format([[
		size[8,5.8]
		list[context;fuel;0.5,0.5;1,1]
		list[current_player;main;0,2;8,4]
		image[1.5,0.5;1,1;default_furnace_fire_bg.png^[lowpart:%u%%:default_furnace_fire_fg.png]
	]], math.floor(burnprog * 100)) .. lamps)
end
for _,active in pairs{true,false} do
	local id = 'sorcery:generator' .. (active and '_active' or '')
	minetest.register_node(id, {
		description = 'Generator';
		paramtype2 = 'facedir';
		groups = { cracky = 2; sorcery_ley_device = 1; };
		drop = 'sorcery:generator';
		tiles = {
			'sorcery_ley_generator_top.png';
			'sorcery_ley_generator_bottom.png';
			'sorcery_ley_generator_side.png';
			'sorcery_ley_generator_side.png';
			'sorcery_ley_generator_back.png';
			'sorcery_ley_generator_front_' .. (active and 'on' or 'off') .. '.png';
		};
		on_construct = function(pos)
			local meta = minetest.get_meta(pos)
			local inv = meta:get_inventory()
			meta:set_string('infotext','Generator')
			meta:set_float('burntime',0)
			meta:set_float('burnleft',0)
			meta:set_float('power',0)
			generator_update_formspec(pos)
			inv:set_size('fuel',1)
		end;
		after_dig_node = sorcery.lib.node.purge_container;
		on_metadata_inventory_put = update_generator;
		on_metadata_inventory_take = update_generator;
		on_timer = function(pos,delta)
			local meta = minetest.get_meta(pos)
			local inv = meta:get_inventory()
			local self = minetest.get_node(pos)
			local timeleft = meta:get_float('burnleft') - delta
			local again = false
			local power = meta:get_float('power')
			local burning = active
			if timeleft < 0 then timeleft = 0 end
			if not active or timeleft == 0 then
				if inv:is_empty('fuel') then
					-- no fuel, can't start/keep going. drain power if
					-- necessary, otherwise bail
					burning = false
					if power > 0 then
						power = math.max(0, power - constants.generator_power_drain_speed)
						again = true
					end
				else
					-- fuel is present, let's burn it
					local res,decin = minetest.get_craft_result {
						method = 'fuel';
						items = {inv:get_stack('fuel',1)};
					}
					meta:set_float('burntime',res.time)
					timeleft = res.time
					inv:set_stack('fuel',1,decin.items[1])
					again = true
					burning = true
				end
			else
				local eps = constants.generator_max_energy_output / constants.generator_time_to_max_energy
				power = math.min(constants.generator_max_energy_output, power + eps*delta)
				again = true
			end
			::stop:: meta:set_float('power',power)
			         meta:set_float('burnleft',timeleft)
					 generator_update_formspec(pos)
					 if burning and not active then
						 minetest.swap_node(pos, {
							 name = 'sorcery:generator_active';
							 param1 = self.param1, param2 = self.param2;
						 })
					 elseif active and not burning then
						 minetest.swap_node(pos, {
							 name = 'sorcery:generator';
							 param1 = self.param1, param2 = self.param2;
						 })
					 end
					 return again
		end;
		allow_metadata_inventory_put = function(pos,listname,index,stack,user)
			local res = minetest.get_craft_result {
				method = 'fuel';
				items = {stack};
			}
			if res.time ~= 0 then return stack:get_count()
				else return 0 end
		end;
		_sorcery = {
			ley = {
				mode = 'produce', affinity = {'praxic'};
				power = function(pos,delta)
					local meta = minetest.get_meta(pos)
					return meta:get_float('power') * delta;
				end;
			};
		};
	})
end