sorcery  Diff

Differences From Artifact [112e3b2a50]:

To Artifact [8532b8e5fe]:


    71     71   	output = 'sorcery:conduit 4';
    72     72   	recipe = {
    73     73   		{'default:copper_ingot', 'default:copper_ingot',  'default:copper_ingot'};
    74     74   		{'default:copper_ingot', 'sorcery:electrumblock', 'default:copper_ingot'};
    75     75   		{'default:copper_ingot', 'default:copper_ingot',  'default:copper_ingot'};
    76     76   	};
    77     77   };
    78         -minetest.register_craft {
    79         -	output = 'sorcery:wire 4';
    80         -	recipe = {
    81         -		{'', 'basic_materials:copper_wire',''};
    82         -		{'', 'sorcery:fragment_electrum',  ''};
    83         -		{'', 'basic_materials:copper_wire',''};
           78  +
           79  +local makeswitch = function(switch, desc, tex, tiles, power)
           80  +	for _,active in pairs{true,false} do
           81  +		local turn = function(pos)
           82  +			local n = minetest.get_node(pos)
           83  +			minetest.sound_play('doors_steel_door_open', {
           84  +				gain = 0.7;
           85  +				pos = pos;
           86  +			}, true)
           87  +			local leymap = active and sorcery.ley.mapnet(pos) or nil
           88  +			minetest.swap_node(pos, {
           89  +				name = active and (switch .. '_off')
           90  +							   or  switch;
           91  +				param1 = n.param1;
           92  +				param2 = n.param2;
           93  +			})
           94  +			if active then
           95  +				-- if we're turning it off, use the old map,
           96  +				-- because post-swap the network will be
           97  +				-- broken and notify won't reach everyone
           98  +				leymap.map[leymap.startpos] = nil
           99  +				sorcery.ley.notifymap(leymap.map)
          100  +			else sorcery.ley.notify(pos) end
          101  +		end
          102  +		local tl = table.copy(tiles)
          103  +		tl[6] = tex .. '^sorcery_ley_switch_panel.png^sorcery_ley_switch_' .. (active and 'down' or 'up') .. '.png';
          104  +		minetest.register_node(switch .. (active and '' or '_off'), {
          105  +			description = desc;
          106  +			drop = switch;
          107  +			tiles = tl;
          108  +			paramtype2 = 'facedir';
          109  +			groups = {
          110  +				cracky = 2; choppy = 1;
          111  +				punch_operable = 1;
          112  +				sorcery_ley_device = active and 1 or 0;
          113  +			};
          114  +			_sorcery = {
          115  +				ley = active and {
          116  +					mode = 'signal'; power = power;
          117  +				} or nil;
          118  +			};
          119  +			on_punch = function(pos,node,puncher,point)
          120  +				if puncher ~= nil then
          121  +					if puncher:get_wielded_item():is_empty() then
          122  +						turn(pos)
          123  +					end
          124  +				end
          125  +				return minetest.node_punch(pos,node,puncher,point)
          126  +			end;
          127  +			on_rightclick = turn;
          128  +		})
          129  +	end
          130  +end
          131  +
          132  +for _,b in pairs {
          133  +	{'Applewood', 'wood', 'default_wood.png'};
          134  +	{'Junglewood', 'junglewood', 'default_junglewood.png'};
          135  +	{'Pine', 'pine_wood', 'default_pine_wood.png'};
          136  +	{'Acacia', 'acacia_wood', 'default_pine_wood.png'};
          137  +	{'Aspen', 'aspen_wood', 'default_aspen_wood.png'};
          138  +	{'Stone', 'stone', 'default_stone.png'};
          139  +	{'Cobblestone', 'cobble', 'default_cobble.png'};
          140  +	{'Stone Brick', 'stonebrick', 'default_stone_brick.png'};
          141  +	{'Brick', 'brick', 'default_brick.png'};
          142  +} do
          143  +	local id = 'sorcery:conduit_half_' .. b[2]
          144  +	local switch = 'sorcery:conduit_switch_' .. b[2]
          145  +	local item = (b[4] or 'default') .. ':' .. b[2]
          146  +	local tex = b[3]
          147  +	local mod = '^[lowpart:50:'
          148  +	local sidemod = '^[transformR270^[lowpart:50:'
          149  +	local unflip = '^[transformR90'
          150  +	local tiles = {
          151  +		'sorcery_conduit_copper_top.png'..mod..tex; -- top
          152  +		tex..mod..'sorcery_conduit_copper_top.png';
          153  +		tex .. sidemod .. 'sorcery_conduit_copper_side.png' .. unflip; -- side
          154  +		'sorcery_conduit_copper_side.png' .. sidemod .. tex .. unflip; -- side
          155  +		'sorcery_conduit_copper_side.png'; -- back
          156  +		tex; -- front
          157  +	}
          158  +	minetest.register_node(id, {
          159  +		description = 'Half-' .. b[1] .. ' Conduit';
          160  +		paramtype2 = 'facedir';
          161  +		groups = {
          162  +			cracky = 2;
          163  +			choppy = 1;
          164  +			sorcery_ley_device = 1;
          165  +			sorcery_ley_conduit = 1;
          166  +		};
          167  +		_sorcery = {
          168  +			ley = { mode = 'signal'; power = 5; }
          169  +		};
          170  +		tiles = tiles;
          171  +	})
          172  +	minetest.register_craft {
          173  +		output = id .. ' 4';
          174  +		recipe = {
          175  +			{item, 'sorcery:conduit'};
          176  +			{item, 'sorcery:conduit'};
          177  +		};
          178  +	};
          179  +	makeswitch(switch, b[1] .. " Conduit Switch", tex, tiles, 5)
          180  +	minetest.register_craft {
          181  +		output = switch;
          182  +		recipe = {
          183  +			{'xdecor:lever_off',id};
          184  +		};
    84    185   	}
    85         -};
          186  +end
          187  +makeswitch('sorcery:conduit_switch', "Conduit Switch", 'sorcery_conduit_copper_side.png', {
          188  +	'sorcery_conduit_copper_top.png';
          189  +	'sorcery_conduit_copper_top.png';
          190  +	'sorcery_conduit_copper_side.png';
          191  +	'sorcery_conduit_copper_side.png';
          192  +	'sorcery_conduit_copper_side.png';
          193  +	'sorcery_conduit_copper_side.png';
          194  +}, 10)
          195  +minetest.register_craft {
          196  +	output = 'sorcery:conduit_switch';
          197  +	recipe = {
          198  +		{'xdecor:lever_off','sorcery:conduit'};
          199  +	};
          200  +}
          201  +
          202  +for name,metal in pairs(sorcery.data.metals) do
          203  +	if metal.conduct then
          204  +		local cable = 'sorcery:cable_' .. name
          205  +		minetest.register_node(cable, {
          206  +			description = sorcery.lib.str.capitalize(name) .. " Cable";
          207  +			drawtype = 'nodebox';
          208  +			groups = {
          209  +				sorcery_ley_device = 1; snappy = 3; attached = 1;
          210  +				sorcery_ley_cable = 1;
          211  +			};
          212  +			_sorcery = {
          213  +				ley = { mode = 'signal', power = metal.conduct };
          214  +			};
          215  +			sunlight_propagates = true;
          216  +			node_box = {
          217  +				type = 'connected';
          218  +				disconnected   = { -0.05, -0.35, -0.40; 0.05, -0.25, 0.40 };
          219  +				connect_front  = { -0.05, -0.35, -0.50; 0.05, -0.25, 0.05 };
          220  +				connect_back   = { -0.05, -0.35, -0.05; 0.05, -0.25, 0.50 };
          221  +				connect_right  = { -0.05, -0.35, -0.05; 0.50, -0.25, 0.05 };
          222  +				connect_left   = { -0.50, -0.35, -0.05; 0.05, -0.25, 0.05 };
          223  +				connect_top    = { -0.05, -0.25, -0.05; 0.05,  0.50, 0.05 };
          224  +				connect_bottom = { -0.05, -0.50, -0.05; 0.05, -0.35, 0.05 };
          225  +			};
          226  +			connects_to = { 'group:sorcery_ley_device', 'default:mese' };
          227  +			-- harcoding mese is kind of cheating -- figure out a
          228  +			-- better way to do this for the longterm
          229  +			paramtype = 'light';
          230  +			-- paramtype2 = 'facedir';
          231  +			after_place_node = function(pos, placer, stack, point)
          232  +				local vec = vector.subtract(point.under, pos)
          233  +				local n = minetest.get_node(pos)
          234  +				n.param2 = minetest.dir_to_facedir(vec)
          235  +				minetest.swap_node(pos,n)
          236  +			end;
          237  +			tiles = { 'sorcery_ley_plug.png' };
          238  +		})
          239  +
          240  +		minetest.register_craft {
          241  +			output = cable .. ' 8';
          242  +			recipe = {
          243  +				{'basic_materials:copper_wire','basic_materials:copper_wire','basic_materials:copper_wire'};
          244  +				{ metal.parts.fragment, metal.parts.fragment, metal.parts.fragment };
          245  +				{'basic_materials:copper_wire','basic_materials:copper_wire','basic_materials:copper_wire'};
          246  +			};
          247  +			replacements = {
          248  +				{'basic_materials:copper_wire', 'basic_materials:empty_spool'};
          249  +				{'basic_materials:copper_wire', 'basic_materials:empty_spool'};
          250  +				{'basic_materials:copper_wire', 'basic_materials:empty_spool'};
          251  +				{'basic_materials:copper_wire', 'basic_materials:empty_spool'};
          252  +				{'basic_materials:copper_wire', 'basic_materials:empty_spool'};
          253  +				{'basic_materials:copper_wire', 'basic_materials:empty_spool'};
          254  +			};
          255  +		};
          256  +	end
          257  +end
          258  +
          259  +-- ley.notify will normally be called automatically, but if a
          260  +-- ley-producer or consume has fluctuating levels of energy
          261  +-- consumption, it should call this function when levels change
          262  +sorcery.ley.notifymap = function(map)
          263  +	for pos,name in pairs(map) do
          264  +		local props = minetest.registered_nodes[name]._sorcery
          265  +		if props and props.on_leychange then
          266  +			props.on_leychange(pos)
          267  +		end
          268  +	end
          269  +end
          270  +sorcery.ley.notify = function(pos)
          271  +	local n = sorcery.ley.mapnet(pos)
          272  +	sorcery.ley.notifymap(n.map)
          273  +end
    86    274   
    87    275   sorcery.ley.field_to_current = function(strength,time)
    88    276   	local ley_factor = 0.25
    89    277   	-- a ley harvester will produce this much current with
    90    278   	-- access to a full-strength leyline
    91    279   	
    92    280   	return strength * ley_factor * time;
................................................................................
   102    290   			-0.5, -0.5, -0.5;
   103    291   			 0.5,  1.2,  0.5;
   104    292   		};
   105    293   	};
   106    294   	minetest.register_node('sorcery:condenser', {
   107    295   		description = 'Condenser';
   108    296   		drawtype = 'mesh';
          297  +		paramtype2 = 'facedir';
   109    298   		mesh = 'sorcery-condenser.obj';
   110    299   		selection_box = box;
   111    300   		collision_box = box;
   112    301   		tiles = {
   113    302   			amethyst:render();
   114    303   			'sorcery_condenser.png';
   115    304   			'default_tin_block.png';
................................................................................
   122    311   			sorcery_ley_device = 1;
   123    312   		};
   124    313   		on_construct = function(pos)
   125    314   			local meta = minetest.get_meta(pos)
   126    315   			meta:set_string('infotext','Condenser')
   127    316   		end;
   128    317   		_sorcery = {
   129         -			ley = { mode = 'produce' };
   130         -			on_leycalc = function(pos,time)
   131         -				local l = sorcery.ley.estimate(pos)
   132         -				return {
   133         -					power = sorcery.ley.field_to_current(l.force, time);
   134         -					affinity = l.aff;
   135         -				}
   136         -			end;
          318  +			ley = { mode = 'produce';
          319  +				power = function(pos,time)
          320  +					return sorcery.ley.field_to_current(sorcery.ley.estimate(pos).force, time);
          321  +				end;
          322  +				affinity = function(pos)
          323  +					return sorcery.ley.estimate(pos).aff
          324  +				end;
          325  +			};
   137    326   		};
   138    327   	})
   139    328   end
   140    329   
   141    330   minetest.register_craft {
   142    331   	output = 'sorcery:condenser';
   143    332   	recipe = {
   144    333   		{'sorcery:accumulator'};
   145    334   		{'sorcery:conduit'};
   146    335   	};
   147    336   }
          337  +sorcery.ley.txofs = {
          338  +	{x =  0, z =  0, y =  0};
          339  +	{x = -1, z =  0, y =  0};
          340  +	{x =  1, z =  0, y =  0};
          341  +	{x =  0, z = -1, y =  0};
          342  +	{x =  0, z =  1, y =  0};
          343  +	{x =  0, z =  0, y = -1};
          344  +	{x =  0, z =  0, y =  1};
          345  +}
   148    346   sorcery.ley.mapnet = function(startpos,power)
   149    347   	-- this function returns a list of all the nodes accessible from
   150    348   	-- a ley network and their associated positions
   151         -	local net = {}
          349  +	local net,checked = {},{}
   152    350   	power = power or 0
   153    351   	
   154    352   	local devices = {
   155    353   		consume = {};
   156    354   		produce = {};
   157    355   		signal = {};
   158    356   	}
   159    357   	local numfound = 0
   160    358   	local maxconduct = 0
   161    359   	local minconduct
          360  +	local startkey
   162    361   	local foundp = function(p)
   163         -		for k in pairs(net) do
          362  +		for _,k in pairs(checked) do
   164    363   			if vector.equals(p,k) then return true end
   165    364   		end
   166    365   		return false
   167    366   	end
   168    367   	-- we're implementing this with a recursive function to start with
   169    368   	-- but this could rapidly lead to stack overflows so we should
   170    369   	-- replace it with a linear one at some point
   171    370   	local function find(positions)
   172    371   		local searchnext = {}
   173    372   		for _,pos in pairs(positions) do
   174         -			for _,p in pairs {
   175         -				{x =  0, z =  0, y =  0};
   176         -				{x = -1, z =  0, y =  0};
   177         -				{x =  1, z =  0, y =  0};
   178         -				{x =  0, z = -1, y =  0};
   179         -				{x =  0, z =  1, y =  0};
   180         -				{x =  0, z =  0, y = -1};
   181         -				{x =  0, z =  0, y =  1};
   182         -			} do local sum = vector.add(pos,p)
          373  +			for _,p in pairs(sorcery.ley.txofs) do
          374  +				local sum = vector.add(pos,p)
   183    375   				if not foundp(sum) then
          376  +					checked[#checked + 1] = sum
   184    377   					local nodename = minetest.get_node(sum).name
          378  +					if nodename == 'ignore' then
          379  +						minetest.load_area(sum)
          380  +						nodename = minetest.get_node(sum).name
          381  +					end
   185    382   					if minetest.get_item_group(nodename,'sorcery_ley_device') ~= 0
   186    383   					   or sorcery.data.compat.ley[nodename] then
   187         -						local d = sorcery.ley.sample(pos,1,nodename)
          384  +						local d = sorcery.ley.sample(pos,1,nodename,{query={mode=true}})
   188    385   						assert(d.mode == 'signal'
   189    386   						    or d.mode == 'consume'
   190    387   						    or d.mode == 'produce')
   191    388   						devices[d.mode][#(devices[d.mode]) + 1] = {
   192    389   							id = nodename; pos = sum;
   193    390   						}
   194    391   						if d.mode == 'signal' then
          392  +							d.power = sorcery.ley.sample(pos,1,nodename,{query={power=true}}).power
   195    393   							if d.power > power then
   196    394   								if minconduct then
   197    395   									if d.power < minconduct then
   198    396   										minconduct = d.power
   199    397   									end
   200    398   								else minconduct = d.power end
   201    399   								if d.power > maxconduct then
   202    400   									maxconduct = d.power
   203    401   								end
   204    402   							end
   205    403   						end
   206    404   						numfound = numfound + 1;
   207    405   						net[sum] = nodename;
          406  +						if not startkey then
          407  +							if vector.equals(startpos,sum) then
          408  +								startkey = sum
          409  +							end
          410  +						end
   208    411   						searchnext[#searchnext + 1] = sum;
   209    412   					end
   210    413   				end
   211    414   			end
   212    415   		end
   213    416   		if #searchnext > 0 then find(searchnext) end
   214    417   	end
................................................................................
   216    419   	find{startpos}
   217    420   
   218    421   	if numfound > 0 then
   219    422   		return {
   220    423   			count = numfound;
   221    424   			map = net;
   222    425   			devices = devices;
          426  +			startpos = startkey;
   223    427   			conduct = {
   224    428   				min = minconduct;
   225    429   				max = maxconduct;
   226    430   			};
   227    431   		}
   228    432   	else return nil end
   229    433   end
................................................................................
   241    445   		[3] = 'signal';
   242    446   	}
   243    447   	for i=1,#afftbl  do afftbl [afftbl [i]] = i end
   244    448   	for i=1,#modetbl do modetbl[modetbl[i]] = i end
   245    449   	local m = sorcery.lib.marshal
   246    450   	local enc, dec = m.transcoder {
   247    451   		mode = m.t.u8;
   248         -		power = m.t.u32; -- power generated/consumed * 10,000
          452  +		minpower = m.t.u32; -- min power generated/consumed * 10,000
          453  +		maxpower = m.t.u32; -- max power generated/consumed * 10,000
   249    454   		affinity = m.g.array(m.t.u8); -- indexes into afftbl
   250    455   	}
   251    456   	sorcery.ley.encode = function(l)
   252    457   		local idxs = {}
   253    458   		for _,k in pairs(l.affinity) do
   254    459   			idxs[#idxs+1] = afftbl[k]
   255    460   		end
   256    461   		return meta_armor(enc {
   257    462   			mode = modetbl[l.mode];
   258         -			power = l.power * 10000;
          463  +			minpower = l.minpower * 10000;
          464  +			maxpower = l.maxpower * 10000;
   259    465   			affinity = idxs;
   260    466   		}, true)
   261    467   	end
   262    468   	sorcery.ley.decode = function(str)
   263    469   		local obj = dec(meta_dearmor(str,true))
   264    470   		local affs = {}
   265    471   		for _,k in pairs(obj.affinity) do
   266    472   			affs[#affs+1] = afftbl[k]
   267    473   		end
   268    474   		return {
   269    475   			mode = modetbl[obj.mode];
   270         -			power = obj.power / 10000.0;
          476  +			minpower = obj.minpower / 10000.0;
          477  +			maxpower = obj.maxpower / 10000.0;
          478  +			power = (obj.minpower == obj.maxpower) and obj.minpower or nil;
   271    479   			affinity = affs;
   272    480   		}
   273    481   	end
   274    482   end
   275    483   sorcery.ley.setnode = function(pos,l)
   276    484   	local meta = minetest.get_node(pos)
   277    485   	meta:set_string('sorcery:ley',sorcery.ley.encode(l))
   278    486   end
   279    487   
   280         -sorcery.ley.sample = function(pos,timespan,name)
          488  +sorcery.ley.sample = function(pos,timespan,name,flags)
   281    489   	-- returns how much ley-force can be transmitted by a
   282    490   	-- device over timespan
          491  +	local ret = {}
   283    492   	name = name or minetest.get_node(pos).name
          493  +	flags = flags or {}
          494  +	flags.query = flags.query or {
          495  +		mode = true; power = true; affinity = true;
          496  +		minpower = true; maxpower = true;
          497  +	}
   284    498   	local props = minetest.registered_nodes[name]._sorcery
   285         -	local callback = props and props.on_leycalc or nil
   286         -	local p,a,m
   287         -	if callback then
   288         -		local gen = callback(pos,timespan)
   289         -		p = gen.power
   290         -		a = gen.affinity
   291         -		m = gen.mode
          499  +
          500  +	local evaluate = function(v)
          501  +		if type(v) == 'function' then
          502  +			return v(pos)
          503  +		else return v end
   292    504   	end
   293    505   
   294         -	if not (p and a and m) then
          506  +	local leymeta do
   295    507   		local nm = minetest.get_meta(pos)
   296    508   		if nm:contains('sorcery:ley') then
   297         -			local l = sorcery.ley.decode(nm:get_string('sorcery:ley'))
   298         -			p = p or sorcery.ley.field_to_current(l.power,timespan)
   299         -			a = a or l.affinity
   300         -			m = m or l.mode
          509  +			leymeta = sorcery.ley.decode(nm:get_string('sorcery:ley'))
          510  +		end
          511  +	end
          512  +
          513  +	local compat = sorcery.data.compat.ley[name] 
          514  +
          515  +	local lookup = function(k,default)
          516  +		if leymeta and leymeta[k] then return leymeta[k]
          517  +		elseif props and props.ley and props.ley[k] then return props.ley[k]
          518  +		elseif compat and compat[k] then return compat[k]
          519  +		else return default end 
          520  +	end
          521  +	if flags.query.mode     then ret.mode     = evaluate(lookup('mode','none')) end
          522  +	if flags.query.affinity then ret.affinity = evaluate(lookup('affinity',{})) end
          523  +	if flags.query.minpower or flags.query.maxpower or flags.query.power then
          524  +		local condset = function(name,var)
          525  +			if flags.query[name] then ret[name] = var end
          526  +		end
          527  +		local p = lookup('power')
          528  +		if p then
          529  +			if type(p) == 'function' then
          530  +			-- we have a single function to calculate power usage; we need to
          531  +			-- check whether it returns min,max or a constant
          532  +				local min, max = p(pos,timespan)
          533  +				if (not max) or min == max then
          534  +					ret.power = min
          535  +					condset('power',min)
          536  +					condset('minpower',min)
          537  +					condset('maxpower',min)
          538  +				else
          539  +					condset('minpower',min)
          540  +					condset('maxpower',max)
          541  +				end
          542  +			else -- power usage is simply a constant
          543  +				condset('power',p)
          544  +				condset('minpower',p)
          545  +				condset('maxpower',p)
          546  +			end
          547  +		else
          548  +			local feval = function(v)
          549  +				if type(v) == 'function' then
          550  +					return v(pos,timespan)
          551  +				else return v * timespan end
          552  +			end
          553  +			local min = feval(lookup('minpower'))
          554  +			local max = feval(lookup('maxpower'))
          555  +			condset('minpower',min)
          556  +			condset('maxpower',max)
          557  +			if min == max then condset('power',min) end
   301    558   		end
   302    559   	end
   303    560   
   304         -	if (not (p and a and m)) and props and props.ley then
   305         -		p = p or sorcery.ley.field_to_current(props.ley.power,timespan)
   306         -		a = a or props.ley.affinity
   307         -		m = m or props.ley.mode
          561  +	if ret.power then
          562  +		if flags.query.minpower and not ret.minpower then ret.minpower = power end
          563  +		if flags.query.maxpower and not ret.maxpower then ret.maxpower = power end
   308    564   	end
   309         -
   310         -	if (not (p and a and m)) then
   311         -		local compat = sorcery.data.compat.ley[name] 
   312         -		if compat then
   313         -			p = p or sorcery.ley.field_to_current(compat.power,timespan)
   314         -			a = a or compat.affinity
   315         -			m = m or compat.mode
   316         -		end
   317         -	end
   318         -
   319         -	return {
   320         -		power = p or 0;
   321         -		mode = m or 'none';
   322         -		affinity = a or {};
   323         -	}
          565  +	return ret
   324    566   end
   325    567   
   326    568   sorcery.ley.netcaps = function(pos,timespan,exclude)
   327    569   	local net = sorcery.ley.mapnet(pos)
   328    570   	local maxpower = 0
   329    571   	local freepower = 0
   330    572   	local affs,usedaffs = {},{}
          573  +	local flexpowerdevs = {}
          574  +	local devself
   331    575   	for _,n in pairs(net.devices.produce) do
          576  +		if vector.equals(pos,n.pos) then devself = n end
   332    577   		if not exclude or not vector.equals(n.pos,exclude) then
   333    578   			local ln = sorcery.ley.sample(n.pos,timespan,n.id)
          579  +			n.powersupply = ln.power
          580  +			n.affinity = ln.affinity
   334    581   			maxpower = maxpower + ln.power
          582  +			-- production power does not vary, tho at some point it
          583  +			-- might be useful to enable some kind of power scaling
   335    584   			for _,a in pairs(ln.affinity) do
   336    585   				affs[a] = (affs[a] or 0) + 1
   337    586   			end
   338    587   		end
   339    588   	end
   340    589   	freepower = maxpower;
   341    590   	for _,n in pairs(net.devices.consume) do
          591  +		if vector.equals(pos,n.pos) then devself = n end
   342    592   		if not exclude or not vector.equals(n.pos,exclude) then
   343         -			local ln = sorcery.ley.sample(n.pos,timespan,n.id)
   344         -			freepower = freepower - ln.power
          593  +			local ln = sorcery.ley.sample(n.pos,timespan,n.id, {
          594  +				query = { power = true; minpower = true; maxpower = true; affinity = true; };
          595  +			})
          596  +			n.powerdraw = (ln.minpower <= freepower) and ln.minpower or 0
          597  +			freepower = freepower - n.powerdraw
          598  +			-- merge in sample data and return it along with the map
          599  +			n.minpower = ln.minpower
          600  +			n.maxpower = ln.maxpower
          601  +			n.affinity = ln.affinity
          602  +			if ln.maxpower > ln.minpower then
          603  +				flexpowerdevs[#flexpowerdevs+1] = n
          604  +			end
   345    605   			for _,a in pairs(ln.affinity) do
   346    606   				usedaffs[a] = (usedaffs[a] or 0) + 1
   347    607   			end
   348    608   		end
   349    609   	end
          610  +
          611  +	-- now we know the following: all devices; if possible, have been
          612  +	-- given the minimum amount of power they need to run. if freepower
          613  +	-- < 0 then the network is overloaded and inoperable. if freepower>0,
          614  +	-- we now need to distribute the remaining power to devices that
          615  +	-- have a variable power consumption. there's no clean way of doing
          616  +	-- this, so we use the following algorithm:
          617  +	--   1. take a list of devices that want more power
          618  +	--   2. divide the amount of free power by the number of such devices
          619  +	--      to derive the maximum power that can be allocated to any device
          620  +	--   3. iterate through the devices. increase their power consumption by
          621  +	--      the maximum term. any device that is satiated can be removed from
          622  +	--      the list.
          623  +	--   4. if there is still power remaining, repeat until there is not.
          624  +
          625  +	while freepower > 0 and #flexpowerdevs > 0 do
          626  +		local nextiter = {}
          627  +		local maxgive = freepower / #flexpowerdevs
          628  +		for _,d in pairs(flexpowerdevs) do
          629  +			local give = math.min(maxgive,d.maxpower - d.powerdraw)
          630  +			freepower = freepower - give
          631  +			d.powerdraw = d.powerdraw + give
          632  +			if d.powerdraw < d.maxpower then
          633  +				nextiter[#nextiter+1] = d
          634  +			end
          635  +		end
          636  +		flexpowerdevs = nextiter
          637  +	end
   350    638   	
   351    639   	return {
   352    640   		net = net;
   353    641   		freepower = freepower;
   354    642   		maxpower = maxpower;
   355    643   		affinity = affs;
   356    644   		affinity_balance = usedaffs;
          645  +		self = devself;
   357    646   	}
   358    647   end
          648  +
          649  +minetest.register_on_placenode(function(pos, node)
          650  +	if minetest.get_item_group(node.name, 'sorcery_ley_device') ~= 0 then
          651  +		sorcery.ley.notify(pos)
          652  +	end
          653  +end)
          654  +
          655  +local constants = {
          656  +	generator_max_energy_output = 5;
          657  +	-- how much energy a generator makes after
          658  +
          659  +	generator_time_to_max_energy = 150;
          660  +	-- seconds of activity
          661  +	
          662  +	generator_power_drain_speed = 0.1;
          663  +	-- points of energy output drained per second of no fuel
          664  +}
          665  +local update_generator = function(pos)
          666  +	minetest.get_node_timer(pos):start(1)
          667  +end
          668  +local generator_update_formspec = function(pos)
          669  +	local meta = minetest.get_meta(pos)
          670  +	local burnprog = math.min(1,meta:get_float('burnleft') / meta:get_float('burntime'))
          671  +	local power = meta:get_float('power')
          672  +	local inv = meta:get_inventory()
          673  +	local lamps = ''
          674  +	for i=0,4 do 
          675  +		local color
          676  +		if power - i >= 1 then
          677  +			color = 'red'
          678  +		elseif power - i > 0 then
          679  +			color = 'yellow'
          680  +		else
          681  +			color = 'off'
          682  +		end
          683  +		lamps = lamps .. string.format([[
          684  +			image[%f,0.5;1,1;sorcery_statlamp_%s.png]
          685  +		]], 2.5 + i, color)
          686  +	end
          687  +	meta:set_string('formspec', string.format([[
          688  +		size[8,5.8]
          689  +		list[context;fuel;0.5,0.5;1,1]
          690  +		list[current_player;main;0,2;8,4]
          691  +		image[1.5,0.5;1,1;default_furnace_fire_bg.png^[lowpart:%u%%:default_furnace_fire_fg.png]
          692  +	]], math.floor(burnprog * 100)) .. lamps)
          693  +end
          694  +for _,active in pairs{true,false} do
          695  +	local id = 'sorcery:generator' .. (active and '_active' or '')
          696  +	minetest.register_node(id, {
          697  +		description = 'Generator';
          698  +		paramtype2 = 'facedir';
          699  +		groups = { cracky = 2; sorcery_ley_device = 1; };
          700  +		drop = 'sorcery:generator';
          701  +		tiles = {
          702  +			'sorcery_ley_generator_top.png';
          703  +			'sorcery_ley_generator_bottom.png';
          704  +			'sorcery_ley_generator_side.png';
          705  +			'sorcery_ley_generator_side.png';
          706  +			'sorcery_ley_generator_back.png';
          707  +			'sorcery_ley_generator_front_' .. (active and 'on' or 'off') .. '.png';
          708  +		};
          709  +		on_construct = function(pos)
          710  +			local meta = minetest.get_meta(pos)
          711  +			local inv = meta:get_inventory()
          712  +			meta:set_string('infotext','Generator')
          713  +			meta:set_float('burntime',0)
          714  +			meta:set_float('burnleft',0)
          715  +			meta:set_float('power',0)
          716  +			generator_update_formspec(pos)
          717  +			inv:set_size('fuel',1)
          718  +		end;
          719  +		after_dig_node = sorcery.lib.node.purge_container;
          720  +		on_metadata_inventory_put = update_generator;
          721  +		on_metadata_inventory_take = update_generator;
          722  +		on_timer = function(pos,delta)
          723  +			local meta = minetest.get_meta(pos)
          724  +			local inv = meta:get_inventory()
          725  +			local self = minetest.get_node(pos)
          726  +			local timeleft = meta:get_float('burnleft') - delta
          727  +			local again = false
          728  +			local power = meta:get_float('power')
          729  +			local burning = active
          730  +			if timeleft < 0 then timeleft = 0 end
          731  +			if not active or timeleft == 0 then
          732  +				if inv:is_empty('fuel') then
          733  +					-- no fuel, can't start/keep going. drain power if
          734  +					-- necessary, otherwise bail
          735  +					burning = false
          736  +					if power > 0 then
          737  +						power = math.max(0, power - constants.generator_power_drain_speed)
          738  +						again = true
          739  +					end
          740  +				else
          741  +					-- fuel is present, let's burn it
          742  +					local res,decin = minetest.get_craft_result {
          743  +						method = 'fuel';
          744  +						items = {inv:get_stack('fuel',1)};
          745  +					}
          746  +					meta:set_float('burntime',res.time)
          747  +					timeleft = res.time
          748  +					inv:set_stack('fuel',1,decin.items[1])
          749  +					again = true
          750  +					burning = true
          751  +				end
          752  +			else
          753  +				local eps = constants.generator_max_energy_output / constants.generator_time_to_max_energy
          754  +				power = math.min(constants.generator_max_energy_output, power + eps*delta)
          755  +				again = true
          756  +			end
          757  +			::stop:: meta:set_float('power',power)
          758  +			         meta:set_float('burnleft',timeleft)
          759  +					 generator_update_formspec(pos)
          760  +					 if burning and not active then
          761  +						 minetest.swap_node(pos, {
          762  +							 name = 'sorcery:generator_active';
          763  +							 param1 = self.param1, param2 = self.param2;
          764  +						 })
          765  +					 elseif active and not burning then
          766  +						 minetest.swap_node(pos, {
          767  +							 name = 'sorcery:generator';
          768  +							 param1 = self.param1, param2 = self.param2;
          769  +						 })
          770  +					 end
          771  +					 return again
          772  +		end;
          773  +		allow_metadata_inventory_put = function(pos,listname,index,stack,user)
          774  +			local res = minetest.get_craft_result {
          775  +				method = 'fuel';
          776  +				items = {stack};
          777  +			}
          778  +			if res.time ~= 0 then return stack:get_count()
          779  +				else return 0 end
          780  +		end;
          781  +		_sorcery = {
          782  +			ley = {
          783  +				mode = 'produce', affinity = {'praxic'};
          784  +				power = function(pos,delta)
          785  +					local meta = minetest.get_meta(pos)
          786  +					return meta:get_float('power') * delta;
          787  +				end;
          788  +			};
          789  +		};
          790  +	})
          791  +end