local S = default.get_translator local esc = core.formspec_escape local formspec_size = "size[8,8]" local function formspec_core(tab) if tab == nil then tab = 1 else tab = tostring(tab) end return "tabheader[0,0;book_header;" .. esc(S("Write")) .. "," .. esc(S("Read")) .. ";" .. tab .. ";false;false]" end local function formspec_write(title, text) return "field[0.5,1;7.5,0;title;" .. esc(S("Title:")) .. ";" .. esc(title) .. "]" .. "textarea[0.5,1.5;7.5,7;text;" .. esc(S("Contents:")) .. ";" .. esc(text) .. "]" .. "button_exit[2.5,7.5;3,1;save;" .. esc(S("Save")) .. "]" end local function formspec_read(owner, title, string, text, page, page_max) return "label[0.5,0.5;" .. esc(S("by @1", owner)) .. "]" .. "tablecolumns[color;text]" .. "tableoptions[background=#00000000;highlight=#00000000;border=false]" .. "table[0.4,0;7,0.5;title;#FFFF00," .. esc(title) .. "]" .. "textarea[0.5,1.5;7.5,7;;" .. esc(string ~= "" and string or text) .. ";]" .. "button[2.4,7.6;0.8,0.8;book_prev;<]" .. "label[3.2,7.7;" .. esc(S("Page @1 of @2", page, page_max)) .. "]" .. "button[4.9,7.6;0.8,0.8;book_next;>]" end local function formspec_string(lpp, page, lines, string) for i = ((lpp * page) - lpp) + 1, lpp * page do if not lines[i] then break end string = string .. lines[i] .. "\n" end return string end local book_writers = {} core.register_on_leaveplayer(function(player) book_writers[player:get_player_name()] = nil end) local tab_number local lpp = 14 -- Lines per book's page local function book_on_use(itemstack, user) local player_name = user:get_player_name() local meta = itemstack:get_meta() local title, text, owner = "", "", player_name local page, page_max, lines, string = 1, 1, {}, "" -- Backwards compatibility local old_data = core.deserialize(itemstack:get_metadata()) if old_data then meta:from_table({ fields = old_data }) end local data = meta:to_table().fields if data.owner then title = data.title or "" text = data.text or "" owner = data.owner for str in (text .. "\n"):gmatch("([^\n]*)[\n]") do lines[#lines + 1] = str end if data.page then page = data.page page_max = data.page_max string = formspec_string(lpp, page, lines, string) end end local formspec if title == "" and text == "" then formspec = formspec_write(title, text) elseif owner == player_name then local tab = tab_number or 1 if tab == 2 then formspec = formspec_core(tab) .. formspec_read(owner, title, string, text, page, page_max) else formspec = formspec_core(tab) .. formspec_write(title, text) end else formspec = formspec_read(owner, title, string, text, page, page_max) end core.show_formspec(player_name, "default:book", formspec_size .. formspec) -- Store the wield index in case the user accidentally switches before the formspec is shown book_writers[player_name] = { wield_index = user:get_wield_index() } return itemstack end local max_text_size = 10000 local max_title_size = 80 local short_title_size = 35 core.register_on_player_receive_fields(function(player, formname, fields) if formname ~= "default:book" then return end local player_name = player:get_player_name() local inv = player:get_inventory() if not book_writers[player_name] then return end local wield_index = book_writers[player_name].wield_index local wield_list = player:get_wield_list() local stack = inv:get_stack(wield_list, wield_index) local written = stack:get_name() == "default:book_written" if stack:get_name() ~= "default:book" and not written then -- No book in the wield slot, abort & inform the player core.chat_send_player(player_name, S("The book you were writing to mysteriously disappeared.")) return end local data = stack:get_meta():to_table().fields local title = data.title or "" local text = data.text or "" if fields.book_header ~= nil and data.owner == player_name then local contents local tab = tonumber(fields.book_header) if tab == 1 then contents = formspec_core(tab) .. formspec_write(title, text) elseif tab == 2 then local lines, string = {}, "" for str in (text .. "\n"):gmatch("([^\n]*)[\n]") do lines[#lines + 1] = str end string = formspec_string(lpp, data.page, lines, string) contents = formspec_read(player_name, title, string, text, data.page, data.page_max) end tab_number = tab local formspec = formspec_size .. formspec_core(tab) .. contents core.show_formspec(player_name, "default:book", formspec) return end if fields.quit then book_writers[player_name] = nil end if fields.save and fields.title and fields.text then local new_stack if not written then local count = stack:get_count() if count == 1 then stack:set_name("default:book_written") else stack:set_count(count - 1) new_stack = ItemStack("default:book_written") end end if data.owner ~= player_name and title ~= "" and text ~= "" then return end if not data then data = {} end data.title = fields.title:sub(1, max_title_size) data.owner = player:get_player_name() local short_title = data.title -- Don't bother triming the title if the trailing dots would make it longer if #short_title > short_title_size + 3 then short_title = short_title:sub(1, short_title_size) .. "..." end data.description = S("\"@1\" by @2", short_title, data.owner) data.text = fields.text:sub(1, max_text_size) data.text = data.text:gsub("\r\n", "\n"):gsub("\r", "\n") data.text = data.text:gsub("[%z\1-\8\11-\31\127]", "") -- strip naughty control characters (keeps \t and \n) data.page = 1 data.page_max = math.ceil((#data.text:gsub("[^\n]", "") + 1) / lpp) if new_stack then new_stack:get_meta():from_table({ fields = data }) if inv:room_for_item("main", new_stack) then inv:add_item("main", new_stack) else core.add_item(player:get_pos(), new_stack) end else stack:get_meta():from_table({ fields = data }) end elseif fields.book_next or fields.book_prev then if not data.page then return end data.page = tonumber(data.page) data.page_max = tonumber(data.page_max) if fields.book_next then data.page = data.page + 1 if data.page > data.page_max then data.page = 1 end else data.page = data.page - 1 if data.page == 0 then data.page = data.page_max end end stack:get_meta():from_table({ fields = data }) stack = book_on_use(stack, player) end -- Update stack inv:set_stack(wield_list, wield_index, stack) end) -- local bookshelf_formspec = -- "size[8,7;]" .. -- "list[context;books;0,0.3;8,2;]" .. -- "list[current_player;main;0,2.85;8,1;]" .. -- "list[current_player;main;0,4.08;8,3;8]" .. -- "listring[context;books]" .. -- "listring[current_player;main]" .. -- default.get_hotbar_bg(0, 2.85) -- local function update_bookshelf(pos) -- local meta = core.get_meta(pos) -- local inv = meta:get_inventory() -- local invlist = inv:get_list("books") -- local formspec = bookshelf_formspec -- -- Inventory slots overlay -- local bx, by = 0, 0.3 -- local n_written, n_empty = 0, 0 -- for i = 1, 16 do -- if i == 9 then -- bx = 0 -- by = by + 1 -- end -- local stack = invlist[i] -- if stack:is_empty() then -- formspec = formspec .. -- "image[" .. bx .. "," .. by .. ";1,1;default_bookshelf_slot.png]" -- else -- local metatable = stack:get_meta():to_table() or {} -- if metatable.fields and metatable.fields.text then -- n_written = n_written + stack:get_count() -- else -- n_empty = n_empty + stack:get_count() -- end -- end -- bx = bx + 1 -- end -- meta:set_string("formspec", formspec) -- if n_written + n_empty == 0 then -- meta:set_string("infotext", S("Empty Bookshelf")) -- else -- meta:set_string("infotext", S("Bookshelf (@1 written, @2 empty books)", n_written, n_empty)) -- end -- end local default_bookshelf_def = { -- description = S("Bookshelf"), drawtype = "mesh", mesh = "voxelmodel.obj", tiles = { "voxelmodel_bookshelf.png" }, use_texture_alpha = "clip", inventory_image = "[inventorycube{default_wood.png{default_wood.png&default_bookshelf.png{default_wood.png&default_wood.png", -- wield_image = -- "[inventorycube{default_wood.png{default_wood.png&default_bookshelf.png{default_wood.png&default_wood.png", paramtype = "light", -- paramtype2 = "facedir", -- is_ground_content = false, -- groups = { choppy = 3, oddly_breakable_by_hand = 2, flammable = 3 }, -- sounds = xcompat.sounds.node_sound_wood_defaults(), -- on_construct = function(pos) -- local meta = core.get_meta(pos) -- local inv = meta:get_inventory() -- inv:set_size("books", 8 * 2) -- update_bookshelf(pos) -- end, -- can_dig = function(pos, player) -- local inv = core.get_meta(pos):get_inventory() -- return inv:is_empty("books") -- end, -- allow_metadata_inventory_put = function(pos, listname, index, stack) -- if core.get_item_group(stack:get_name(), "book") ~= 0 then -- return stack:get_count() -- end -- return 0 -- end, -- on_blast = function(pos) -- local drops = {} -- default.get_inventory_drops(pos, "books", drops) -- drops[#drops + 1] = "default:bookshelf" -- core.remove_node(pos) -- return drops -- end, } -- default.set_inventory_action_loggers(default_bookshelf_def, "bookshelf") core.override_item("default:bookshelf", default_bookshelf_def) core.register_node(":default:book", { description = S("Book"), -- tiles = { "default_book.png" }, tiles = { 'Red-Book-Slices.png' }, -- inventory_image = "default_book.png", -- wield_image = "default_book.png", inventory_image = 'Red-Book-Inv.png', -- wield_image = 'Red-Book-Inv.png', drawtype = 'mesh', paramtype = 'light', paramtype2 = 'facedir', -- selection_box = { -- type = 'fixed', -- fixed = { -8 / 16, -8 / 16, -8 / 16, 8 / 16, -7 / 16, 8 / 16 } -- }, -- mesh = 'extrusion_mesh_16.obj', mesh = 'Book.obj', use_texture_alpha = 'clip', walkable = false, is_ground_content = false, groups = { book = 1, flammable = 3, falling_node = 1, oddly_breakable_by_hand = 3, snappy = 3, choppy = 3, attached_node = 1, dig_immediate = 3 }, on_use = book_on_use, }) core.register_node(":default:book_written", { description = S("Book with Text"), tiles = { "default_book_written.png" }, inventory_image = "default_book_written.png", wield_image = "default_book_written.png", drawtype = 'mesh', paramtype = 'light', paramtype2 = 'facedir', selection_box = { type = 'fixed', fixed = { -8 / 16, -8 / 16, -8 / 16, 8 / 16, -7 / 16, 8 / 16 } }, mesh = 'extrusion_mesh_16.obj', use_texture_alpha = 'clip', walkable = false, is_ground_content = false, groups = { book = 1, not_in_creative_inventory = 1, flammable = 3, falling_node = 1, oddly_breakable_by_hand = 3, snappy = 3, choppy = 3, attached_node = 1, dig_immediate = 3 }, stack_max = 1, on_use = book_on_use, }) core.register_node(":default:clay_brick", { description = S("Clay Brick"), tiles = { "default_clay_brick.png" }, inventory_image = "default_clay_brick.png", wield_image = "default_clay_brick.png", drawtype = 'mesh', paramtype = 'light', paramtype2 = 'facedir', selection_box = { type = 'fixed', fixed = { -8 / 16, -8 / 16, -8 / 16, 8 / 16, -7 / 16, 8 / 16 } }, mesh = 'extrusion_mesh_16.obj', use_texture_alpha = 'clip', walkable = false, is_ground_content = false, groups = { falling_node = 1, oddly_breakable_by_hand = 3, snappy = 3, choppy = 3, attached_node = 1, dig_immediate = 3 }, }) core.register_node(":default:clay_lump", { description = S("Clay Lump"), tiles = { "default_clay_lump.png" }, inventory_image = "default_clay_lump.png", wield_image = "default_clay_lump.png", drawtype = 'mesh', paramtype = 'light', paramtype2 = 'none', selection_box = { type = 'fixed', fixed = { -8 / 16, -8 / 16, -8 / 16, 8 / 16, -7 / 16, 8 / 16 } }, mesh = 'extrusion_mesh_16.obj', use_texture_alpha = 'clip', walkable = false, is_ground_content = false, groups = { falling_node = 1, oddly_breakable_by_hand = 3, snappy = 3, choppy = 3, attached_node = 1, dig_immediate = 3 }, }) core.register_node(":default:coal_lump", { description = S("Coal Lump"), tiles = { "default_coal_lump.png" }, inventory_image = "default_coal_lump.png", wield_image = "default_coal_lump.png", drawtype = 'mesh', paramtype = 'light', paramtype2 = 'none', selection_box = { type = 'fixed', fixed = { -8 / 16, -8 / 16, -8 / 16, 8 / 16, -7 / 16, 8 / 16 } }, mesh = 'extrusion_mesh_16.obj', use_texture_alpha = 'clip', walkable = false, is_ground_content = false, groups = { coal = 1, flammable = 1, falling_node = 1, oddly_breakable_by_hand = 3, snappy = 3, choppy = 3, attached_node = 1, dig_immediate = 3 } }) core.register_node(":default:copper_lump", { description = S("Copper Lump"), tiles = { "default_copper_lump.png" }, inventory_image = "default_copper_lump.png", wield_image = "default_copper_lump.png", drawtype = 'mesh', paramtype = 'light', paramtype2 = 'none', selection_box = { type = 'fixed', fixed = { -8 / 16, -8 / 16, -8 / 16, 8 / 16, -7 / 16, 8 / 16 } }, mesh = 'extrusion_mesh_16.obj', use_texture_alpha = 'clip', walkable = false, is_ground_content = false, groups = { falling_node = 1, oddly_breakable_by_hand = 3, snappy = 3, choppy = 3, attached_node = 1, dig_immediate = 3 }, }) core.register_node(":default:diamond", { description = S("Diamond"), tiles = { "default_diamond.png" }, inventory_image = "default_diamond.png", wield_image = "default_diamond.png", drawtype = 'mesh', paramtype = 'light', paramtype2 = 'none', selection_box = { type = 'fixed', fixed = { -8 / 16, -8 / 16, -8 / 16, 8 / 16, -7 / 16, 8 / 16 } }, mesh = 'extrusion_mesh_16.obj', use_texture_alpha = 'clip', walkable = false, is_ground_content = false, groups = { falling_node = 1, oddly_breakable_by_hand = 3, snappy = 3, choppy = 3, attached_node = 1, dig_immediate = 3 }, }) core.register_node(":default:flint", { description = S("Flint"), tiles = { "default_flint.png" }, inventory_image = "default_flint.png", wield_image = "default_flint.png", drawtype = 'mesh', paramtype = 'light', paramtype2 = 'none', selection_box = { type = 'fixed', fixed = { -8 / 16, -8 / 16, -8 / 16, 8 / 16, -7 / 16, 8 / 16 } }, mesh = 'extrusion_mesh_16.obj', use_texture_alpha = 'clip', walkable = false, is_ground_content = false, groups = { falling_node = 1, oddly_breakable_by_hand = 3, snappy = 3, choppy = 3, attached_node = 1, dig_immediate = 3 }, }) core.register_node(":default:gold_lump", { description = S("Gold Lump"), tiles = { "default_gold_lump.png" }, inventory_image = "default_gold_lump.png", wield_image = "default_gold_lump.png", drawtype = 'mesh', paramtype = 'light', paramtype2 = 'none', selection_box = { type = 'fixed', fixed = { -8 / 16, -8 / 16, -8 / 16, 8 / 16, -7 / 16, 8 / 16 } }, mesh = 'extrusion_mesh_16.obj', use_texture_alpha = 'clip', walkable = false, is_ground_content = false, groups = { falling_node = 1, oddly_breakable_by_hand = 3, snappy = 3, choppy = 3, attached_node = 1, dig_immediate = 3 }, }) core.register_node(":default:iron_lump", { description = S("Iron Lump"), tiles = { "default_iron_lump.png" }, inventory_image = "default_iron_lump.png", wield_image = "default_iron_lump.png", drawtype = 'mesh', paramtype = 'light', paramtype2 = 'none', selection_box = { type = 'fixed', fixed = { -8 / 16, -8 / 16, -8 / 16, 8 / 16, -7 / 16, 8 / 16 } }, mesh = 'extrusion_mesh_16.obj', use_texture_alpha = 'clip', walkable = false, is_ground_content = false, groups = { falling_node = 1, oddly_breakable_by_hand = 3, snappy = 3, choppy = 3, attached_node = 1, dig_immediate = 3 }, }) core.register_node(":default:mese_crystal", { description = S("Mese Crystal"), tiles = { "default_mese_crystal.png" }, inventory_image = "default_mese_crystal.png", wield_image = "default_mese_crystal.png", drawtype = 'mesh', paramtype = 'light', paramtype2 = 'none', selection_box = { type = 'fixed', fixed = { -8 / 16, -8 / 16, -8 / 16, 8 / 16, -7 / 16, 8 / 16 } }, mesh = 'extrusion_mesh_16.obj', use_texture_alpha = 'clip', walkable = false, is_ground_content = false, groups = { falling_node = 1, oddly_breakable_by_hand = 3, snappy = 3, choppy = 3, attached_node = 1, dig_immediate = 3 }, }) core.register_node(":default:mese_crystal_fragment", { description = S("Mese Crystal Fragment"), tiles = { "default_mese_crystal_fragment.png" }, inventory_image = "default_mese_crystal_fragment.png", wield_image = "default_mese_crystal_fragment.png", drawtype = 'mesh', paramtype = 'light', paramtype2 = 'none', selection_box = { type = 'fixed', fixed = { -8 / 16, -8 / 16, -8 / 16, 8 / 16, -7 / 16, 8 / 16 } }, mesh = 'extrusion_mesh_16.obj', use_texture_alpha = 'clip', walkable = false, is_ground_content = false, groups = { falling_node = 1, oddly_breakable_by_hand = 3, snappy = 3, choppy = 3, attached_node = 1, dig_immediate = 3 }, }) core.register_node(":default:obsidian_shard", { description = S("Obsidian Shard"), tiles = { "default_obsidian_shard.png" }, inventory_image = "default_obsidian_shard.png", wield_image = "default_obsidian_shard.png", drawtype = 'mesh', paramtype = 'light', paramtype2 = 'none', selection_box = { type = 'fixed', fixed = { -8 / 16, -8 / 16, -8 / 16, 8 / 16, -7 / 16, 8 / 16 } }, mesh = 'extrusion_mesh_16.obj', use_texture_alpha = 'clip', walkable = false, is_ground_content = false, groups = { falling_node = 1, oddly_breakable_by_hand = 3, snappy = 3, choppy = 3, attached_node = 1, dig_immediate = 3 }, }) core.register_node(":default:paper", { description = S("Paper"), tiles = { "default_paper.png" }, inventory_image = "default_paper.png", wield_image = "default_paper.png", drawtype = 'mesh', paramtype = 'light', paramtype2 = 'facedir', selection_box = { type = 'fixed', fixed = { -8 / 16, -8 / 16, -8 / 16, 8 / 16, -7 / 16, 8 / 16 } }, mesh = 'extrusion_mesh_16.obj', use_texture_alpha = 'clip', walkable = false, is_ground_content = false, groups = { flammable = 3, falling_node = 1, oddly_breakable_by_hand = 3, snappy = 3, choppy = 3, attached_node = 1, dig_immediate = 3 } }) core.register_node(':default:stick', { description = S('Stick'), drawtype = 'mesh', paramtype = 'light', paramtype2 = 'facedir', selection_box = { type = 'fixed', fixed = { -8 / 16, -8 / 16, -8 / 16, 8 / 16, -7 / 16, 8 / 16 } }, mesh = 'extrusion_mesh_16.obj', tiles = { 'default_stick.png' }, use_texture_alpha = 'clip', inventory_image = "default_stick.png", wield_image = "default_stick.png", floodable = true, walkable = false, sunlight_propagates = true, buildable_to = true, is_ground_content = false, groups = { stick = 1, falling_node = 1, oddly_breakable_by_hand = 3, snappy = 3, flammable = 2, choppy = 3, attached_node = 1, dig_immediate = 3, }, sounds = default.node_sound_wood_defaults() }) core.register_node(":default:tin_lump", { description = S("Tin Lump"), tiles = { "default_tin_lump.png" }, inventory_image = "default_tin_lump.png", wield_image = "default_tin_lump.png", drawtype = 'mesh', paramtype = 'light', paramtype2 = 'none', selection_box = { type = 'fixed', fixed = { -8 / 16, -8 / 16, -8 / 16, 8 / 16, -7 / 16, 8 / 16 } }, mesh = 'extrusion_mesh_16.obj', use_texture_alpha = 'clip', walkable = false, is_ground_content = false, groups = { falling_node = 1, oddly_breakable_by_hand = 3, snappy = 3, choppy = 3, attached_node = 1, dig_immediate = 3 }, }) if not core.global_exists('ingots') then local function register_ingots(ingot_item, texture) --checks, whether the item name is a valid item (thanks 'puzzlecube') if not core.registered_items[ingot_item] then core.log("warning", ingot_item .. " is not registered. Skipping ingot registration") return end local node_name = core.get_current_modname() .. ":" .. ingot_item:match(":(.+)$") .. "_big_" --this way there is no need for a separate on_punch function for a stack of 1 ingot core.register_alias(node_name .. "0", "air") --gives the ingot_item the ability to be placed and increase already placed stacks of ingots core.override_item(ingot_item, { -- wield_image = core.registered_items[ingot_item].inventory_image .. '^[transform4', on_place = function(itemstack, placer, pointed_thing) local player_name = placer:get_player_name() local pos = pointed_thing.above if core.is_protected(pos, player_name) then return end local under_node = core.get_node(pointed_thing.under) local under_name = under_node.name local under_def = core.registered_nodes[under_name] if under_def._ingot_count and under_def._ingot_count < 8 then core.set_node(pointed_thing.under, { name = node_name .. under_def._ingot_count + 1, param2 = under_node.param2 }) if not core.is_creative_enabled(player_name) then itemstack:take_item() end else local _, pos = core.item_place(ItemStack(node_name .. "1"), placer, pointed_thing) if pos and not core.is_creative_enabled(player_name) then itemstack:take_item() end end return itemstack end }) --registers 'stack_size' number of nodes, each has one more ingot in it than the last for i = 1, 8 do local box = { type = "fixed", fixed = { --rectangular box which encompases all placed ingots { -0.5, -0.5, -0.5, 0.5, (((i + 1 - ((i + 1) % 2)) / 8) - 0.5), 0.5 } }, } core.register_node(":" .. node_name .. i, { description = "ingots", drawtype = "mesh", tiles = { texture }, mesh = "ingot_big_" .. i .. ".obj", selection_box = box, collision_box = box, paramtype = "light", paramtype2 = "facedir", groups = { cracky = 3, level = 2, not_in_creative_inventory = 1 }, drop = ingot_item .. " " .. i, on_punch = function(pos, node, puncher, pointed_thing) if not puncher then return end local player_name = puncher:get_player_name() local wield = puncher:get_wielded_item() if core.is_protected(pos, player_name) then return end --checks, so that a stack can be taken appart only by hand or relevant ingot_item if wield:get_name() == ingot_item or wield:get_count() == 0 then core.set_node(pos, { name = node_name .. i - 1, param2 = node.param2 }) if not core.is_creative_enabled(player_name) then local stack = ItemStack(ingot_item) puncher:get_inventory():add_item("main", stack) end end end, _ingot_count = i, }) core.register_alias("ingots:" .. ingot_item:match(":(.+)$") .. "_big_" .. i, core.get_current_modname() .. ":" .. ingot_item:match(":(.+)$") .. "_big_" .. i) end end register_ingots("default:copper_ingot", "ingot_copper.png") register_ingots("default:tin_ingot", "ingot_tin.png") register_ingots("default:bronze_ingot", "ingot_bronze.png") register_ingots("default:steel_ingot", "ingot_steel.png") register_ingots("default:gold_ingot", "ingot_gold.png") else for i = 1, 8 do core.register_alias(core.get_current_modname() .. ":copper_ingot_big_" .. i, "ingots:copper_ingot_big_" .. i) core.register_alias(core.get_current_modname() .. ":tin_ingot_big_" .. i, "ingots:tin_ingot_big_" .. i) core.register_alias(core.get_current_modname() .. ":bronze_ingot_big_" .. i, "ingots:bronze_ingot_big_" .. i) core.register_alias(core.get_current_modname() .. ":steel_ingot_big_" .. i, "ingots:steel_ingot_big_" .. i) core.register_alias(core.get_current_modname() .. ":gold_ingot_big_" .. i, "ingots:gold_ingot_big_" .. i) end end