local S = default.S local C = default.colors local esc = minetest.formspec_escape 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 function gold(s) return minetest.colorize("#ff0", s) end 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 = minetest.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_b64 and minetest.decode_base64(data.title_b64) or data.title or "" text = data.text_b64 and minetest.decode_base64(data.text_b64) or 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 item_name = itemstack:get_name() local formspec = "size[9,8.75]" .. default.gui_bg .. default.gui_bg_img .. default.gui_close_btn() .. "item_image[0,-0.1;1,1;" .. item_name .. "]" if owner == player_name then formspec = formspec .. "label[0.9,0.1;" .. esc(S("Book")) .. "]" .. "field[0.5,1.8;8.5,0;title;" .. esc(S("Title:")) .. ";" .. esc(title) .. "]" .. "textarea[0.5,2.25;8.6,6.75;text;" .. esc(S("Contents:")) .. ";" .. esc(text) .. "]" .. "button_exit[3,8.1;3,1;save;" .. esc(S("Save")) .. "]" else formspec = formspec .. "label[0.9,0.1;" .. esc(S("Book")) .. ": " .. "\"" .. esc(gold(title)) .. "\", " .. esc(S("by @1", owner)) .. "]" .. "textarea[0.5,0.9;8.5,8;;" .. esc(string ~= "" and string or text) .. ";]" .. "image_button[0.1,8.2;0.75,0.75;formspec_prev.png;book_prev;;true;false;formspec_prev_pressed.png]" .. "image_button[3,8.2;3,0.75;blank.png;;" .. S("Page: @1 of @2", gold(page), gold(page_max)) .. ";false;false;]" .. "image_button[8.1,8.2;0.75,0.75;formspec_next.png;book_next;;true;false;formspec_next_pressed.png]" end minetest.show_formspec(player_name, "default:book", formspec) return itemstack end local max_text_size = 10000 local max_title_size = 50 local short_title_size = 30 local single_character_escapes = {[34] = true, [92] = true, [47] = true, [8] = true, [12] = true, [10] = true, [13] = true, [9] = true} local function get_itemstack_meta_len(text) local size = 0 for i = 1, #text do local char = text:byte(i) if single_character_escapes[char] then size = size + 2 elseif char >= 32 and char <= 126 then -- ASCII printable characters are added as-is (so long as they -- don't need to be escaped) size = size + 1 else -- Minetest's ItemStack metadata encodes every other byte (not -- character) as \u00 -- This isn't valid JSON size = size + 6 end end return size end -- Like text:sub(1, max_size) but won't split a multi-byte character (which -- causes the text to be shown as or something) local function safely_trim_to_length(text, max_size) if #text > max_size then return utf8.remove(text:sub(1, max_size + 1)) end return text end local function set_optionally_b64_field(data, field_name, text) -- 4 is added to the base64 length for "_b64" if get_itemstack_meta_len(text) > math.ceil(#text / 0.75) + 4 then data[field_name] = nil data[field_name .. "_b64"] = minetest.encode_base64(text) else data[field_name] = text data[field_name .. "_b64"] = nil end end minetest.register_on_player_receive_fields(function(player, formname, fields) if formname ~= "default:book" then return end local inv = player:get_inventory() local stack = player:get_wielded_item() if fields.save and fields.title and fields.text and fields.title ~= "" and fields.text ~= "" then local new_stack, data if stack:get_name() ~= "default:book_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 else data = stack:get_meta():to_table().fields end if data and data.owner and data.owner ~= player:get_player_name() then return end if not data then data = {} end data.owner = player:get_player_name() -- Title local title = safely_trim_to_length(fields.title, max_title_size) set_optionally_b64_field(data, "title", title) -- Description local short_title = 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 = safely_trim_to_length(short_title, short_title_size) .. "..." end data.description = S("\"@1\" by @2", short_title, data.owner) -- Text local text = safely_trim_to_length(fields.text, max_text_size) text = text:gsub("\r\n", "\n"):gsub("\r", "\n") set_optionally_b64_field(data, "text", text) data.page = 1 data.page_max = math.ceil((#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 minetest.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 local data = stack:get_meta():to_table().fields if not data or 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 (check current stack first) local wield_item = player:get_wielded_item():get_name() if wield_item == "default:book" or wield_item == "default:book_written" then player:set_wielded_item(stack) end end) -- -- Craftitem registry -- minetest.register_craftitem("default:blueberries", { description = S("Blueberries"), inventory_image = "default_blueberries.png", groups = {food = 1, food_blueberries = 1, food_berry = 1}, on_use = minetest.item_eat(1) }) minetest.register_craftitem("default:book", { description = S("Book"), inventory_image = "default_book.png", groups = {book = 1, flammable = 3}, on_use = book_on_use }) minetest.register_craftitem("default:book_written", { description = S("Book with Text"), inventory_image = "default_book_written.png", groups = {book = 1, not_in_creative_inventory = 1, flammable = 3}, stack_max = 1, on_use = book_on_use }) minetest.register_craftitem("default:clay_brick", { description = S("Clay Brick"), inventory_image = "default_clay_brick.png" }) minetest.register_craftitem("default:clay_lump", { description = S("Clay Lump"), inventory_image = "default_clay_lump.png" }) minetest.register_craftitem("default:coal_lump", { description = S("Coal Lump"), inventory_image = "default_coal_lump.png", groups = {coal = 1, flammable = 1} }) minetest.register_craftitem("default:charcoal_lump", { description = S("Charcoal Lump"), inventory_image = "default_charcoal_lump.png", groups = {coal = 1, flammable = 1} }) minetest.register_craftitem("default:diamond", { description = S("Diamond"), inventory_image = "default_diamond.png" }) minetest.register_craftitem("default:flint", { description = S("Flint"), inventory_image = "default_flint.png" }) minetest.register_craftitem("default:gold_ingot", { description = S("Gold Ingot"), inventory_image = "default_gold_ingot.png" }) minetest.register_craftitem("default:paper", { description = S("Paper"), inventory_image = "default_paper.png", groups = {flammable = 3} }) minetest.register_craftitem("default:steel_ingot", { description = S("Steel Ingot"), inventory_image = "default_steel_ingot.png" }) minetest.register_craftitem("default:stick", { description = S("Stick"), inventory_image = "default_stick.png", groups = {stick = 1, flammable = 2, wieldview = 2} }) minetest.register_craftitem("default:emerald", { description = C.emerald .. S("Emerald"), inventory_image = "default_emerald.png" }) minetest.register_craftitem("default:ruby", { description = C.ruby .. S("Ruby"), inventory_image = "default_ruby.png" }) minetest.register_craftitem("default:gunpowder", { description = S("Gunpowder"), inventory_image = "default_gunpowder.png" }) minetest.register_craftitem("default:bone", { description = S("Bone"), inventory_image = "default_bone.png", groups = {wieldview = 2} }) minetest.register_craftitem("default:glowstone_dust", { description = S("Glowstone Dust"), inventory_image = "default_glowstone_dust.png" }) minetest.register_craftitem("default:sugar", { description = S("Sugar"), inventory_image = "default_sugar.png" }) minetest.register_craftitem("default:snowball", { description = S("Snowball"), inventory_image = "default_snowball.png", stack_max = 16, groups = {flammable = 3}, on_place = function(itemstack, placer, pointed_thing) if minetest.item_place_node(ItemStack("default:snow"), placer, pointed_thing) then if not minetest.is_creative_enabled(placer:get_player_name()) then itemstack:take_item() end end return itemstack end }) -- -- Crafting recipes -- minetest.register_craft({ output = "default:book", recipe = { {"default:paper"}, {"default:paper"}, {"default:paper"} } }) default.register_craft_metadata_copy("default:book", "default:book_written") minetest.register_craft({ output = "default:clay_brick 4", recipe = { {"default:brick"} } }) minetest.register_craft({ output = "default:coal_lump 9", recipe = { {"default:coalblock"} } }) minetest.register_craft({ output = "default:diamond 9", recipe = { {"default:diamondblock"} } }) minetest.register_craft({ output = "default:gold_ingot 9", recipe = { {"default:goldblock"} } }) minetest.register_craft({ output = "default:paper", recipe = { {"default:sugarcane", "default:sugarcane", "default:sugarcane"} } }) minetest.register_craft({ output = "default:steel_ingot 9", recipe = { {"default:steelblock"} } }) minetest.register_craft({ output = "default:stick 4", recipe = { {"group:wood"}, {"group:wood"} } }) minetest.register_craft({ output = "default:snowball 9", recipe = { {"default:snowblock"} } }) minetest.register_craft({ output = "default:emerald 9", recipe = { {"default:emeraldblock"} } }) minetest.register_craft({ output = "default:ruby 9", recipe = { {"default:rubyblock"} } }) minetest.register_craft({ output = "default:glowstone_dust 4", recipe = { {"default:glowstone"} } }) minetest.register_craft({ type = "shapeless", output = "default:gunpowder", recipe = { "default:sand", "default:gravel" } }) minetest.register_craft({ output = "default:sugar 2", recipe = { {"default:sugarcane"} } }) -- -- Cooking recipes -- minetest.register_craft({ type = "cooking", output = "default:clay_brick", recipe = "default:clay_lump" }) minetest.register_craft({ type = "cooking", output = "default:hardened_clay", recipe = "default:clay" }) minetest.register_craft({ type = "cooking", output = "default:gold_ingot", recipe = "default:stone_with_gold" }) minetest.register_craft({ type = "cooking", output = "default:steel_ingot", recipe = "default:stone_with_iron" }) minetest.register_craft({ type = "cooking", output = "default:diamond", recipe = "default:stone_with_diamond" }) minetest.register_craft({ type = "cooking", output = "default:charcoal_lump", recipe = "group:tree" }) minetest.register_craft({ type = "cooking", output = "default:coal_lump", recipe = "default:stone_with_coal" }) -- -- Fuels -- minetest.register_craft({ type = "fuel", recipe = "default:book", burntime = 3 }) minetest.register_craft({ type = "fuel", recipe = "default:book_written", burntime = 3 }) minetest.register_craft({ type = "fuel", recipe = "default:coal_lump", burntime = 60 }) minetest.register_craft({ type = "fuel", recipe = "default:charcoal_lump", burntime = 60 }) minetest.register_craft({ type = "fuel", recipe = "default:paper", burntime = 1 }) minetest.register_craft({ type = "fuel", recipe = "group:stick", burntime = 1 })