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: |
93f944b581af9d3a2f8c5ef7d0a27fa6 |
User & Date: | lexi on 2020-08-30 14:45:03 |
Other Links: | manifest | tags |
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 | |
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)