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     36   	})
    37     37   	minetest.register_alias('new_campfire:ash', 'sorcery:ash')
    38     38   end
    39     39   
    40     40   return {
    41     41   	defp = function(name)
    42     42   		return minetest.registered_items[name] or minetest.registered_aliases[name]
    43         -	end
           43  +	end;
    44     44   }

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

     5      5   
     6      6   sorcery.cookbook = {}
     7      7   local constants = {
     8      8   	-- do not show recipes for items in these groups
     9      9   	exclude_groups = {
    10     10   	};
    11     11   	exclude_names = {
    12         -		'_stairs';
    13         -		'_slab';
    14         -		'slope_';
           12  +		'stairs';
           13  +		'slab';
           14  +		'slope';
    15     15   	};
    16     16   	-- do not show recipes from this namespace
    17     17   	blacklist_mods = {
    18     18   		'group'; -- WHY IS THIS NECESSARY
    19     19   		'moreblocks'; -- too much noise
    20     20   	};
    21     21   
    22     22   	recipes_per_cookbook_page = 3;
           23  +
           24  +	group_ids = {
           25  +		wood   = { caption = 'Any Wood',   cnitem = 'default:wood'   };
           26  +		tree   = { caption = 'Any Tree',   cnitem = 'default:tree'   };
           27  +		leaves = { caption = 'Any Leaves', cnitem = 'default:leaves' };
           28  +		stone  = { caption = 'Any Stone',  cnitem = 'default:stone'  };
           29  +		dye    = { caption = 'Any Dye',    cnitem = 'dye:black'      };
           30  +		bone   = { caption = 'Any Bone',   cnitem = 'bonemeal:bone'  };
           31  +		vessel = { caption = 'Any Bottle', cnitem = 'vessels:glass_bottle' };
           32  +		flower = { caption = 'Any Flower', cnitem = 'flowers:rose' };
           33  +		mushroom = { caption = 'Any Mushroom', cnitem = 'flowers:mushroom_brown' };
           34  +		water_bucket = { caption = 'Water Bucket', cnitem = 'bucket:bucket_water' };
           35  +		sorcery_ley_cable = { caption = 'Cable', cnitem = 'sorcery:cable_vidrium' };
           36  +	};
    23     37   }
    24     38   
    25     39   local slot3x3 = {
    26     40   	{0,0}, {1,0}, {2,0};
    27     41   	{0,1}, {1,1}, {2,1};
    28     42   	{0,2}, {1,2}, {2,2};
    29     43   }
................................................................................
    43     57   	-- ow ow ow ow ow ow ow
    44     58   	local names = {}
    45     59   	for k in pairs(minetest.registered_items) do
    46     60   		local rec = minetest.get_craft_recipe(k)
    47     61   		if rec.items ~= nil and (rec.method == kind or (rec.method == 'shapeless' and kind == 'normal')) then -- is this last bit necessary?
    48     62   			local excluded = false
    49     63   			for _,n in pairs(constants.exclude_names) do
    50         -				if string.find(p,n) then
           64  +				if string.find(k,n) ~= nil then
    51     65   					excluded = true break end
    52     66   			end
    53     67   			if not excluded then for _,g in pairs(constants.exclude_groups) do
    54         -				if minetest.get_item_group(p, g) > 0 then
           68  +				if minetest.get_item_group(k, g) > 0 then
    55     69   					excluded = true break end
    56     70   			end end
    57     71   			local props = minetest.registered_items[k]._sorcery
    58     72   			local module = modofname(k)
    59     73   			if not (excluded
    60     74   				or sorcery.lib.tbl.has(constants.blacklist_mods,module)
    61     75   				or (props and props.recipe and props.recipe.secret)
................................................................................
    77     91   		local col = (j-1) % w
    78     92   		if i.items[j] then
    79     93   			rec[1 + (row * 3) + col] = i.items[j]
    80     94   		end
    81     95   	end
    82     96   	return rec
    83     97   end
    84         -local desc_builtin = function(i)
    85         -	local desc = minetest.registered_items[i].description
           98  +local function group_eval(i)
           99  +	if string.sub(i,1,6) == 'group:' then
          100  +		local g = string.sub(i,7)
          101  +		if constants.group_ids[g] then
          102  +			return constants.group_ids[g].cnitem,
          103  +			       constants.group_ids[g].caption
          104  +		end
          105  +		for i,v in pairs(minetest.registered_items) do
          106  +			if minetest.get_item_group(i, g) > 0 then
          107  +				return i, v.description
          108  +			end
          109  +		end
          110  +		return i
          111  +	end
          112  +	return i
          113  +end
          114  +local function desc_builtin(i)
          115  +	local desc
          116  +	i, desc = group_eval(i)
          117  +	-- print('describing ',i,dump(minetest.registered_items[i]))
          118  +	if not minetest.registered_items[i] then
          119  +		minetest.log('WARNING: unknown item in recipe ' .. i)
          120  +		return 'Unknown Item'
          121  +	end
          122  +	if not desc then desc = minetest.registered_items[i].description end
    86    123   	if not desc then return 'Peculiar Item' end
    87    124   
    88    125   	local eol = string.find(desc,'\n')
    89    126   	if not eol then return desc else return string.sub(desc,1,eol-1) end
    90    127   end;
    91    128   
    92    129   local bookadjs = { -- sets are in reverse order!
................................................................................
   264    301   		local x, y = k.slots[i][1], k.slots[i][2]
   265    302   		if ingredients[i] and ingredients[i] ~= '' then
   266    303   			local tt
   267    304   			if k.indesc then tt = k.indesc(ingredients[i]) else tt = desc_builtin(ingredients[i]) end
   268    305   			t = t .. string.format([[
   269    306   				item_image[%f,%f;1,1;%s]
   270    307   				tooltip[%f,%f;1,1;%s]
   271         -			]], x,y, minetest.formspec_escape(ingredients[i]),
          308  +			]], x,y, minetest.formspec_escape(group_eval(ingredients[i])),
   272    309   			    x,y, minetest.formspec_escape(tt))
   273    310   		else
   274    311   			if k.drawslots == nil or k.drawslots then
   275    312   				t = string.format('box[%f,%f;0.1,0.1;#00000060]',x+0.45,y+0.45) .. t
   276    313   			end
   277    314   		end
   278    315   	end
................................................................................
   336    373   
   337    374   dungeon_loot.register {
   338    375   	name = 'sorcery:recipe';
   339    376   	chance = 0.9;
   340    377   	count = {1,7};
   341    378   }
   342    379   
   343         -minetest.register_craft { type = 'fuel', recipe = 'sorcery:recipe', burntime = 3 }
   344         -minetest.register_craft {
   345         -	type = 'cooking';
   346         -	recipe = 'sorcery:recipe';
   347         -	output = 'sorcery:ash';
   348         -	cooktime = 3;
   349         -}
   350         -
   351    380   default.register_craft_metadata_copy('default:paper','sorcery:recipe')
          381  +-- this seems bugged; it doesn't like it when its item shows up in another
          382  +-- recipe. so we'll do it manually :/
   352    383   -- default.register_craft_metadata_copy('default:book','sorcery:cookbook')
   353    384   
   354    385   for i=1,8 do
   355    386   	local rcp = {}
   356    387   	for i=1,i do rcp[i] = 'sorcery:recipe' end
   357    388   	rcp[#rcp+1]='default:book' minetest.register_craft {
   358    389   		type = 'shapeless', recipe = rcp, output = 'sorcery:cookbook';
   359    390   	}
   360    391   	rcp[#rcp]='sorcery:cookbook' minetest.register_craft {
   361    392   		type = 'shapeless', recipe = rcp, output = 'sorcery:cookbook';
   362    393   	}
   363    394   end
   364    395   
          396  +minetest.register_craft {
          397  +	type = 'shapeless';
          398  +	recipe = {
          399  +		'sorcery:cookbook';
          400  +		'default:book';
          401  +	};
          402  +	output = 'sorcery:cookbook';
          403  +};
   365    404   
   366    405   local m = sorcery.lib.marshal
   367    406   local encbook, decbook = m.transcoder {
   368    407   	pages = m.g.array(8, m.g.struct {
   369    408   		kind = m.t.str;
   370    409   		name = m.t.str;
   371    410   	})
................................................................................
   459    498   	end
   460    499   
   461    500   	uinv:set_stack('main',idx,stack)
   462    501   	bookform(stack,user)
   463    502   end)
   464    503   
   465    504   minetest.register_on_craft(function(stack,player,grid,inv)
          505  +	-- god this is messy. i'm sorry. minetest made me do it
   466    506   	if stack:get_name() ~= 'sorcery:cookbook' then return nil end
   467    507   
   468    508   	local oldbook
   469    509   	local topic, onetopic = nil, true
   470    510   	local recipes = {}
   471         -	for _,s in pairs(grid) do
          511  +	local copybook = false
          512  +	local obindex
          513  +	for i,s in pairs(grid) do
   472    514   		if s:get_name() == 'sorcery:recipe' then
   473    515   			recipes[#recipes+1] = s
   474         -		elseif s:get_name() == 'sorcery:cookbook' then oldbook = s end
          516  +		elseif s:get_name() == 'default:book' then copybook = true
          517  +		elseif s:get_name() == 'sorcery:cookbook' then oldbook = s obindex = i end
          518  +	end
          519  +
          520  +	if #recipes == 0 and copybook and oldbook then
          521  +		inv:set_stack('craft',obindex,oldbook)
          522  +		local newmeta = stack:get_meta()
          523  +		local copy = function(field)
          524  +			newmeta:set_string(field,oldbook:get_meta():get_string(field))
          525  +		end
          526  +		copy('cookbook') copy('description')
          527  +		newmeta:set_string('owner',player:get_player_name())
          528  +		return stack
   475    529   	end
   476    530   
   477    531   	oldbook = oldbook or stack
   478    532   	local bookmeta = oldbook:get_meta()
   479    533   	local newbook = not bookmeta:contains('cookbook')
   480    534   	local book = bookprops(oldbook)
   481    535   
................................................................................
   494    548   
   495    549   	if topic and newbook then
   496    550   		if not onetopic then topic = nil end
   497    551   		bookmeta:set_string('description',namebook(topic,player:get_player_name()))
   498    552   		bookmeta:set_string('owner',player:get_player_name())
   499    553   	end
   500    554   
   501         -	print('new book',bookmeta:get_string('description'))
   502         -	print('new book',dump(book))
   503    555   	bookmeta:set_string('cookbook', sorcery.lib.str.meta_armor(encbook(book),true))
   504    556   	return oldbook
   505    557   end)
   506    558   
   507    559   if minetest.get_modpath('books') then
   508    560   	-- make our own placeable cookbook somehow
   509    561   end

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

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

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

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

Added displacer.lua version [54869bc440].

            1  +local constants = {
            2  +	xmit_wattage = 0.4;
            3  +	-- the amount of power per second needed to transmit an item from
            4  +	-- one displacer to another
            5  +
            6  +	rcpt_wattage = 0.15;
            7  +	-- the amount of power needed to broadcast a receptor's availability
            8  +}
            9  +
           10  +local gettxr = function(pos)
           11  +	local txrcomps = {
           12  +		'sorcery:displacer';
           13  +		'sorcery:displacer_transmit_gem';
           14  +		'sorcery:displacer_transmit_attune';
           15  +		'sorcery:displacer_receive_gem';
           16  +		'sorcery:displacer_receive_attune';
           17  +	}
           18  +
           19  +	local devs = sorcery.lib.node.amass(pos,txrcomps,sorcery.lib.node.offsets.neighbors)
           20  +	local r = {
           21  +		receptacles = {};
           22  +		connections = {};
           23  +		counts = {
           24  +			receptacles = 0;
           25  +			transmitters = 0;
           26  +			receptors = 0;
           27  +		};
           28  +	}
           29  +	local getcode = function(pos)
           30  +		local inv = minetest.get_meta(pos):get_inventory()
           31  +		local code = {}
           32  +		local empty = true
           33  +		for i=1,inv:get_size('code') do
           34  +			if not inv:get_stack('code',i):is_empty()
           35  +				then empty = false end
           36  +
           37  +			code[i] = inv:get_stack('code',i):get_name()
           38  +		end
           39  +		if empty then return nil
           40  +		         else return code end
           41  +	end
           42  +	for pos, dev in pairs(devs) do
           43  +		if dev == 'sorcery:displacer_receive_gem' then
           44  +			r.counts.receptors = r.counts.receptors + 1
           45  +			r.connections[#r.connections+1] = {
           46  +				pos = pos; mode = 'receive';
           47  +				code = getcode(pos); -- TODO retrieve code
           48  +			}
           49  +		elseif dev == 'sorcery:displacer_receive_attune' then
           50  +			local tune = sorcery.attunement.verify(pos)
           51  +			if tune then
           52  +				r.counts.receptors = r.counts.receptors + 1
           53  +				r.connections[#r.connections+1] = {
           54  +					pos = pos; mode = 'receive';
           55  +					partner = tune.partner;
           56  +				}
           57  +			end
           58  +		elseif dev == 'sorcery:displacer_transmit_gem' then
           59  +			r.counts.transmitters = r.counts.transmitters + 1
           60  +			r.connections[#r.connections+1] = {
           61  +				pos = pos; mode = 'transmit';
           62  +				code = getcode(pos); -- TODO retrieve code
           63  +			}
           64  +		elseif dev == 'sorcery:displacer_transmit_attune' then
           65  +			local tune = sorcery.attunement.verify(pos)
           66  +			if tune then
           67  +				r.counts.transmitters = r.counts.transmitters + 1
           68  +				r.connections[#r.connections+1] = {
           69  +					pos = pos; mode = 'transmit';
           70  +					partner = tune.partner;
           71  +				}
           72  +			end
           73  +		elseif dev == 'sorcery:displacer' then
           74  +			r.counts.receptacles = r.counts.receptacles + 1
           75  +			r.receptacles[#r.receptacles+1] = pos
           76  +		end
           77  +	end
           78  +	return r
           79  +end
           80  +
           81  +local autoselect = function(pos)
           82  +	local dev = gettxr(pos)
           83  +	local active
           84  +	if dev.counts.receptors == 0 and dev.counts.transmitters == 1 then
           85  +		active = dev.connections[1].pos
           86  +	end
           87  +	for _,rcp in pairs(dev.receptacles) do
           88  +		local meta = minetest.get_meta(rcp)
           89  +		meta:set_string('active-device',active and minetest.pos_to_string(active) or '')
           90  +	end
           91  +	return active ~= nil
           92  +end
           93  +
           94  +minetest.register_node('sorcery:displacer', {
           95  +	description = 'Displacer Receptacle';
           96  +	paramtype2 = 'facedir';
           97  +	on_construct = function(pos)
           98  +		local meta = minetest.get_meta(pos)
           99  +		local inv = meta:get_inventory()
          100  +		minetest.get_node_timer(pos):start(1)
          101  +		inv:set_size('cache', 6)
          102  +		meta:set_string('infotext','displacer')
          103  +		meta:set_string('active-device','')
          104  +		meta:set_string('formspec', [[
          105  +			formspec_version[3] size[10.25,8]
          106  +			list[context;cache;3.125,0.25;3,2]
          107  +			list[current_player;main;0.25,3;8,4]
          108  +			listring[]
          109  +		]])
          110  +	end;
          111  +
          112  +	-- vararg wrapping necessary to discard the return value,
          113  +	-- as a return value of true will prevent the item from
          114  +	-- being removed from the user's inventory after placement
          115  +	after_place_node = function(...) autoselect(...) end;
          116  +	after_dig_node = function(...)
          117  +		autoselect(...)
          118  +		sorcery.lib.node.purge_container(...)
          119  +	end;
          120  +	on_metadata_inventory_put = function(pos)
          121  +		minetest.get_node_timer(pos):start(1)
          122  +	end;
          123  +	on_timer = function(pos,delta)
          124  +		local meta = minetest.get_meta(pos)
          125  +		if not meta:contains('active-device') then return false end
          126  +
          127  +		local inv = meta:get_inventory()
          128  +		if inv:is_empty('cache') then return false end
          129  +
          130  +		local dev = gettxr(pos)
          131  +		local active = minetest.string_to_pos(meta:get_string('active-device'))
          132  +
          133  +		local ad
          134  +		for _,d in pairs(dev.connections) do
          135  +			if vector.equals(d.pos, active) then ad = d break end
          136  +		end
          137  +		if not ad then
          138  +			meta:set_string('active-device','')
          139  +			return false
          140  +		end
          141  +
          142  +		local remote
          143  +		if ad.partner then
          144  +			remote = gettxr(ad.partner)
          145  +		elseif ad.code then
          146  +			local net = sorcery.farcaster.junction(pos,constants.xmit_wattage)
          147  +			for _,n in pairs(net) do
          148  +				for _,d in pairs(n.caps.net.devices.consume) do
          149  +					if d.id == 'sorcery:displacer' then
          150  +						local t = gettxr(d.pos)
          151  +						for _,d in pairs(t.connections) do
          152  +							if d.mode == 'receive' and d.code then
          153  +								local match = true
          154  +								for i=1,#d.code do
          155  +									if d.code[i] ~= ad.code[i] then
          156  +										match = false break
          157  +									end
          158  +								end
          159  +								if match then
          160  +									remote = t
          161  +									break
          162  +								end
          163  +							end
          164  +						end
          165  +					end
          166  +					if remote then break end
          167  +				end
          168  +				if remote then break end
          169  +			end
          170  +		end
          171  +
          172  +		if not remote then return false end
          173  +
          174  +
          175  +		local n = sorcery.ley.netcaps(pos,delta,nil,constants.xmit_wattage)
          176  +		if n.self.powerdraw == n.self.maxpower then
          177  +			-- fully powered for transmission; find an object to transmit
          178  +			local transmission
          179  +			for i=1,inv:get_size('cache') do
          180  +				local s = inv:get_stack('cache',i)
          181  +				if not (s:is_empty() or minetest.get_item_group(s:get_name(), 'sorcery_nontranslocatable') ~= 0) then
          182  +					local quantity = 1
          183  +					local tq = minetest.get_item_group(s:get_name(), 'sorcery_translocate_pack')
          184  +					if tq ~= 0 then quantity = math.min(tq, s:get_count()) end
          185  +					transmission = s:take_item(quantity)
          186  +					inv:set_stack('cache',i,s)
          187  +					break
          188  +				end
          189  +			end
          190  +			if not transmission then return false end
          191  +			-- iterate through available receptacles and see if there's room
          192  +			-- in any of them. otherwise, fail
          193  +			for _,r in pairs(remote.receptacles) do
          194  +				local i = minetest.get_meta(r):get_inventory()
          195  +				transmission = i:add_item('cache',transmission)
          196  +				if transmission:is_empty() then break end
          197  +			end
          198  +			if not transmission:is_empty() then inv:add_item('cache',transmission) end
          199  +			return true
          200  +		elseif n.maxpower >= n.self.maxpower then
          201  +			-- other devices are currently drawing power and might stop,
          202  +			-- making enough available for us; keep iterating just in case
          203  +			return true
          204  +		else
          205  +			-- the system does not have the capability to generate
          206  +			-- sufficient power, no point in continuing to fuck around
          207  +			return false
          208  +		end
          209  +	end;
          210  +	groups = {
          211  +		cracky = 2;
          212  +		sorcery_ley_device = 1;
          213  +		sorcery_magitech = 1;
          214  +	};
          215  +	tiles = {
          216  +		'sorcery_displacer_top.png';
          217  +		'sorcery_displacer_top.png';
          218  +		'sorcery_displacer_side.png';
          219  +		'sorcery_displacer_side.png';
          220  +		'sorcery_displacer_side.png';
          221  +		'sorcery_displacer_front.png';
          222  +	};
          223  +
          224  +	_sorcery = {
          225  +		on_leychange = function(pos)
          226  +			minetest.get_node_timer(pos):start(1)
          227  +		end;
          228  +		ley = {
          229  +			mode = 'consume', affinity = {'mandatic'};
          230  +			power = function(pos,time)
          231  +				local meta = minetest.get_meta(pos)
          232  +				local power = 0
          233  +				if meta:contains('active-device') then
          234  +					power = constants.xmit_wattage
          235  +				end
          236  +
          237  +				local dev = gettxr(pos)
          238  +				power = power + constants.rcpt_wattage * dev.counts.receptors
          239  +
          240  +				return (power / dev.counts.receptacles) * time
          241  +			end;
          242  +		};
          243  +	};
          244  +})
          245  +
          246  +for mode,m in pairs {
          247  +	gem={
          248  +		desc = 'Gem-Coded';
          249  +		construct = function(pos)
          250  +			local meta = minetest.get_meta(pos)
          251  +			local inv = meta:get_inventory()
          252  +			inv:set_size('code',6)
          253  +			meta:set_string('formspec', [[
          254  +				formspec_version[3] size[10.25,7]
          255  +				list[context;code;1.5,0.25;6,1]
          256  +				list[current_player;main;0.25,1.75;8,4]
          257  +				listring[]
          258  +			]])
          259  +		end;
          260  +		allowput = function(pos,list,idx,stack)
          261  +			if list == 'code' then
          262  +				if sorcery.itemclass.get(stack:get_name(),'gem') then return 1 end
          263  +			end
          264  +			return 0
          265  +		end;
          266  +	};
          267  +	attune={
          268  +		desc = 'Attuned';
          269  +	};
          270  +} do for kind,n in pairs {
          271  +		transmit = {
          272  +			name = 'Transmission';
          273  +			button = function(pos)
          274  +				minetest.sound_play('doors_steel_door_open', {pos = pos})
          275  +				local n = minetest.get_node(pos)
          276  +				local dev = gettxr(pos)
          277  +				if dev.counts.receptacles > 0 then
          278  +					for _,r in pairs(dev.receptacles) do
          279  +						local m = minetest.get_meta(r)
          280  +						m:set_string('active-device',minetest.pos_to_string(pos))
          281  +						minetest.get_node_timer(r):start(1)
          282  +					end
          283  +				end
          284  +			end;
          285  +			attune = {
          286  +				target = true, accepts = 'sorcery:displacer';
          287  +				reciprocal = true;
          288  +			};
          289  +		};
          290  +		receive = {
          291  +			name = 'Reception';
          292  +			attune = {
          293  +				source = true, class = 'sorcery:displacer';
          294  +				reciprocal = true;
          295  +			}
          296  +		};
          297  +	} do local id = 'sorcery:displacer_' .. kind .. '_' .. mode
          298  +		minetest.register_node(id, {
          299  +			description = m.desc .. ' ' .. n.name .. ' Module';
          300  +			paramtype2 = 'facedir';
          301  +			tiles = {
          302  +				'sorcery_displacer_top.png';
          303  +				'sorcery_displacer_top.png';
          304  +				'sorcery_displacer_side.png';
          305  +				'sorcery_displacer_side.png';
          306  +				'sorcery_displacer_side.png';
          307  +				'sorcery_displacer_module_' .. kind .. '.png';
          308  +			};
          309  +			on_construct = m.construct;
          310  +			on_rightclick = mode ~= 'gem' and n.button or nil;
          311  +			on_punch = n.button and function(pos,node,puncher)
          312  +				if puncher and puncher:get_wielded_item():is_empty() then
          313  +					n.button(pos)
          314  +				end
          315  +			end or nil;
          316  +			after_place_node = function(...) autoselect(...) end;
          317  +			allow_metadata_inventory_put = m.allowput;
          318  +			_sorcery = {
          319  +				attune = (mode == 'attune') and n.attune or nil;
          320  +			};
          321  +			after_dig_node = function(...)
          322  +				autoselect(...)
          323  +				sorcery.lib.node.purge_container(...)
          324  +			end;
          325  +			groups = {
          326  +				cracky = 2;
          327  +				sorcery_magitech = 1;
          328  +			};
          329  +		})
          330  +	end
          331  +end

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

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

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

    24     24   
    25     25   	if gem.foreign_shard then
    26     26   		minetest.clear_craft {output=shardname}
    27     27   	else
    28     28   		minetest.register_craftitem(shardname, {
    29     29   			description = sorcery.lib.str.capitalize(name) .. ' shard';
    30     30   			inventory_image = 'sorcery_gem_' .. name .. '_shard.png';
    31         -			groups = { sorcery_shard = 1; };
    32         -			_proto = gem;
           31  +			groups = { gemshard = 1; crystalshard = 1; sorcery_shard = 1; };
           32  +			_sorcery = {
           33  +				material = {
           34  +					gem = true;
           35  +					id = name, data = gem;
           36  +					raw = true, value = 1;
           37  +				};
           38  +			};
    33     39   		})
    34     40   	end
    35     41   	if not gem.foreign_amulet then
    36     42   		minetest.register_craftitem(amuletname, {
    37     43   			description = sorcery.lib.str.capitalize(name) .. ' amulet';
    38     44   			inventory_image = sorcery.lib.image('sorcery_amulet.png'):multiply(sorcery.lib.color(gem.tone)):render();
    39         -			_proto = {
    40         -				id = name;
    41         -				data = gem;
           45  +			_sorcery = {
           46  +				material = {
           47  +					gem = true, id = name, data = gem;
           48  +					value = (5 * shards_per_gem) + 4;
           49  +				};
    42     50   			};
    43     51   		}) 
    44     52   	end
    45     53   	minetest.register_craft {
    46     54   		type = 'shapeless';
    47     55   		recipe = (minetest.get_modpath('xdecor') and {
    48     56   			'xdecor:hammer', itemname;
................................................................................
   125    133   		}
   126    134   	end
   127    135   
   128    136   	if gem.foreign then return false end
   129    137   	minetest.register_craftitem(itemname, {
   130    138   		description = sorcery.lib.str.capitalize(name);
   131    139   		inventory_image = 'sorcery_gem_' .. name .. '.png';
   132         -		groups = { sorcery_gem = 1; };
   133         -		_proto = gem;
          140  +		groups = { gem = 1; crystal = 1; sorcery_gem = 1; };
          141  +		_sorcery = {
          142  +			material = {
          143  +				id = name, data = gem;
          144  +				raw = true, value = shards_per_gem;
          145  +			};
          146  +		};
   134    147   	})
   135    148   	local tools = gem.tools
   136    149   	if tools == nil then tools = {
   137    150   		'group:pickaxe';
   138    151   		'group:pick'; -- FUCK YOU INSTANT_ORES
   139    152   		'~default:enchanted_pick_'; -- FUCK YOU XDECOR
   140    153   	} end

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

    49     49   	'potions', 'oils', 'greases',
    50     50   		'draughts', 'elixirs',
    51     51   		'philters', 'extracts';
    52     52   	'register';
    53     53   }
    54     54   
    55     55   for _,u in pairs {
    56         -	'attunement'; 'ores'; 'gems'; 'leylines';
    57         -	'potions'; 'infuser'; 'altar'; 'wands';
    58         -	'tools'; 'crafttools'; 'enchanter'; 'harvester';
    59         -	'metallurgy-hot', 'metallurgy-cold';
    60         -	'entities'; 'recipes'; 'coins';
    61         -	'interop'; 'tnodes'; 'forcefield';
    62         -	'farcaster'; 'portal'; 'cookbook'; 'disassembly';
           56  +	'attunement'; 'metal', 'gems'; 'itemclass';
           57  +	'leylines'; 'potions', 'infuser'; 'altar';
           58  +	'wands'; 'tools', 'crafttools'; 'enchanter';
           59  +	'harvester'; 'metallurgy-hot', 'metallurgy-cold';
           60  +	'entities'; 'recipes'; 'coins'; 'interop';
           61  +	'tnodes'; 'forcefield'; 'farcaster'; 'portal';
           62  +	'cookbook', 'writing'; 'disassembly'; 'displacer';
    63     63   } do sorcery.load(u) end

Added itemclass.lua version [bee797c513].

            1  +-- in theory, minetest groups are supposed to allow us to
            2  +-- give consistent, cross-mod classes to items, and easily
            3  +-- detect whether items fit into a particular class. unfortunately,
            4  +-- they don't really work for this purpose because we often
            5  +-- need to attach additional data to items that are outside
            6  +-- of our control (and the default mod's authors are amazingly
            7  +-- lax in grouping items; for instance, diamonds and mese
            8  +-- crystals aren't even part of a 'gem' or 'crystal' group!)
            9  +-- this module allows us to consistently classify items, and
           10  +-- easily maintain complex hierarchies of subclasses. whether
           11  +-- an item belongs to a class can be determined by checking
           12  +-- its groups, consulting compat tables, calling a custom
           13  +-- predicate function (possibly to check for a _sorcery
           14  +-- defprop), or recursing through a list of subclasses.
           15  +-- this also means that matters of identity are all controlled
           16  +-- from a central location.
           17  +sorcery.itemclass = {
           18  +	classes = {
           19  +		-- gem/crystalline and metal/metallic differentiate
           20  +		-- between crafting materials (i.e. gems or ingots
           21  +		-- themselves) and items crafted from those materials.
           22  +		-- the former includes only crafting materials, the
           23  +		-- latter includes both.
           24  +		gem = {
           25  +			compat = 'gems';
           26  +			groups = { 'gem', 'crystal'; };
           27  +			predicate = function(name)
           28  +				if minetest.get_item_group(name, 'sorcery_gem') ~= 0 
           29  +				or minetest.get_item_group(name, 'sorcery_shard') ~= 0 then
           30  +					return minetest.registered_items[name]._sorcery.material;
           31  +				end
           32  +			end;
           33  +		};
           34  +		crystalline = {
           35  +			subclass = {'gem'};
           36  +			predicate = function(name)
           37  +				local mat = sorcery.matreg.lookup[name]
           38  +				if mat and mat.gem then return mat end
           39  +			end;
           40  +		};
           41  +		grindable = {
           42  +			compat = 'grindables';
           43  +			subclass = {'metallic'};
           44  +			predicate = function(name)
           45  +				local def = minetest.registered_items[name]._sorcery
           46  +				if not def then return nil end
           47  +				def = def.material
           48  +				if def and def.grindvalue then
           49  +					return def end
           50  +			end;
           51  +		};
           52  +		metal = {
           53  +			predicate = function(name)
           54  +				-- metallookup is a table of 'primary' metal
           55  +				-- items, like ingots, fragments, and powders
           56  +				return sorcery.data.metallookup[name]
           57  +			end;
           58  +		};
           59  +		metallic = {
           60  +			subclass = {'metal'};
           61  +			predicate = function(name)
           62  +				-- matreg is a registry binding crafted items,
           63  +				-- like armors and tools, to the material they
           64  +				-- are made out of
           65  +				local mat = sorcery.matreg.lookup[name]
           66  +				if mat and mat.metal then return mat end
           67  +			end;
           68  +		};
           69  +	};
           70  +	get = function(name,class)
           71  +		local c = sorcery.itemclass.classes[class]
           72  +		local o
           73  +		if not c then return false end
           74  +
           75  +		if c.predicate then 
           76  +			o = c.predicate(name)
           77  +			if o then return o end
           78  +		end
           79  +
           80  +		if c.compat then
           81  +			o = sorcery.data.compat[c.compat][name]
           82  +			if o then return o end
           83  +		end
           84  +
           85  +		if c.subclass then
           86  +			for _,s in pairs(c.subclass) do
           87  +				o = sorcery.itemclass.get(name,s)
           88  +				if o then return o end
           89  +			end
           90  +		end
           91  +
           92  +		if c.groups then
           93  +			for _,g in pairs(c.groups) do
           94  +				o = minetest.get_item_group(name,g) 
           95  +				if o > 0 then return { kind = o } end
           96  +			end
           97  +			o = nil
           98  +		end
           99  +
          100  +		return false
          101  +	end;
          102  +}

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

   216    216   			node_box = {
   217    217   				type = 'connected';
   218    218   				disconnected   = { -0.05, -0.35, -0.40; 0.05, -0.25, 0.40 };
   219    219   				connect_front  = { -0.05, -0.35, -0.50; 0.05, -0.25, 0.05 };
   220    220   				connect_back   = { -0.05, -0.35, -0.05; 0.05, -0.25, 0.50 };
   221    221   				connect_right  = { -0.05, -0.35, -0.05; 0.50, -0.25, 0.05 };
   222    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 };
          223  +				connect_top    = { -0.05, -0.35, -0.05; 0.05,  0.50, 0.05 };
   224    224   				connect_bottom = { -0.05, -0.50, -0.05; 0.05, -0.35, 0.05 };
   225    225   			};
   226    226   			connects_to = { 'group:sorcery_ley_device', 'default:mese' };
   227    227   			-- harcoding mese is kind of cheating -- figure out a
   228    228   			-- better way to do this for the longterm
   229    229   			paramtype = 'light';
   230    230   			-- paramtype2 = 'facedir';
................................................................................
   311    311   			sorcery_ley_device = 1;
   312    312   			sorcery_magitech = 1;
   313    313   		};
   314    314   		on_construct = function(pos)
   315    315   			local meta = minetest.get_meta(pos)
   316    316   			meta:set_string('infotext','Condenser')
   317    317   		end;
          318  +		on_rightclick = function(pos)
          319  +			local c = sorcery.ley.netcaps(pos,1)
          320  +			c.net.devices.signal = nil
          321  +			print('LEYNET', dump(c))
          322  +		end;
   318    323   		_sorcery = {
   319    324   			ley = { mode = 'produce';
   320    325   				power = function(pos,time)
   321    326   					return sorcery.ley.field_to_current(sorcery.ley.estimate(pos).force, time);
   322    327   				end;
   323    328   				affinity = function(pos)
   324    329   					return sorcery.ley.estimate(pos).aff
................................................................................
   375    380   				local sum = vector.add(pos,p)
   376    381   				if not foundp(sum) then
   377    382   					checked[#checked + 1] = sum
   378    383   					local nodename = minetest.get_node(sum).name
   379    384   					if nodename == 'ignore' then
   380    385   						minetest.load_area(sum)
   381    386   						nodename = minetest.get_node(sum).name
   382         -						print('**** ignorenode, loaded',nodename)
   383    387   					end
   384    388   					if minetest.get_item_group(nodename,'sorcery_ley_device') ~= 0
   385    389   					   or sorcery.data.compat.ley[nodename] then
   386    390   						local d = sorcery.ley.sample(pos,1,nodename,{query={mode=true}})
   387    391   						assert(d.mode == 'signal'
   388    392   						    or d.mode == 'consume'
   389    393   						    or d.mode == 'produce')

Name change from ores.lua to metal.lua.


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

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

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

    58     58   	minetest.load_area(
    59     59   		vector.add(pos, {x =  5, y =  1 + constants.portal_max_height, z =  5}),
    60     60   		vector.add(pos, {x = -5, y = -1 - constants.portal_max_height, z = -5})
    61     61   	)
    62     62   	-- starting at a portal node, search connected blocks
    63     63   	-- recursively to locate portal pads and their paired
    64     64   	-- partners. return a table characterizing the portal
    65         -	-- return false if no portal found
           65  +	-- or return false if no portal found
    66     66   	
    67     67   	-- first search immediate neighbors. the portal node
    68     68   	-- can be connected to either reflectors or pads
    69     69   	local startpoint, startwithpads
    70     70   	for _, d in pairs(sorcery.lib.node.offsets.neighbors) do
    71     71   		local sum = vector.add(pos,d)
    72     72   		local name = minetest.get_node(sum).name
................................................................................
   158    158   		for _, d in pairs(n.caps.net.devices.consume) do
   159    159   			if d.id == 'sorcery:portal_node' and portal_composition(d.pos) then
   160    160   				circuit[#circuit+1] = {
   161    161   					pos = d.pos;
   162    162   					hops = n.hops;
   163    163   					route = n.route;
   164    164   				}
   165         -				print(' ! found portal node',d.pos)
   166         -			else
   167         -				print('   -- skipping node',d.pos,d.id)
   168    165   			end
   169    166   		end
   170    167   	end
   171    168   	return circuit
   172    169   end
   173    170   
   174    171   local portal_disposition = function(dev)
................................................................................
   288    285   		local tune = sorcery.attunement.verify(pos)
   289    286   		local partner -- raw position of partner node, if any
   290    287   		if tune and tune.partner then
   291    288   			minetest.load_area(tune.partner)
   292    289   			-- we are attuned to a partner, but is it in the circuit?
   293    290   			for _,v in pairs(crc) do
   294    291   				if vector.equals(v.pos,tune.partner) then
   295         -					print('found partner in circuit')
   296    292   					partner = tune.partner
   297    293   					break
   298    294   				end
   299    295   			end
   300    296   		end
   301    297   
   302         -		print("power reqs",cap.self.minpower,cap.self.powerdraw)
   303    298   		if cap.self.minpower ~= cap.self.powerdraw then
   304    299   			print("not enough power")
   305    300   			return true
   306    301   		end
   307    302   
   308    303   		-- clean out user table
   309    304   		for name,user in pairs(portal_context.users) do
................................................................................
   330    325   					local user = portal_context.users[pname]
   331    326   					if not vector.equals(pos,user.portal) then
   332    327   						user.time = 0
   333    328   						user.portal = pos
   334    329   					end
   335    330   					local cap = sorcery.ley.netcaps(pos,delta)
   336    331   					local jc = (constants.portal_jump_cost_local*delta)
   337         -					print('free power',cap.freepower,jc)
   338    332   					if not user.dest and cap.freepower >= jc  then
   339    333   						user.dest = portal_pick_destination(dev,crc,partner)
   340         -					else
   341         -						print('powerdraw',cap.self.powerdraw)
   342    334   					end
   343    335   					if not user.dest then goto skippad else
   344    336   						minetest.load_area(user.dest)
   345    337   					end
   346    338   					local fac = (user.time / constants.portal_jump_time);
   347    339   					minetest.add_particlespawner {
   348    340   						time = 1, amount = 100 + (fac * 200);
................................................................................
   444    436   			mode = 'consume', affinity = {'mandatic'};
   445    437   			power = function(pos,delta)
   446    438   				-- return power use if device is currently in process
   447    439   				-- of teleporting a player; otherwise, return 0
   448    440   				local cost = constants.portal_node_power
   449    441   				for _,u in pairs(portal_context.users) do
   450    442   					if u.dest and vector.equals(u.portal, pos) then
   451         -						print('user is on pad',dump(pos))
   452    443   						cost = cost + constants.portal_jump_cost_local
   453    444   					end
   454    445   				end
   455    446   				return cost * delta
   456    447   			end;
   457    448   		};
   458    449   		

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

     7      7   		"vessels:glass_bottle"
     8      8   	};
     9      9   	output = "sorcery:potion_water 3";
    10     10   	replacements = {
    11     11   		{ "group:water_bucket", "bucket:bucket_empty" }
    12     12   	};
    13     13   }
           14  +minetest.register_craft {
           15  +	output = 'dye:white 4';
           16  +	recipe = {
           17  +		{'',           'sorcery:ash',             ''};
           18  +		{'sorcery:ash','basic_materials:paraffin','sorcery:ash'};
           19  +		{'',           'bucket:bucket_water',     ''};
           20  +	};
           21  +	replacements = {
           22  +		{'bucket:bucket_water', 'bucket:bucket_empty'};
           23  +	};
           24  +};
    14     25   
    15     26   minetest.register_craft {
    16     27   	type = "shapeless";
    17     28   	recipe = {
    18     29   		"bucket:bucket_empty";
    19     30   		"sorcery:potion_water";
    20     31   		"sorcery:potion_water";
................................................................................
   250    261   	};
   251    262   	output = "sorcery:infuser";
   252    263   }
   253    264   
   254    265   minetest.register_craft {
   255    266   	output = "sorcery:displacer";
   256    267   	recipe = {
   257         -		{'sorcery:platinum_ingot','sorcery:leyline_stabilizer','sorcery:platinum_ingot'};
   258         -		{'sorcery:inverter_coil','sorcery:core_syncretic','sorcery:inverter_coil'};
   259         -		{'sorcery:platinum_ingot','default:chest','sorcery:platinum_ingot'};
   260         -	};
   261         -}
   262         -
   263         -minetest.register_craft {
   264         -	output = "sorcery:displacement_node";
   265         -	recipe = {
   266    268   		{'sorcery:platinum_ingot','sorcery:screw_tungsten','sorcery:platinum_ingot'};
   267    269   		{'basic_materials:copper_wire','sorcery:core_syncretic','doors:trapdoor_steel'};
   268    270   		{'sorcery:platinum_ingot','sorcery:screw_tungsten','sorcery:platinum_ingot'};
   269    271   	};
   270    272   	replacements = {
   271    273   		{'basic_materials:copper_wire','basic_materials:empty_spool'};
   272    274   	};
   273    275   }
          276  +
          277  +minetest.register_craft {
          278  +	output = "sorcery:displacer_transmit_attune";
          279  +	recipe = {
          280  +		{'sorcery:platinum_ingot','sorcery:screw_tungsten','sorcery:platinum_ingot'};
          281  +		{'sorcery:leyline_stabilizer','sorcery:core_mandatic','sorcery:tuning_disc'};
          282  +		{'sorcery:platinum_ingot','sorcery:screw_tungsten','sorcery:platinum_ingot'};
          283  +	};
          284  +}
          285  +
          286  +minetest.register_craft {
          287  +	output = "sorcery:displacer_transmit_gem";
          288  +	recipe = {
          289  +		{'sorcery:platinum_ingot','sorcery:screw_tungsten','sorcery:platinum_ingot'};
          290  +		{'sorcery:leyline_stabilizer','sorcery:core_mandatic','sorcery:gem_ruby'};
          291  +		{'sorcery:platinum_ingot','sorcery:screw_tungsten','sorcery:platinum_ingot'};
          292  +	};
          293  +}
          294  +
          295  +minetest.register_craft {
          296  +	output = "sorcery:displacer_receive_attune";
          297  +	recipe = {
          298  +		{'sorcery:platinum_ingot','sorcery:screw_tungsten','sorcery:platinum_ingot'};
          299  +		{'sorcery:inverter_coil','sorcery:core_mandatic','sorcery:tuning_disc'};
          300  +		{'sorcery:platinum_ingot','sorcery:screw_tungsten','sorcery:platinum_ingot'};
          301  +	};
          302  +}
          303  +
          304  +minetest.register_craft {
          305  +	output = "sorcery:displacer_receive_gem";
          306  +	recipe = {
          307  +		{'sorcery:platinum_ingot','sorcery:screw_tungsten','sorcery:platinum_ingot'};
          308  +		{'sorcery:inverter_coil','sorcery:core_mandatic','sorcery:gem_ruby'};
          309  +		{'sorcery:platinum_ingot','sorcery:screw_tungsten','sorcery:platinum_ingot'};
          310  +	};
          311  +}
   274    312   
   275    313   minetest.register_craft {
   276    314   	output = 'sorcery:raycaster';
   277    315   	recipe = {
   278    316   		{'sorcery:gem_amethyst', 'sorcery:gem_amethyst', 'sorcery:gem_amethyst'};
   279    317   		{'default:gold_ingot','sorcery:beam_generator','default:gold_ingot'};
   280    318   		{'sorcery:gem_amethyst', 'sorcery:gem_amethyst', 'sorcery:gem_amethyst'};
................................................................................
   348    386   	groups = {
   349    387   		sorcery_magitech = 1; metal = 1; sorcery_magitech_core = 1;
   350    388   	};
   351    389   });
   352    390   
   353    391   minetest.register_craftitem('sorcery:core_syncretic',{
   354    392   	description = 'Syncresis Core';
   355         -	inventory_image = 'sorcery_core_sycretic.png';
          393  +	inventory_image = 'sorcery_core_syncretic.png';
   356    394   	groups = {
   357    395   		sorcery_magitech = 1; metal = 1; sorcery_magitech_core = 1;
   358    396   	};
   359    397   });
   360    398   
   361    399   minetest.register_craftitem('sorcery:suppression_matrix',{
   362    400   	description = 'Suppression Matrix';
................................................................................
   442    480   	};
   443    481   }
   444    482   
   445    483   minetest.register_craft {
   446    484   	output = 'sorcery:core_syncretic';
   447    485   	recipe = {
   448    486   		{'sorcery:gem_sapphire_shard','default:gold_ingot','sorcery:gem_sapphire_shard'};
   449         -		{'default:gold_ingot','sorcery:gem_diamond','default:gold_ingot'};
          487  +		{'default:gold_ingot','default:diamond','default:gold_ingot'};
   450    488   		{'sorcery:gem_sapphire_shard','default:gold_ingot','sorcery:gem_sapphire_shard'};
   451    489   	};
   452    490   }
   453    491   
   454    492   minetest.register_craft {
   455    493   	output = 'sorcery:core_mandatic';
   456    494   	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  +-- this file contains a few enhancements to the normal book and
            2  +-- paper functionality. it allows authors to disavow their books,
            3  +-- making them appear as by an "unknown author", by smudging out
            4  +-- the byline with black dye. it also allows written books to be
            5  +-- soaked in a bucket of water to wash out the ink and return
            6  +-- them to a virginal, unwritten state. finally, it makes it so
            7  +-- that when a book (or any owned item, for that matter) is
            8  +-- copied, the owner of the new copy is set to the user who
            9  +-- copied it, allowing users to collaborate on books.
           10  +
           11  +local paperburn = function(item,value)
           12  +	minetest.register_craft { type = 'fuel', recipe = item, burntime = 3 * value }
           13  +	minetest.register_craft {
           14  +		type = 'cooking';
           15  +		recipe = item;
           16  +		output = 'sorcery:ash ' .. tostring(value);
           17  +		cooktime = 3 * value;
           18  +	}
           19  +end
           20  +
           21  +paperburn('default:paper',1) paperburn('sorcery:recipe',1)
           22  +paperburn('default:book',3) paperburn('sorcery:cookbook',3)
           23  +paperburn('default:book_written',3)
           24  +
           25  +minetest.register_craft {
           26  +	type = "shapeless";
           27  +	recipe = {"default:book_written", "bucket:bucket_water"};
           28  +	output = "default:book";
           29  +	replacements = {
           30  +		{"bucket:bucket_water", "bucket:bucket_empty"}
           31  +	}
           32  +}
           33  +
           34  +minetest.register_craft {
           35  +	type = 'shapeless';
           36  +	recipe = {"default:book_written", "dye:black"};
           37  +	output = 'default:book_written';
           38  +}
           39  +
           40  +minetest.register_on_craft(function(itemstack,player,recipe,pinv)
           41  +	local meta = itemstack:get_meta()
           42  +	if not meta:contains('owner') then return nil end
           43  +	local pname = player:get_player_name()
           44  +	if meta:get_string('owner') ~= pname then
           45  +		meta:set_string('owner', pname)
           46  +	end
           47  +
           48  +	if itemstack:get_name() == 'default:book_written' then
           49  +		local found_book, found_dye, book_idx = false, false, 0
           50  +		for i,v in pairs(recipe) do
           51  +			if v:get_name() == 'dye:black' and not found_dye
           52  +				then found_dye = true
           53  +			elseif v:get_name() == 'default:book_written' and not found_book
           54  +				then found_book = v book_idx = i
           55  +			elseif not v:is_empty() then found_book = false break end
           56  +		end
           57  +		if found_book and found_dye then
           58  +			meta:from_table(found_book:get_meta():to_table())
           59  +			meta:set_string('owner','unknown author')
           60  +			meta:set_string('description','"'..meta:get_string('title')..'"')
           61  +			pinv:set_stack('craft',book_idx,ItemStack())
           62  +		end
           63  +	end
           64  +	return itemstack
           65  +end)