Index: compat.lua ================================================================== --- compat.lua +++ compat.lua @@ -38,7 +38,7 @@ end return { defp = function(name) return minetest.registered_items[name] or minetest.registered_aliases[name] - end + end; } Index: cookbook.lua ================================================================== --- cookbook.lua +++ cookbook.lua @@ -7,21 +7,35 @@ local constants = { -- do not show recipes for items in these groups exclude_groups = { }; exclude_names = { - '_stairs'; - '_slab'; - 'slope_'; + 'stairs'; + 'slab'; + 'slope'; }; -- do not show recipes from this namespace blacklist_mods = { 'group'; -- WHY IS THIS NECESSARY 'moreblocks'; -- too much noise }; recipes_per_cookbook_page = 3; + + group_ids = { + wood = { caption = 'Any Wood', cnitem = 'default:wood' }; + tree = { caption = 'Any Tree', cnitem = 'default:tree' }; + leaves = { caption = 'Any Leaves', cnitem = 'default:leaves' }; + stone = { caption = 'Any Stone', cnitem = 'default:stone' }; + dye = { caption = 'Any Dye', cnitem = 'dye:black' }; + bone = { caption = 'Any Bone', cnitem = 'bonemeal:bone' }; + vessel = { caption = 'Any Bottle', cnitem = 'vessels:glass_bottle' }; + flower = { caption = 'Any Flower', cnitem = 'flowers:rose' }; + mushroom = { caption = 'Any Mushroom', cnitem = 'flowers:mushroom_brown' }; + water_bucket = { caption = 'Water Bucket', cnitem = 'bucket:bucket_water' }; + sorcery_ley_cable = { caption = 'Cable', cnitem = 'sorcery:cable_vidrium' }; + }; } local slot3x3 = { {0,0}, {1,0}, {2,0}; {0,1}, {1,1}, {2,1}; @@ -45,15 +59,15 @@ for k in pairs(minetest.registered_items) do local rec = minetest.get_craft_recipe(k) if rec.items ~= nil and (rec.method == kind or (rec.method == 'shapeless' and kind == 'normal')) then -- is this last bit necessary? local excluded = false for _,n in pairs(constants.exclude_names) do - if string.find(p,n) then + if string.find(k,n) ~= nil then excluded = true break end end if not excluded then for _,g in pairs(constants.exclude_groups) do - if minetest.get_item_group(p, g) > 0 then + if minetest.get_item_group(k, g) > 0 then excluded = true break end end end local props = minetest.registered_items[k]._sorcery local module = modofname(k) if not (excluded @@ -79,12 +93,35 @@ rec[1 + (row * 3) + col] = i.items[j] end end return rec end -local desc_builtin = function(i) - local desc = minetest.registered_items[i].description +local function group_eval(i) + if string.sub(i,1,6) == 'group:' then + local g = string.sub(i,7) + if constants.group_ids[g] then + return constants.group_ids[g].cnitem, + constants.group_ids[g].caption + end + for i,v in pairs(minetest.registered_items) do + if minetest.get_item_group(i, g) > 0 then + return i, v.description + end + end + return i + end + return i +end +local function desc_builtin(i) + local desc + i, desc = group_eval(i) + -- print('describing ',i,dump(minetest.registered_items[i])) + if not minetest.registered_items[i] then + minetest.log('WARNING: unknown item in recipe ' .. i) + return 'Unknown Item' + end + if not desc then desc = minetest.registered_items[i].description end if not desc then return 'Peculiar Item' end local eol = string.find(desc,'\n') if not eol then return desc else return string.sub(desc,1,eol-1) end end; @@ -266,11 +303,11 @@ local tt if k.indesc then tt = k.indesc(ingredients[i]) else tt = desc_builtin(ingredients[i]) end t = t .. string.format([[ item_image[%f,%f;1,1;%s] tooltip[%f,%f;1,1;%s] - ]], x,y, minetest.formspec_escape(ingredients[i]), + ]], x,y, minetest.formspec_escape(group_eval(ingredients[i])), x,y, minetest.formspec_escape(tt)) else if k.drawslots == nil or k.drawslots then t = string.format('box[%f,%f;0.1,0.1;#00000060]',x+0.45,y+0.45) .. t end @@ -338,19 +375,13 @@ name = 'sorcery:recipe'; chance = 0.9; count = {1,7}; } -minetest.register_craft { type = 'fuel', recipe = 'sorcery:recipe', burntime = 3 } -minetest.register_craft { - type = 'cooking'; - recipe = 'sorcery:recipe'; - output = 'sorcery:ash'; - cooktime = 3; -} - default.register_craft_metadata_copy('default:paper','sorcery:recipe') +-- this seems bugged; it doesn't like it when its item shows up in another +-- recipe. so we'll do it manually :/ -- default.register_craft_metadata_copy('default:book','sorcery:cookbook') for i=1,8 do local rcp = {} for i=1,i do rcp[i] = 'sorcery:recipe' end @@ -360,10 +391,18 @@ rcp[#rcp]='sorcery:cookbook' minetest.register_craft { type = 'shapeless', recipe = rcp, output = 'sorcery:cookbook'; } end +minetest.register_craft { + type = 'shapeless'; + recipe = { + 'sorcery:cookbook'; + 'default:book'; + }; + output = 'sorcery:cookbook'; +}; local m = sorcery.lib.marshal local encbook, decbook = m.transcoder { pages = m.g.array(8, m.g.struct { kind = m.t.str; @@ -461,19 +500,34 @@ uinv:set_stack('main',idx,stack) bookform(stack,user) end) minetest.register_on_craft(function(stack,player,grid,inv) + -- god this is messy. i'm sorry. minetest made me do it if stack:get_name() ~= 'sorcery:cookbook' then return nil end local oldbook local topic, onetopic = nil, true local recipes = {} - for _,s in pairs(grid) do + local copybook = false + local obindex + for i,s in pairs(grid) do if s:get_name() == 'sorcery:recipe' then recipes[#recipes+1] = s - elseif s:get_name() == 'sorcery:cookbook' then oldbook = s end + elseif s:get_name() == 'default:book' then copybook = true + elseif s:get_name() == 'sorcery:cookbook' then oldbook = s obindex = i end + end + + if #recipes == 0 and copybook and oldbook then + inv:set_stack('craft',obindex,oldbook) + local newmeta = stack:get_meta() + local copy = function(field) + newmeta:set_string(field,oldbook:get_meta():get_string(field)) + end + copy('cookbook') copy('description') + newmeta:set_string('owner',player:get_player_name()) + return stack end oldbook = oldbook or stack local bookmeta = oldbook:get_meta() local newbook = not bookmeta:contains('cookbook') @@ -496,14 +550,12 @@ if not onetopic then topic = nil end bookmeta:set_string('description',namebook(topic,player:get_player_name())) bookmeta:set_string('owner',player:get_player_name()) end - print('new book',bookmeta:get_string('description')) - print('new book',dump(book)) bookmeta:set_string('cookbook', sorcery.lib.str.meta_armor(encbook(book),true)) return oldbook end) if minetest.get_modpath('books') then -- make our own placeable cookbook somehow end Index: data/compat.lua ================================================================== --- data/compat.lua +++ data/compat.lua @@ -1,5 +1,13 @@ +-- compatibility tables +-- this file is used to hold information that would normally +-- be tagged in the _sorcery or _proto fields of an item's +-- definition, but cannot be placed there because the item +-- is outside the control of the author and its module does +-- not cooperate with sorcery. it is used by itemclass.lua +-- to seamlessly locate the material properties and +-- capabilities of an item. local grain = { hardness = 1; value = 1; powder = 'farming:flour'; grindcost = 4; @@ -20,7 +28,21 @@ ley = { ['default:mese'] = { power = 0.25; mode = 'produce'; }; + }; + gems = { + ['default:mese_crystal'] = { + id = 'mese', gem = true; + value = 9, raw = true; + }; + ['default:mese_crystal_fragment'] = { + id = 'mese', gem = true; + value = 1, raw = true; + }; + ['default:diamond'] = { + id = 'diamond', gem = true; + value = 9, raw = true; + }; }; } Index: data/spells.lua ================================================================== --- data/spells.lua +++ data/spells.lua @@ -283,11 +283,11 @@ name = 'dowsing'; leytype = 'cognic'; color = {65,116,255}; affinity = {'acacia','dark','silent'}; uses = 176; - desc = 'Send up sparks of radia to indicate nearness or absence of attuned blocks'; + desc = 'Send up sparks of radia to indicate nearness or absence of the blocks whose presence the wand is attuned to'; }; verdant = { name = 'verdant'; color = {16,29,255}; uses = 48; @@ -341,11 +341,10 @@ local srcpos = {x=data.x,y=data.y,z=data.z} local srcnode = minetest.get_node(srcpos) local srcdef = minetest.registered_nodes[srcnode.name] if srcdef and srcdef._sorcery and srcdef._sorcery.attune then if sorcery.attunement.nodeid(srcpos) == data.id then - -- check for ink src = { pos = srcpos; props = srcdef._sorcery.attune; } end ADDED displacer.lua Index: displacer.lua ================================================================== --- displacer.lua +++ displacer.lua @@ -0,0 +1,331 @@ +local constants = { + xmit_wattage = 0.4; + -- the amount of power per second needed to transmit an item from + -- one displacer to another + + rcpt_wattage = 0.15; + -- the amount of power needed to broadcast a receptor's availability +} + +local gettxr = function(pos) + local txrcomps = { + 'sorcery:displacer'; + 'sorcery:displacer_transmit_gem'; + 'sorcery:displacer_transmit_attune'; + 'sorcery:displacer_receive_gem'; + 'sorcery:displacer_receive_attune'; + } + + local devs = sorcery.lib.node.amass(pos,txrcomps,sorcery.lib.node.offsets.neighbors) + local r = { + receptacles = {}; + connections = {}; + counts = { + receptacles = 0; + transmitters = 0; + receptors = 0; + }; + } + local getcode = function(pos) + local inv = minetest.get_meta(pos):get_inventory() + local code = {} + local empty = true + for i=1,inv:get_size('code') do + if not inv:get_stack('code',i):is_empty() + then empty = false end + + code[i] = inv:get_stack('code',i):get_name() + end + if empty then return nil + else return code end + end + for pos, dev in pairs(devs) do + if dev == 'sorcery:displacer_receive_gem' then + r.counts.receptors = r.counts.receptors + 1 + r.connections[#r.connections+1] = { + pos = pos; mode = 'receive'; + code = getcode(pos); -- TODO retrieve code + } + elseif dev == 'sorcery:displacer_receive_attune' then + local tune = sorcery.attunement.verify(pos) + if tune then + r.counts.receptors = r.counts.receptors + 1 + r.connections[#r.connections+1] = { + pos = pos; mode = 'receive'; + partner = tune.partner; + } + end + elseif dev == 'sorcery:displacer_transmit_gem' then + r.counts.transmitters = r.counts.transmitters + 1 + r.connections[#r.connections+1] = { + pos = pos; mode = 'transmit'; + code = getcode(pos); -- TODO retrieve code + } + elseif dev == 'sorcery:displacer_transmit_attune' then + local tune = sorcery.attunement.verify(pos) + if tune then + r.counts.transmitters = r.counts.transmitters + 1 + r.connections[#r.connections+1] = { + pos = pos; mode = 'transmit'; + partner = tune.partner; + } + end + elseif dev == 'sorcery:displacer' then + r.counts.receptacles = r.counts.receptacles + 1 + r.receptacles[#r.receptacles+1] = pos + end + end + return r +end + +local autoselect = function(pos) + local dev = gettxr(pos) + local active + if dev.counts.receptors == 0 and dev.counts.transmitters == 1 then + active = dev.connections[1].pos + end + for _,rcp in pairs(dev.receptacles) do + local meta = minetest.get_meta(rcp) + meta:set_string('active-device',active and minetest.pos_to_string(active) or '') + end + return active ~= nil +end + +minetest.register_node('sorcery:displacer', { + description = 'Displacer Receptacle'; + paramtype2 = 'facedir'; + on_construct = function(pos) + local meta = minetest.get_meta(pos) + local inv = meta:get_inventory() + minetest.get_node_timer(pos):start(1) + inv:set_size('cache', 6) + meta:set_string('infotext','displacer') + meta:set_string('active-device','') + meta:set_string('formspec', [[ + formspec_version[3] size[10.25,8] + list[context;cache;3.125,0.25;3,2] + list[current_player;main;0.25,3;8,4] + listring[] + ]]) + end; + + -- vararg wrapping necessary to discard the return value, + -- as a return value of true will prevent the item from + -- being removed from the user's inventory after placement + after_place_node = function(...) autoselect(...) end; + after_dig_node = function(...) + autoselect(...) + sorcery.lib.node.purge_container(...) + end; + on_metadata_inventory_put = function(pos) + minetest.get_node_timer(pos):start(1) + end; + on_timer = function(pos,delta) + local meta = minetest.get_meta(pos) + if not meta:contains('active-device') then return false end + + local inv = meta:get_inventory() + if inv:is_empty('cache') then return false end + + local dev = gettxr(pos) + local active = minetest.string_to_pos(meta:get_string('active-device')) + + local ad + for _,d in pairs(dev.connections) do + if vector.equals(d.pos, active) then ad = d break end + end + if not ad then + meta:set_string('active-device','') + return false + end + + local remote + if ad.partner then + remote = gettxr(ad.partner) + elseif ad.code then + local net = sorcery.farcaster.junction(pos,constants.xmit_wattage) + for _,n in pairs(net) do + for _,d in pairs(n.caps.net.devices.consume) do + if d.id == 'sorcery:displacer' then + local t = gettxr(d.pos) + for _,d in pairs(t.connections) do + if d.mode == 'receive' and d.code then + local match = true + for i=1,#d.code do + if d.code[i] ~= ad.code[i] then + match = false break + end + end + if match then + remote = t + break + end + end + end + end + if remote then break end + end + if remote then break end + end + end + + if not remote then return false end + + + local n = sorcery.ley.netcaps(pos,delta,nil,constants.xmit_wattage) + if n.self.powerdraw == n.self.maxpower then + -- fully powered for transmission; find an object to transmit + local transmission + for i=1,inv:get_size('cache') do + local s = inv:get_stack('cache',i) + if not (s:is_empty() or minetest.get_item_group(s:get_name(), 'sorcery_nontranslocatable') ~= 0) then + local quantity = 1 + local tq = minetest.get_item_group(s:get_name(), 'sorcery_translocate_pack') + if tq ~= 0 then quantity = math.min(tq, s:get_count()) end + transmission = s:take_item(quantity) + inv:set_stack('cache',i,s) + break + end + end + if not transmission then return false end + -- iterate through available receptacles and see if there's room + -- in any of them. otherwise, fail + for _,r in pairs(remote.receptacles) do + local i = minetest.get_meta(r):get_inventory() + transmission = i:add_item('cache',transmission) + if transmission:is_empty() then break end + end + if not transmission:is_empty() then inv:add_item('cache',transmission) end + return true + elseif n.maxpower >= n.self.maxpower then + -- other devices are currently drawing power and might stop, + -- making enough available for us; keep iterating just in case + return true + else + -- the system does not have the capability to generate + -- sufficient power, no point in continuing to fuck around + return false + end + end; + groups = { + cracky = 2; + sorcery_ley_device = 1; + sorcery_magitech = 1; + }; + tiles = { + 'sorcery_displacer_top.png'; + 'sorcery_displacer_top.png'; + 'sorcery_displacer_side.png'; + 'sorcery_displacer_side.png'; + 'sorcery_displacer_side.png'; + 'sorcery_displacer_front.png'; + }; + + _sorcery = { + on_leychange = function(pos) + minetest.get_node_timer(pos):start(1) + end; + ley = { + mode = 'consume', affinity = {'mandatic'}; + power = function(pos,time) + local meta = minetest.get_meta(pos) + local power = 0 + if meta:contains('active-device') then + power = constants.xmit_wattage + end + + local dev = gettxr(pos) + power = power + constants.rcpt_wattage * dev.counts.receptors + + return (power / dev.counts.receptacles) * time + end; + }; + }; +}) + +for mode,m in pairs { + gem={ + desc = 'Gem-Coded'; + construct = function(pos) + local meta = minetest.get_meta(pos) + local inv = meta:get_inventory() + inv:set_size('code',6) + meta:set_string('formspec', [[ + formspec_version[3] size[10.25,7] + list[context;code;1.5,0.25;6,1] + list[current_player;main;0.25,1.75;8,4] + listring[] + ]]) + end; + allowput = function(pos,list,idx,stack) + if list == 'code' then + if sorcery.itemclass.get(stack:get_name(),'gem') then return 1 end + end + return 0 + end; + }; + attune={ + desc = 'Attuned'; + }; +} do for kind,n in pairs { + transmit = { + name = 'Transmission'; + button = function(pos) + minetest.sound_play('doors_steel_door_open', {pos = pos}) + local n = minetest.get_node(pos) + local dev = gettxr(pos) + if dev.counts.receptacles > 0 then + for _,r in pairs(dev.receptacles) do + local m = minetest.get_meta(r) + m:set_string('active-device',minetest.pos_to_string(pos)) + minetest.get_node_timer(r):start(1) + end + end + end; + attune = { + target = true, accepts = 'sorcery:displacer'; + reciprocal = true; + }; + }; + receive = { + name = 'Reception'; + attune = { + source = true, class = 'sorcery:displacer'; + reciprocal = true; + } + }; + } do local id = 'sorcery:displacer_' .. kind .. '_' .. mode + minetest.register_node(id, { + description = m.desc .. ' ' .. n.name .. ' Module'; + paramtype2 = 'facedir'; + tiles = { + 'sorcery_displacer_top.png'; + 'sorcery_displacer_top.png'; + 'sorcery_displacer_side.png'; + 'sorcery_displacer_side.png'; + 'sorcery_displacer_side.png'; + 'sorcery_displacer_module_' .. kind .. '.png'; + }; + on_construct = m.construct; + on_rightclick = mode ~= 'gem' and n.button or nil; + on_punch = n.button and function(pos,node,puncher) + if puncher and puncher:get_wielded_item():is_empty() then + n.button(pos) + end + end or nil; + after_place_node = function(...) autoselect(...) end; + allow_metadata_inventory_put = m.allowput; + _sorcery = { + attune = (mode == 'attune') and n.attune or nil; + }; + after_dig_node = function(...) + autoselect(...) + sorcery.lib.node.purge_container(...) + end; + groups = { + cracky = 2; + sorcery_magitech = 1; + }; + }) + end +end Index: farcaster.lua ================================================================== --- farcaster.lua +++ farcaster.lua @@ -24,11 +24,10 @@ source = true, target = true, reciprocal = true; }; farcaster = { partner = function(pos) local tune = sorcery.attunement.verify(pos) - print(' *!* verifying farcaster tuning',tune) if not tune then return nil end minetest.load_area(tune.partner) local vis = false local ignored repeat Index: gems.lua ================================================================== --- gems.lua +++ gems.lua @@ -26,21 +26,29 @@ minetest.clear_craft {output=shardname} else minetest.register_craftitem(shardname, { description = sorcery.lib.str.capitalize(name) .. ' shard'; inventory_image = 'sorcery_gem_' .. name .. '_shard.png'; - groups = { sorcery_shard = 1; }; - _proto = gem; + groups = { gemshard = 1; crystalshard = 1; sorcery_shard = 1; }; + _sorcery = { + material = { + gem = true; + id = name, data = gem; + raw = true, value = 1; + }; + }; }) end if not gem.foreign_amulet then minetest.register_craftitem(amuletname, { description = sorcery.lib.str.capitalize(name) .. ' amulet'; inventory_image = sorcery.lib.image('sorcery_amulet.png'):multiply(sorcery.lib.color(gem.tone)):render(); - _proto = { - id = name; - data = gem; + _sorcery = { + material = { + gem = true, id = name, data = gem; + value = (5 * shards_per_gem) + 4; + }; }; }) end minetest.register_craft { type = 'shapeless'; @@ -127,12 +135,17 @@ if gem.foreign then return false end minetest.register_craftitem(itemname, { description = sorcery.lib.str.capitalize(name); inventory_image = 'sorcery_gem_' .. name .. '.png'; - groups = { sorcery_gem = 1; }; - _proto = gem; + groups = { gem = 1; crystal = 1; sorcery_gem = 1; }; + _sorcery = { + material = { + id = name, data = gem; + raw = true, value = shards_per_gem; + }; + }; }) local tools = gem.tools if tools == nil then tools = { 'group:pickaxe'; 'group:pick'; -- FUCK YOU INSTANT_ORES Index: init.lua ================================================================== --- init.lua +++ init.lua @@ -51,13 +51,13 @@ 'philters', 'extracts'; 'register'; } for _,u in pairs { - 'attunement'; 'ores'; 'gems'; 'leylines'; - 'potions'; 'infuser'; 'altar'; 'wands'; - 'tools'; 'crafttools'; 'enchanter'; 'harvester'; - 'metallurgy-hot', 'metallurgy-cold'; - 'entities'; 'recipes'; 'coins'; - 'interop'; 'tnodes'; 'forcefield'; - 'farcaster'; 'portal'; 'cookbook'; 'disassembly'; + 'attunement'; 'metal', 'gems'; 'itemclass'; + 'leylines'; 'potions', 'infuser'; 'altar'; + 'wands'; 'tools', 'crafttools'; 'enchanter'; + 'harvester'; 'metallurgy-hot', 'metallurgy-cold'; + 'entities'; 'recipes'; 'coins'; 'interop'; + 'tnodes'; 'forcefield'; 'farcaster'; 'portal'; + 'cookbook', 'writing'; 'disassembly'; 'displacer'; } do sorcery.load(u) end ADDED itemclass.lua Index: itemclass.lua ================================================================== --- itemclass.lua +++ itemclass.lua @@ -0,0 +1,102 @@ +-- in theory, minetest groups are supposed to allow us to +-- give consistent, cross-mod classes to items, and easily +-- detect whether items fit into a particular class. unfortunately, +-- they don't really work for this purpose because we often +-- need to attach additional data to items that are outside +-- of our control (and the default mod's authors are amazingly +-- lax in grouping items; for instance, diamonds and mese +-- crystals aren't even part of a 'gem' or 'crystal' group!) +-- this module allows us to consistently classify items, and +-- easily maintain complex hierarchies of subclasses. whether +-- an item belongs to a class can be determined by checking +-- its groups, consulting compat tables, calling a custom +-- predicate function (possibly to check for a _sorcery +-- defprop), or recursing through a list of subclasses. +-- this also means that matters of identity are all controlled +-- from a central location. +sorcery.itemclass = { + classes = { + -- gem/crystalline and metal/metallic differentiate + -- between crafting materials (i.e. gems or ingots + -- themselves) and items crafted from those materials. + -- the former includes only crafting materials, the + -- latter includes both. + gem = { + compat = 'gems'; + groups = { 'gem', 'crystal'; }; + predicate = function(name) + if minetest.get_item_group(name, 'sorcery_gem') ~= 0 + or minetest.get_item_group(name, 'sorcery_shard') ~= 0 then + return minetest.registered_items[name]._sorcery.material; + end + end; + }; + crystalline = { + subclass = {'gem'}; + predicate = function(name) + local mat = sorcery.matreg.lookup[name] + if mat and mat.gem then return mat end + end; + }; + grindable = { + compat = 'grindables'; + subclass = {'metallic'}; + predicate = function(name) + local def = minetest.registered_items[name]._sorcery + if not def then return nil end + def = def.material + if def and def.grindvalue then + return def end + end; + }; + metal = { + predicate = function(name) + -- metallookup is a table of 'primary' metal + -- items, like ingots, fragments, and powders + return sorcery.data.metallookup[name] + end; + }; + metallic = { + subclass = {'metal'}; + predicate = function(name) + -- matreg is a registry binding crafted items, + -- like armors and tools, to the material they + -- are made out of + local mat = sorcery.matreg.lookup[name] + if mat and mat.metal then return mat end + end; + }; + }; + get = function(name,class) + local c = sorcery.itemclass.classes[class] + local o + if not c then return false end + + if c.predicate then + o = c.predicate(name) + if o then return o end + end + + if c.compat then + o = sorcery.data.compat[c.compat][name] + if o then return o end + end + + if c.subclass then + for _,s in pairs(c.subclass) do + o = sorcery.itemclass.get(name,s) + if o then return o end + end + end + + if c.groups then + for _,g in pairs(c.groups) do + o = minetest.get_item_group(name,g) + if o > 0 then return { kind = o } end + end + o = nil + end + + return false + end; +} Index: leylines.lua ================================================================== --- leylines.lua +++ leylines.lua @@ -218,11 +218,11 @@ disconnected = { -0.05, -0.35, -0.40; 0.05, -0.25, 0.40 }; connect_front = { -0.05, -0.35, -0.50; 0.05, -0.25, 0.05 }; connect_back = { -0.05, -0.35, -0.05; 0.05, -0.25, 0.50 }; connect_right = { -0.05, -0.35, -0.05; 0.50, -0.25, 0.05 }; connect_left = { -0.50, -0.35, -0.05; 0.05, -0.25, 0.05 }; - connect_top = { -0.05, -0.25, -0.05; 0.05, 0.50, 0.05 }; + connect_top = { -0.05, -0.35, -0.05; 0.05, 0.50, 0.05 }; connect_bottom = { -0.05, -0.50, -0.05; 0.05, -0.35, 0.05 }; }; connects_to = { 'group:sorcery_ley_device', 'default:mese' }; -- harcoding mese is kind of cheating -- figure out a -- better way to do this for the longterm @@ -313,10 +313,15 @@ }; on_construct = function(pos) local meta = minetest.get_meta(pos) meta:set_string('infotext','Condenser') end; + on_rightclick = function(pos) + local c = sorcery.ley.netcaps(pos,1) + c.net.devices.signal = nil + print('LEYNET', dump(c)) + end; _sorcery = { ley = { mode = 'produce'; power = function(pos,time) return sorcery.ley.field_to_current(sorcery.ley.estimate(pos).force, time); end; @@ -377,11 +382,10 @@ checked[#checked + 1] = sum local nodename = minetest.get_node(sum).name if nodename == 'ignore' then minetest.load_area(sum) nodename = minetest.get_node(sum).name - print('**** ignorenode, loaded',nodename) end if minetest.get_item_group(nodename,'sorcery_ley_device') ~= 0 or sorcery.data.compat.ley[nodename] then local d = sorcery.ley.sample(pos,1,nodename,{query={mode=true}}) assert(d.mode == 'signal' ADDED metal.lua Index: metal.lua ================================================================== --- metal.lua +++ metal.lua @@ -0,0 +1,195 @@ +local fragments_per_ingot = 4 + +minetest.register_lbm { + label = "delete duranium ore again"; + name = "sorcery:delete_duranium_ore_again"; + nodenames = {'sorcery:stone_with_duranium'}; + action = function(pos,node) + minetest.set_node(pos, {name = 'default:stone'}) + end +} + +sorcery.data.alloys = {} +sorcery.data.kilnrecs = {} +sorcery.data.metallookup = { + -- compat bullshit + ['moreores:silver_ingot'] = { + id = 'silver'; data = sorcery.data.metals.silver; + value = fragments_per_ingot; + }; + ['moreores:silver_block'] = { + id = 'silver'; data = sorcery.data.metals.silver; + value = fragments_per_ingot * 9; + }; + + ['basic_materials:brass_ingot'] = { + id = 'brass'; data = sorcery.data.metals.brass; + value = fragments_per_ingot; + }; + ['basic_materials:brass_block'] = { + id = 'brass'; data = sorcery.data.metals.brass; + value = fragments_per_ingot * 9; + }; + ['morelights_vintage:brass_ingot'] = { + id = 'brass'; data = sorcery.data.metals.brass; + value = fragments_per_ingot; + }; + ['morelights_vintage:brass_block'] = { + id = 'brass'; data = sorcery.data.metals.brass; + value = fragments_per_ingot * 9; + }; +} + +local tools, armors = sorcery.matreg.tools, sorcery.matreg.armors +for name, metal in pairs(sorcery.data.metals) do + local ingot = metal.ingot or 'sorcery:' .. name .. '_ingot' + local block = metal.block or 'sorcery:' .. name .. 'block' + local screw = 'sorcery:screw_' .. name + local fragment = 'sorcery:fragment_' .. name + local powder = 'sorcery:powder_' .. name + metal.parts = { + ingot = ingot; + block = block; + screw = screw; + fragment = fragment; + powder = powder; + } + if not metal.no_tools then for t,c in pairs(tools) do + sorcery.matreg.lookup[(metal.items and metal.items[t]) or ('sorcery:' .. t .. '_' .. name)] = { + metal = true; + id = name; data = metal; + value = c.cost * fragments_per_ingot; + } + end end + if not metal.no_armor then for a,c in pairs(armors) do + sorcery.matreg.lookup[(metal.items and metal.items[a]) or ('sorcery:' .. a .. '_' .. name)] = { + metal = true; + id = name; data = metal; + value = c.cost * fragments_per_ingot; + } + end end + sorcery.data.metallookup[ingot] = { + id = name; data = metal; + value = fragments_per_ingot; + } + sorcery.data.metallookup[block] = { + id = name; data = metal; + value = fragments_per_ingot * 9; + } + sorcery.data.metallookup[fragment] = { + id = name; data = metal; + value = 1; + } + sorcery.data.metallookup[screw] = { + id = name; data = metal; + value = 0; -- prevent use in smelting + } + minetest.register_craftitem(screw, { + description = sorcery.lib.str.capitalize(name) .. ' Screw'; + inventory_image = sorcery.lib.image('sorcery_screw.png'):multiply(sorcery.lib.color(metal.tone)):render(); + }) + minetest.register_craftitem(powder, { + description = sorcery.lib.str.capitalize(name) .. ' Powder'; + inventory_image = 'sorcery_' .. name .. '_powder.png'; + }) + if metal.dye then + minetest.register_craft { + output = 'dye:' .. metal.dye .. ' 4'; + recipe = { + {'', powder, ''}; + {powder,'basic_materials:paraffin',powder}; + {'','bucket:bucket_water',''}; + }; + replacements = { + {'bucket:bucket_water', 'bucket:bucket_empty'}; + }; + }; + end + -- TODO: replace crafting recipe with kiln recipe + minetest.register_craft { + output = screw.. ' 8'; + recipe = { + {'', 'xdecor:hammer',''}; + { fragment,fragment,fragment}; + {'', fragment,''}; + }; + replacements = { + {'xdecor:hammer','xdecor:hammer'}; + }; + } + if not sorcery.compat.defp(ingot) then + -- TODO: remove instant_ores dependency + instant_ores.register_metal { + name = 'sorcery:' .. name; + description = sorcery.lib.str.capitalize(name); + color = sorcery.lib.color(metal.tone):hex() .. ':' .. ((metal.alpha and tostring(metal.alpha)) or '45'); + rarity = metal.rarity; + depth = metal.depth; + no_armor = metal.no_armor; + no_tools = metal.no_tools; + durability = metal.durability; + power = metal.power; + speed = metal.speed; + artificial = metal.artificial; + cooktime = metal.cooktime; + hardness = (metal.hardness/8) * 3; -- scaled wrt diamond + level = math.ceil(((metal.hardness/8) * 3)) + 1; + ingot_image = (metal.image and metal.image.ingot) or nil; + lump_image = (metal.image and metal.image.lump) or nil; + armor_weight = metal.armor_weight; + armor_protection = metal.armor_protection; + } + end + minetest.register_craftitem(fragment, { + inventory_image = 'sorcery_' .. name .. '_fragment.png'; + description = sorcery.lib.str.capitalize(name) .. ' Fragment'; + }) + minetest.register_craft { + type = 'cooking'; + recipe = powder; + cooktime = (metal.cooktime or 4) * 0.25; + output = fragment; + } + minetest.register_craft { + type = 'cooking'; + recipe = ingot; + cooktime = (metal.cooktime or 4) * 0.5; + output = fragment .. ' ' .. tostring(fragments_per_ingot); + } + do local rec = {} + for i=1,fragments_per_ingot do + rec[#rec+1]=fragment + end + minetest.register_craft { + type = 'shapeless'; + recipe = rec; + output = ingot; + } + end + if metal.fuel then + minetest.register_craft { + type = 'fuel'; + recipe = powder; + burntime = metal.fuel; + } + end + if metal.mix then + sorcery.data.register.alloy(sorcery.lib.tbl.merge(metal.mix, { + output = name; + cooktime = metal.cooktime or 10; + })) + end + if metal.sinter then + local powders = {} + for _,m in pairs(metal.sinter) do + powders[#powders+1] = 'sorcery:powder_' .. m + end + if metal.sinter_catalyst then for _,m in pairs(metal.sinter_catalyst) + do powders[#powders+1] = m end end + minetest.register_craft { + type = 'shapeless'; + output = powder .. ' ' .. tostring(#powders); + recipe = powders; + }; + end +end Index: metallurgy-cold.lua ================================================================== --- metallurgy-cold.lua +++ metallurgy-cold.lua @@ -87,11 +87,12 @@ local mat = sorcery.matreg.lookup[item:get_name()] local comp = sorcery.data.compat.grindables[item:get_name()] if metal or (mat and mat.metal) or comp then return item:get_count() else - mat = item:get_definition()._matprop + mat = item:get_definition()._sorcery and + item:get_definition()._sorcery.material if mat and mat.grindvalue then return item:get_count() end end end @@ -105,11 +106,12 @@ local mat = sorcery.matreg.lookup[item:get_name()] if mat and mat.metal then metal = mat end end - local mp = item:get_definition()._matprop + local mp = (item:get_definition()._sorcery and + item:get_definition()._sorcery.material) or sorcery.data.compat.grindables[item:get_name()] or {} if metal then mp = { hardness = mp.hardness or metal.data.hardness; DELETED ores.lua Index: ores.lua ================================================================== --- ores.lua +++ ores.lua @@ -1,195 +0,0 @@ -local fragments_per_ingot = 4 - -minetest.register_lbm { - label = "delete duranium ore again"; - name = "sorcery:delete_duranium_ore_again"; - nodenames = {'sorcery:stone_with_duranium'}; - action = function(pos,node) - minetest.set_node(pos, {name = 'default:stone'}) - end -} - -sorcery.data.alloys = {} -sorcery.data.kilnrecs = {} -sorcery.data.metallookup = { - -- compat bullshit - ['moreores:silver_ingot'] = { - id = 'silver'; data = sorcery.data.metals.silver; - value = fragments_per_ingot; - }; - ['moreores:silver_block'] = { - id = 'silver'; data = sorcery.data.metals.silver; - value = fragments_per_ingot * 9; - }; - - ['basic_materials:brass_ingot'] = { - id = 'brass'; data = sorcery.data.metals.brass; - value = fragments_per_ingot; - }; - ['basic_materials:brass_block'] = { - id = 'brass'; data = sorcery.data.metals.brass; - value = fragments_per_ingot * 9; - }; - ['morelights_vintage:brass_ingot'] = { - id = 'brass'; data = sorcery.data.metals.brass; - value = fragments_per_ingot; - }; - ['morelights_vintage:brass_block'] = { - id = 'brass'; data = sorcery.data.metals.brass; - value = fragments_per_ingot * 9; - }; -} - -local tools, armors = sorcery.matreg.tools, sorcery.matreg.armors -for name, metal in pairs(sorcery.data.metals) do - local ingot = metal.ingot or 'sorcery:' .. name .. '_ingot' - local block = metal.block or 'sorcery:' .. name .. 'block' - local screw = 'sorcery:screw_' .. name - local fragment = 'sorcery:fragment_' .. name - local powder = 'sorcery:powder_' .. name - metal.parts = { - ingot = ingot; - block = block; - screw = screw; - fragment = fragment; - powder = powder; - } - if not metal.no_tools then for t,c in pairs(tools) do - sorcery.matreg.lookup[(metal.items and metal.items[t]) or ('sorcery:' .. t .. '_' .. name)] = { - metal = true; - id = name; data = metal; - value = c.cost * fragments_per_ingot; - } - end end - if not metal.no_armor then for a,c in pairs(armors) do - sorcery.matreg.lookup[(metal.items and metal.items[a]) or ('sorcery:' .. a .. '_' .. name)] = { - metal = true; - id = name; data = metal; - value = c.cost * fragments_per_ingot; - } - end end - sorcery.data.metallookup[ingot] = { - id = name; data = metal; - value = fragments_per_ingot; - } - sorcery.data.metallookup[block] = { - id = name; data = metal; - value = fragments_per_ingot * 9; - } - sorcery.data.metallookup[fragment] = { - id = name; data = metal; - value = 1; - } - sorcery.data.metallookup[screw] = { - id = name; data = metal; - value = 0; -- prevent use in smelting - } - minetest.register_craftitem(screw, { - description = sorcery.lib.str.capitalize(name) .. ' Screw'; - inventory_image = sorcery.lib.image('sorcery_screw.png'):multiply(sorcery.lib.color(metal.tone)):render(); - }) - minetest.register_craftitem(powder, { - description = sorcery.lib.str.capitalize(name) .. ' Powder'; - inventory_image = 'sorcery_' .. name .. '_powder.png'; - }) - if metal.dye then - minetest.register_craft { - output = 'dye:' .. metal.dye .. ' 4'; - recipe = { - {'', powder, ''}; - {powder,'basic_materials:paraffin',powder}; - {'','bucket:bucket_water',''}; - }; - replacements = { - {'bucket:bucket_water', 'bucket:bucket_empty'}; - }; - }; - end - -- TODO: replace crafting recipe with kiln recipe - minetest.register_craft { - output = screw.. ' 8'; - recipe = { - {'', 'xdecor:hammer',''}; - { fragment,fragment,fragment}; - {'', fragment,''}; - }; - replacements = { - {'xdecor:hammer','xdecor:hammer'}; - }; - } - if not sorcery.compat.defp(ingot) then - -- TODO: remove instant_ores dependency - instant_ores.register_metal { - name = 'sorcery:' .. name; - description = sorcery.lib.str.capitalize(name); - color = sorcery.lib.color(metal.tone):hex() .. ':' .. ((metal.alpha and tostring(metal.alpha)) or '45'); - rarity = metal.rarity; - depth = metal.depth; - no_armor = metal.no_armor; - no_tools = metal.no_tools; - durability = metal.durability; - power = metal.power; - speed = metal.speed; - artificial = metal.artificial; - cooktime = metal.cooktime; - hardness = (metal.hardness/8) * 3; -- scaled wrt diamond - level = math.ceil(((metal.hardness/8) * 3)) + 1; - ingot_image = (metal.image and metal.image.ingot) or nil; - lump_image = (metal.image and metal.image.lump) or nil; - armor_weight = metal.armor_weight; - armor_protection = metal.armor_protection; - } - end - minetest.register_craftitem(fragment, { - inventory_image = 'sorcery_' .. name .. '_fragment.png'; - description = sorcery.lib.str.capitalize(name) .. ' Fragment'; - }) - minetest.register_craft { - type = 'cooking'; - recipe = powder; - cooktime = (metal.cooktime or 4) * 0.25; - output = fragment; - } - minetest.register_craft { - type = 'cooking'; - recipe = ingot; - cooktime = (metal.cooktime or 4) * 0.5; - output = fragment .. ' ' .. tostring(fragments_per_ingot); - } - do local rec = {} - for i=1,fragments_per_ingot do - rec[#rec+1]=fragment - end - minetest.register_craft { - type = 'shapeless'; - recipe = rec; - output = ingot; - } - end - if metal.fuel then - minetest.register_craft { - type = 'fuel'; - recipe = powder; - burntime = metal.fuel; - } - end - if metal.mix then - sorcery.data.register.alloy(sorcery.lib.tbl.merge(metal.mix, { - output = name; - cooktime = metal.cooktime or 10; - })) - end - if metal.sinter then - local powders = {} - for _,m in pairs(metal.sinter) do - powders[#powders+1] = 'sorcery:powder_' .. m - end - if metal.sinter_catalyst then for _,m in pairs(metal.sinter_catalyst) - do powders[#powders+1] = m end end - minetest.register_craft { - type = 'shapeless'; - output = powder .. ' ' .. tostring(#powders); - recipe = powders; - }; - end -end Index: portal.lua ================================================================== --- portal.lua +++ portal.lua @@ -60,11 +60,11 @@ vector.add(pos, {x = -5, y = -1 - constants.portal_max_height, z = -5}) ) -- starting at a portal node, search connected blocks -- recursively to locate portal pads and their paired -- partners. return a table characterizing the portal - -- return false if no portal found + -- or return false if no portal found -- first search immediate neighbors. the portal node -- can be connected to either reflectors or pads local startpoint, startwithpads for _, d in pairs(sorcery.lib.node.offsets.neighbors) do @@ -160,13 +160,10 @@ circuit[#circuit+1] = { pos = d.pos; hops = n.hops; route = n.route; } - print(' ! found portal node',d.pos) - else - print(' -- skipping node',d.pos,d.id) end end end return circuit end @@ -290,18 +287,16 @@ if tune and tune.partner then minetest.load_area(tune.partner) -- we are attuned to a partner, but is it in the circuit? for _,v in pairs(crc) do if vector.equals(v.pos,tune.partner) then - print('found partner in circuit') partner = tune.partner break end end end - print("power reqs",cap.self.minpower,cap.self.powerdraw) if cap.self.minpower ~= cap.self.powerdraw then print("not enough power") return true end @@ -332,15 +327,12 @@ user.time = 0 user.portal = pos end local cap = sorcery.ley.netcaps(pos,delta) local jc = (constants.portal_jump_cost_local*delta) - print('free power',cap.freepower,jc) if not user.dest and cap.freepower >= jc then user.dest = portal_pick_destination(dev,crc,partner) - else - print('powerdraw',cap.self.powerdraw) end if not user.dest then goto skippad else minetest.load_area(user.dest) end local fac = (user.time / constants.portal_jump_time); @@ -446,11 +438,10 @@ -- return power use if device is currently in process -- of teleporting a player; otherwise, return 0 local cost = constants.portal_node_power for _,u in pairs(portal_context.users) do if u.dest and vector.equals(u.portal, pos) then - print('user is on pad',dump(pos)) cost = cost + constants.portal_jump_cost_local end end return cost * delta end; Index: recipes.lua ================================================================== --- recipes.lua +++ recipes.lua @@ -9,10 +9,21 @@ output = "sorcery:potion_water 3"; replacements = { { "group:water_bucket", "bucket:bucket_empty" } }; } +minetest.register_craft { + output = 'dye:white 4'; + recipe = { + {'', 'sorcery:ash', ''}; + {'sorcery:ash','basic_materials:paraffin','sorcery:ash'}; + {'', 'bucket:bucket_water', ''}; + }; + replacements = { + {'bucket:bucket_water', 'bucket:bucket_empty'}; + }; +}; minetest.register_craft { type = "shapeless"; recipe = { "bucket:bucket_empty"; @@ -252,27 +263,54 @@ } minetest.register_craft { output = "sorcery:displacer"; recipe = { - {'sorcery:platinum_ingot','sorcery:leyline_stabilizer','sorcery:platinum_ingot'}; - {'sorcery:inverter_coil','sorcery:core_syncretic','sorcery:inverter_coil'}; - {'sorcery:platinum_ingot','default:chest','sorcery:platinum_ingot'}; - }; -} - -minetest.register_craft { - output = "sorcery:displacement_node"; - recipe = { {'sorcery:platinum_ingot','sorcery:screw_tungsten','sorcery:platinum_ingot'}; {'basic_materials:copper_wire','sorcery:core_syncretic','doors:trapdoor_steel'}; {'sorcery:platinum_ingot','sorcery:screw_tungsten','sorcery:platinum_ingot'}; }; replacements = { {'basic_materials:copper_wire','basic_materials:empty_spool'}; }; } + +minetest.register_craft { + output = "sorcery:displacer_transmit_attune"; + recipe = { + {'sorcery:platinum_ingot','sorcery:screw_tungsten','sorcery:platinum_ingot'}; + {'sorcery:leyline_stabilizer','sorcery:core_mandatic','sorcery:tuning_disc'}; + {'sorcery:platinum_ingot','sorcery:screw_tungsten','sorcery:platinum_ingot'}; + }; +} + +minetest.register_craft { + output = "sorcery:displacer_transmit_gem"; + recipe = { + {'sorcery:platinum_ingot','sorcery:screw_tungsten','sorcery:platinum_ingot'}; + {'sorcery:leyline_stabilizer','sorcery:core_mandatic','sorcery:gem_ruby'}; + {'sorcery:platinum_ingot','sorcery:screw_tungsten','sorcery:platinum_ingot'}; + }; +} + +minetest.register_craft { + output = "sorcery:displacer_receive_attune"; + recipe = { + {'sorcery:platinum_ingot','sorcery:screw_tungsten','sorcery:platinum_ingot'}; + {'sorcery:inverter_coil','sorcery:core_mandatic','sorcery:tuning_disc'}; + {'sorcery:platinum_ingot','sorcery:screw_tungsten','sorcery:platinum_ingot'}; + }; +} + +minetest.register_craft { + output = "sorcery:displacer_receive_gem"; + recipe = { + {'sorcery:platinum_ingot','sorcery:screw_tungsten','sorcery:platinum_ingot'}; + {'sorcery:inverter_coil','sorcery:core_mandatic','sorcery:gem_ruby'}; + {'sorcery:platinum_ingot','sorcery:screw_tungsten','sorcery:platinum_ingot'}; + }; +} minetest.register_craft { output = 'sorcery:raycaster'; recipe = { {'sorcery:gem_amethyst', 'sorcery:gem_amethyst', 'sorcery:gem_amethyst'}; @@ -350,11 +388,11 @@ }; }); minetest.register_craftitem('sorcery:core_syncretic',{ description = 'Syncresis Core'; - inventory_image = 'sorcery_core_sycretic.png'; + inventory_image = 'sorcery_core_syncretic.png'; groups = { sorcery_magitech = 1; metal = 1; sorcery_magitech_core = 1; }; }); @@ -444,11 +482,11 @@ minetest.register_craft { output = 'sorcery:core_syncretic'; recipe = { {'sorcery:gem_sapphire_shard','default:gold_ingot','sorcery:gem_sapphire_shard'}; - {'default:gold_ingot','sorcery:gem_diamond','default:gold_ingot'}; + {'default:gold_ingot','default:diamond','default:gold_ingot'}; {'sorcery:gem_sapphire_shard','default:gold_ingot','sorcery:gem_sapphire_shard'}; }; } minetest.register_craft { Index: textures/sorcery_core_syncretic.png ================================================================== --- textures/sorcery_core_syncretic.png +++ textures/sorcery_core_syncretic.png cannot compute difference between binary files ADDED textures/sorcery_displacer_front.png Index: textures/sorcery_displacer_front.png ================================================================== --- textures/sorcery_displacer_front.png +++ textures/sorcery_displacer_front.png cannot compute difference between binary files ADDED textures/sorcery_displacer_module_receive.png Index: textures/sorcery_displacer_module_receive.png ================================================================== --- textures/sorcery_displacer_module_receive.png +++ textures/sorcery_displacer_module_receive.png cannot compute difference between binary files ADDED textures/sorcery_displacer_module_transmit.png Index: textures/sorcery_displacer_module_transmit.png ================================================================== --- textures/sorcery_displacer_module_transmit.png +++ textures/sorcery_displacer_module_transmit.png cannot compute difference between binary files ADDED textures/sorcery_displacer_side.png Index: textures/sorcery_displacer_side.png ================================================================== --- textures/sorcery_displacer_side.png +++ textures/sorcery_displacer_side.png cannot compute difference between binary files ADDED textures/sorcery_displacer_top.png Index: textures/sorcery_displacer_top.png ================================================================== --- textures/sorcery_displacer_top.png +++ textures/sorcery_displacer_top.png cannot compute difference between binary files Index: textures/sorcery_tuning_disc.png ================================================================== --- textures/sorcery_tuning_disc.png +++ textures/sorcery_tuning_disc.png cannot compute difference between binary files DELETED textures/sorcery_wandworking_station_top.png~ Index: textures/sorcery_wandworking_station_top.png~ ================================================================== --- textures/sorcery_wandworking_station_top.png~ +++ textures/sorcery_wandworking_station_top.png~ cannot compute difference between binary files ADDED writing.lua Index: writing.lua ================================================================== --- writing.lua +++ writing.lua @@ -0,0 +1,65 @@ +-- this file contains a few enhancements to the normal book and +-- paper functionality. it allows authors to disavow their books, +-- making them appear as by an "unknown author", by smudging out +-- the byline with black dye. it also allows written books to be +-- soaked in a bucket of water to wash out the ink and return +-- them to a virginal, unwritten state. finally, it makes it so +-- that when a book (or any owned item, for that matter) is +-- copied, the owner of the new copy is set to the user who +-- copied it, allowing users to collaborate on books. + +local paperburn = function(item,value) + minetest.register_craft { type = 'fuel', recipe = item, burntime = 3 * value } + minetest.register_craft { + type = 'cooking'; + recipe = item; + output = 'sorcery:ash ' .. tostring(value); + cooktime = 3 * value; + } +end + +paperburn('default:paper',1) paperburn('sorcery:recipe',1) +paperburn('default:book',3) paperburn('sorcery:cookbook',3) +paperburn('default:book_written',3) + +minetest.register_craft { + type = "shapeless"; + recipe = {"default:book_written", "bucket:bucket_water"}; + output = "default:book"; + replacements = { + {"bucket:bucket_water", "bucket:bucket_empty"} + } +} + +minetest.register_craft { + type = 'shapeless'; + recipe = {"default:book_written", "dye:black"}; + output = 'default:book_written'; +} + +minetest.register_on_craft(function(itemstack,player,recipe,pinv) + local meta = itemstack:get_meta() + if not meta:contains('owner') then return nil end + local pname = player:get_player_name() + if meta:get_string('owner') ~= pname then + meta:set_string('owner', pname) + end + + if itemstack:get_name() == 'default:book_written' then + local found_book, found_dye, book_idx = false, false, 0 + for i,v in pairs(recipe) do + if v:get_name() == 'dye:black' and not found_dye + then found_dye = true + elseif v:get_name() == 'default:book_written' and not found_book + then found_book = v book_idx = i + elseif not v:is_empty() then found_book = false break end + end + if found_book and found_dye then + meta:from_table(found_book:get_meta():to_table()) + meta:set_string('owner','unknown author') + meta:set_string('description','"'..meta:get_string('title')..'"') + pinv:set_stack('craft',book_idx,ItemStack()) + end + end + return itemstack +end)