Build 01
- initial beta version
193
README.txt
Normal file
@ -0,0 +1,193 @@
|
||||
Books Redux Mod v1.0
|
||||
By Leslie Krause
|
||||
|
||||
Books Redux is full rewrite of default books from Minetest Game, offering players a rich
|
||||
selection of books and papers for in-game communication.
|
||||
|
||||
* Choose from 4 different cover and page styles for books
|
||||
* Choose from 12 different page styles for papers
|
||||
* Choose from a wide variety of background color schemes
|
||||
* Page sizes can range from small, large, wide, and tall
|
||||
* Bookmarks can be added, removed, or recalled on-the-fly
|
||||
* Edits to books and papers can be previewed before saving
|
||||
* Simple page navigation for editing and viewing books
|
||||
* All content is encrypted using a 128-bit block cipher
|
||||
* Legacy books can still be viewed in compatibility mode
|
||||
|
||||
Even better still, books and papers are both easily craftable using the same traditional
|
||||
recipes as before: 3 papyrus for one paper, and 3 paper for one book.
|
||||
|
||||
With so many possible combinations of sizes, colors, and styles, the design possibilities
|
||||
are virtually endless! Moreover, custom layout and formatting of text is also possible
|
||||
using the Bedrock Markup Language. In fact, the editor for books and papers should be
|
||||
very familiar to those accustomed to the Signs Redux mod.
|
||||
|
||||
https://forum.minetest.net/viewtopic.php?f=9&t=23955
|
||||
|
||||
Of course, plain-text messages are acceptable as well, as they will render exactly as
|
||||
they are input, without any fancy colors or styles. Then again, that would be boring ;)
|
||||
|
||||
Now for an important word about security....
|
||||
|
||||
Last year one of my more vigilant players made me aware that it is possible for anyone
|
||||
to inspect locked chests on servers with only basic CSM knowledge. This means that
|
||||
confidential information in written books is vulnerable to intrusion.
|
||||
|
||||
Fortunately, Books Redux addresses this very concern by encrypting the content of all
|
||||
written books and written papers via the Simple Cipher mod. This means that if anyone
|
||||
gains access to a copy of the map, without the server operator's private key, they
|
||||
won't be able to examine the personal correspondence of other players.
|
||||
|
||||
Currently there is no way to automatically convert legacy books to the new format. It
|
||||
must be done manually by copying and pasting text into a new book.
|
||||
|
||||
|
||||
Repository
|
||||
----------------------
|
||||
|
||||
Browse source code:
|
||||
https://bitbucket.org/sorcerykid/books_rx
|
||||
|
||||
Download archive:
|
||||
https://bitbucket.org/sorcerykid/books_rx/get/master.zip
|
||||
https://bitbucket.org/sorcerykid/books_rx/get/master.tar.gz
|
||||
|
||||
Compatability
|
||||
----------------------
|
||||
|
||||
Minetest 0.4.15+ required
|
||||
|
||||
Dependencies
|
||||
----------------------
|
||||
|
||||
Default Mod (required)
|
||||
https://github.com/minetest-game-mods/default
|
||||
|
||||
ActiveFormspecs Mod (required)
|
||||
https://bitbucket.org/sorcerykid/formspecs
|
||||
|
||||
Bedrock Markup Language Mod (required)
|
||||
https://bitbucket.org/sorcerykid/markup
|
||||
|
||||
Safety Deposit Mod (required)
|
||||
https://bitbucket.org/sorcerykid/safety_deposit
|
||||
|
||||
Installation
|
||||
----------------------
|
||||
|
||||
1) Unzip the archive into the mods directory of your game
|
||||
2) Rename the books_rx-master directory to "books_rx"
|
||||
|
||||
|
||||
Source Code License
|
||||
----------------------------------------------------------
|
||||
|
||||
GNU Lesser General Public License v3 (LGPL-3.0)
|
||||
|
||||
Copyright (c) 2019-2020, Leslie E. Krause
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it under the terms of
|
||||
the GNU Lesser General Public License as published by the Free Software Foundation; either
|
||||
version 3 of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
See the GNU Lesser General Public License for more details.
|
||||
|
||||
http://www.gnu.org/licenses/lgpl-2.1.html
|
||||
|
||||
|
||||
Multimedia License (textures, sounds, and models)
|
||||
----------------------------------------------------------
|
||||
|
||||
Attribution-ShareAlike 3.0 Unported (CC BY-SA 3.0)
|
||||
|
||||
/textures/default_paper_written.png
|
||||
obtained from https://github.com/Mossmanikin/memorandum
|
||||
by Mossmanikin
|
||||
modified by sorcerykid
|
||||
|
||||
/textures/book_classic_cover.png
|
||||
obtained from https://www.onlygfx.com/7-old-book-open-textures-jpg/
|
||||
by zolee
|
||||
modified by sorcerykid
|
||||
|
||||
/textures/book_classic_front.png
|
||||
obtained from https://colorlava.com/freebies/book-cover-texture-designs/
|
||||
modified by sorcerykid
|
||||
|
||||
/textures/book_plain_cover.png
|
||||
obtained from https://www.onlygfx.com/7-old-book-open-textures-jpg/
|
||||
by zolee
|
||||
modified by sorcerykid
|
||||
|
||||
/textures/book_plain_front.png
|
||||
obtained from https://colorlava.com/freebies/book-cover-texture-designs/
|
||||
modified by sorcerykid
|
||||
|
||||
/textures/book_vintage_cover.png
|
||||
obtained from https://www.onlygfx.com/7-old-book-open-textures-jpg/
|
||||
by zolee
|
||||
modified by sorcerykid
|
||||
|
||||
/textures/book_vintage_front.png
|
||||
obtained from https://colorlava.com/freebies/book-cover-texture-designs/
|
||||
modified by sorcerykid
|
||||
|
||||
/textures/paper_classic1.png
|
||||
obtained from http://designbeep.com/2012/09/24/40-free-high-resolution-old-book-textures-for-designers/
|
||||
modified by sorcerykid
|
||||
|
||||
/textures/paper_classic2.png
|
||||
obtained from http://designbeep.com/2012/09/24/40-free-high-resolution-old-book-textures-for-designers/
|
||||
modified by sorcerykid
|
||||
|
||||
/textures/paper_plain1.png
|
||||
obtained from http://designbeep.com/2012/09/24/40-free-high-resolution-old-book-textures-for-designers/
|
||||
modified by sorcerykid
|
||||
|
||||
/textures/paper_plain2.png
|
||||
obtained from http://designbeep.com/2012/09/24/40-free-high-resolution-old-book-textures-for-designers/
|
||||
modified by sorcerykid
|
||||
|
||||
/textures/paper_vintage1.png
|
||||
obtained from http://designbeep.com/2012/09/24/40-free-high-resolution-old-book-textures-for-designers/
|
||||
modified by sorcerykid
|
||||
|
||||
/textures/paper_vintage2.png
|
||||
obtained from http://designbeep.com/2012/09/24/40-free-high-resolution-old-book-textures-for-designers/
|
||||
modified by sorcerykid
|
||||
|
||||
/textures/paper_vintage3.png
|
||||
obtained from http://designbeep.com/2012/09/24/40-free-high-resolution-old-book-textures-for-designers/
|
||||
modified by sorcerykid
|
||||
|
||||
/textures/paper_vintage4.png
|
||||
obtained from http://designbeep.com/2012/09/24/40-free-high-resolution-old-book-textures-for-designers/
|
||||
modified by sorcerykid
|
||||
|
||||
|
||||
You are free to:
|
||||
Share — copy and redistribute the material in any medium or format.
|
||||
Adapt — remix, transform, and build upon the material for any purpose, even commercially.
|
||||
The licensor cannot revoke these freedoms as long as you follow the license terms.
|
||||
|
||||
Under the following terms:
|
||||
|
||||
Attribution — You must give appropriate credit, provide a link to the license, and
|
||||
indicate if changes were made. You may do so in any reasonable manner, but not in any way
|
||||
that suggests the licensor endorses you or your use.
|
||||
|
||||
No additional restrictions — You may not apply legal terms or technological measures that
|
||||
legally restrict others from doing anything the license permits.
|
||||
|
||||
Notices:
|
||||
|
||||
You do not have to comply with the license for elements of the material in the public
|
||||
domain or where your use is permitted by an applicable exception or limitation.
|
||||
No warranties are given. The license may not give you all of the permissions necessary
|
||||
for your intended use. For example, other rights such as publicity, privacy, or moral
|
||||
rights may limit how you use the material.
|
||||
|
||||
For more details:
|
||||
http://creativecommons.org/licenses/by-sa/3.0/
|
13
config.lua
Normal file
@ -0,0 +1,13 @@
|
||||
max_book_title_length = 50
|
||||
max_book_message_length = 800
|
||||
max_book_content_length = 5000
|
||||
max_paper_subject_length = 50
|
||||
max_paper_message_length = 800
|
||||
default_book_title = "Untitled"
|
||||
default_book_scale = "small"
|
||||
default_book_style = "plain"
|
||||
default_book_color = "white"
|
||||
default_paper_subject = "Untitled"
|
||||
default_paper_scale = "small"
|
||||
default_paper_style = "plain"
|
||||
default_paper_color = "white"
|
4
depends.txt
Normal file
@ -0,0 +1,4 @@
|
||||
default
|
||||
markup
|
||||
safety_deposit
|
||||
config
|
727
init.lua
Normal file
@ -0,0 +1,727 @@
|
||||
--------------------------------------------------------
|
||||
-- Minetest :: Books Redux Mod (books_rx)
|
||||
--
|
||||
-- See README.txt for licensing and other information.
|
||||
-- Copyright (c) 2019-2020, Leslie E. Krause
|
||||
--
|
||||
-- ./games/minetest_game/mods/books_rx/init.lua
|
||||
--------------------------------------------------------
|
||||
|
||||
local config = minetest.load_config( )
|
||||
|
||||
local fs = minetest.formspec_escape
|
||||
local open_book_viewer, open_book_editor, open_paper_viewer, open_paper_editor
|
||||
|
||||
-----------------------
|
||||
|
||||
function open_defbook_viewer( player_name, owner, title, text )
|
||||
|
||||
local function get_formspec( )
|
||||
local formspec = "size[8.0,8.5]" ..
|
||||
default.gui_bg ..
|
||||
default.gui_bg_img ..
|
||||
"label[0.3,0.2;Title:]" ..
|
||||
"label[1.5,0.2;" .. title .. "]" ..
|
||||
"label[0.3,0.8;By:]" ..
|
||||
"label[1.5,0.8;" .. owner .. "]" ..
|
||||
"textarea[0.5,1.5;7.6,7.0;content;;" .. minetest.formspec_escape( text ) .. "]" ..
|
||||
"button_exit[3.0,7.8;2.0,0.8;close;Close]"
|
||||
|
||||
return formspec
|
||||
end
|
||||
|
||||
minetest.create_form( nil, player_name, get_formspec( ) )
|
||||
end
|
||||
|
||||
function open_book_viewer( player_name, oldtime, owner, title, scale, style, color, pages, marks, page_idx, vars, is_preview )
|
||||
|
||||
local book_scales = {
|
||||
small = { width = 12.0, height = 8.5 },
|
||||
tall = { width = 12.0, height = 11.0 },
|
||||
large = { width = 15.5, height = 11.0 },
|
||||
wide = { width = 15.5, height = 8.5 },
|
||||
}
|
||||
local book_colors = {
|
||||
gray = "#333333",
|
||||
brown = "#663300",
|
||||
teal = "#006633",
|
||||
purple = "#330066",
|
||||
olive = "#336600",
|
||||
indigo = "#003366",
|
||||
maroon = "#660033",
|
||||
red = "#660000",
|
||||
green = "#006600",
|
||||
blue = "#000066",
|
||||
}
|
||||
local mark_ver = 1
|
||||
|
||||
local function add_bookmark( )
|
||||
if not table.get_index( marks, page_idx ) then
|
||||
table.insert( marks, page_idx )
|
||||
table.sort( marks )
|
||||
end
|
||||
end
|
||||
|
||||
local function del_bookmark( )
|
||||
local idx = table.get_index( marks, page_idx )
|
||||
if idx then
|
||||
table.remove( marks, idx )
|
||||
end
|
||||
end
|
||||
|
||||
local function get_formspec( )
|
||||
local page_width = book_scales[ scale ].width / 2
|
||||
local page_height = book_scales[ scale ].height
|
||||
|
||||
local foot_x1 = ( page_width - 4.5 ) / 2 + 0.3 -- horz pos of left-hand footer
|
||||
local foot_x2 = page_width + ( page_width - 4.5 ) / 2 - 0.3 -- horz pos of right-hand footer
|
||||
local foot_y = page_height - 1.3 -- vert pos of both footers
|
||||
|
||||
if page_idx == 1 then
|
||||
-- render background and controls of front cover
|
||||
local formspec =
|
||||
string.format( "size[%0.2f,%0.2f]", page_width, page_height ) ..
|
||||
default.gui_bg ..
|
||||
string.format( "background[0.0,0.0;%0.2f,%0.2f;%s;false]", page_width, page_height,
|
||||
fs( "book_" .. style .. "_front.png^[colorize:" .. book_colors[ color ] .. "66^[noalpha" ) ) ..
|
||||
|
||||
string.format( "%s[%0.2f,%0.2f;2.0,1.0;close;Close]",
|
||||
is_preview and "button" or "button_exit", foot_x1 + 0.8, foot_y ) ..
|
||||
string.format( "button[%0.2f,%0.2f;1.0,1.0;next_page;>>]",
|
||||
foot_x1 + 2.7, foot_y )
|
||||
|
||||
-- render front cover content
|
||||
local page_x1 = 0.8
|
||||
local page_y1 = 0.5
|
||||
local page_x2 = page_x1 + page_width - 1.4
|
||||
local page_y2 = page_y1 + page_height - 1.5
|
||||
|
||||
formspec = formspec ..
|
||||
markup.get_formspec_string( markup.parse_message( pages[ page_idx ], vars ),
|
||||
page_x1, page_y1, page_x2, page_y2, "#666666", "#CCCCCC" )
|
||||
|
||||
return formspec
|
||||
else
|
||||
-- render background and controls of page
|
||||
local mark_idx = table.get_index( marks, page_idx )
|
||||
|
||||
local formspec =
|
||||
string.format( "size[%0.2f,%0.2f]", page_width * 2, page_height ) ..
|
||||
default.gui_bg ..
|
||||
string.format( "background[0.0,0.0;%0.2f,%0.2f;%s;false]", page_width, page_height,
|
||||
fs( "book_" .. style .. "_cover.png^[colorize:" .. book_colors[ color ] .. "66^[noalpha" ) ) ..
|
||||
|
||||
string.format( "background[%0.2f,0.0;%0.2f,%0.2f;%s;false]", page_width, page_width, page_height,
|
||||
fs( "book_" .. style .. "_cover.png^[transformFX^[colorize:" .. book_colors[ color ] .. "66^[noalpha" ) ) ..
|
||||
|
||||
string.format( "button[%0.2f,%0.2f;1.0,1.0;prev_page;<<]", foot_x2, foot_y ) ..
|
||||
string.format( "button[%0.2f,%0.2f;1.0,1.0;next_page;>>]", foot_x2 + 1.0, foot_y ) ..
|
||||
string.format( "%s[%0.2f,%0.2f;2.0,1.0;close;Close]",
|
||||
is_preview and "button" or "button_exit", foot_x2 + 2.5, foot_y ) ..
|
||||
|
||||
string.format( "label[%0.2f,%0.2f;Bookmarks:]", foot_x1, foot_y + 0.2 )
|
||||
|
||||
if #marks > 0 then
|
||||
formspec = formspec ..
|
||||
string.format( "dropdown[%0.2f,%0.2f;1.8,1.0;marks_%d;,%s;%d;true]", foot_x1 + 1.5, foot_y + 0.1, mark_ver,
|
||||
table.join( marks, ",", function ( a, b ) return "Page " .. b - 1 end ),
|
||||
mark_idx and mark_idx + 1 or 1 )
|
||||
else
|
||||
formspec = formspec ..
|
||||
string.format( "dropdown[%0.2f,%0.2f;1.8,1.0;marks;;1;true]", foot_x1 + 1.5, foot_y + 0.1 )
|
||||
end
|
||||
if mark_idx then
|
||||
formspec = formspec ..
|
||||
string.format( "button[%0.2f,%0.2f;1.2,1.0;del_mark;Del]", foot_x1 + 3.3, foot_y )
|
||||
else
|
||||
formspec = formspec ..
|
||||
string.format( "button[%0.2f,%0.2f;1.2,1.0;add_mark;Add]", foot_x1 + 3.3, foot_y )
|
||||
end
|
||||
|
||||
-- render left-hand page content
|
||||
local page_x1 = 1.0
|
||||
local page_y1 = 0.5
|
||||
local page_x2 = page_x1 + page_width - 1.4
|
||||
local page_y2 = page_y1 + page_height - 1.8
|
||||
|
||||
local text_colors = {
|
||||
gray = "#999999",
|
||||
cyan = "#00EEEE",
|
||||
magenta = "#EE00EE",
|
||||
yellow = "#EEEE00",
|
||||
red = "#CC0000",
|
||||
green = "#00CC00",
|
||||
blue = "#0000CC",
|
||||
brown = "#CC9900",
|
||||
teal = "#00CC99",
|
||||
purple = "#9900CC",
|
||||
olive = "#99CC00",
|
||||
indigo = "#0099CC",
|
||||
maroon = "#CC0099",
|
||||
}
|
||||
|
||||
formspec = formspec ..
|
||||
markup.get_formspec_string( markup.parse_message( pages[ page_idx ], vars, { colors = text_colors } ),
|
||||
page_x1, page_y1, page_x2, page_y2, "#666666", "#CCCCCC" )
|
||||
|
||||
if pages[ page_idx + 1 ] then
|
||||
-- render right-hand page content, if it exists
|
||||
page_x1 = page_x1 + page_width - 0.6
|
||||
page_x2 = page_x2 + page_width - 0.6
|
||||
|
||||
formspec = formspec .. markup.get_formspec_string( markup.parse_message( pages[ page_idx + 1 ], vars ),
|
||||
page_x1, page_y1, page_x2, page_y2, "#666666", "#CCCCCC" )
|
||||
end
|
||||
|
||||
return formspec
|
||||
end
|
||||
end
|
||||
|
||||
local function on_close( meta, player, fields )
|
||||
local fields_marks = fields[ "marks_" .. mark_ver ]
|
||||
|
||||
if fields.close then
|
||||
if is_preview then
|
||||
open_book_editor( player_name, oldtime, owner, title, scale, style, color, pages, page_idx, vars )
|
||||
|
||||
elseif mark_ver > 1 then
|
||||
local itemstack = player:get_wielded_item( )
|
||||
local player_inv = player:get_inventory( )
|
||||
local data = {
|
||||
oldtime = oldtime,
|
||||
owner = owner,
|
||||
title = title,
|
||||
scale = scale,
|
||||
style = style,
|
||||
color = color,
|
||||
pages = pages,
|
||||
marks = marks,
|
||||
}
|
||||
safety_deposit.encrypt_metadata( itemstack, data )
|
||||
player:set_wielded_item( itemstack )
|
||||
|
||||
minetest.chat_send_player( player_name, "Your bookmarks were updated!" )
|
||||
end
|
||||
|
||||
elseif fields.next_page then
|
||||
if page_idx < #pages then
|
||||
page_idx = page_idx + 1
|
||||
minetest.update_form( player_name, get_formspec( ) )
|
||||
end
|
||||
|
||||
elseif fields.prev_page then
|
||||
if page_idx > 1 then
|
||||
page_idx = page_idx - 1
|
||||
minetest.update_form( player_name, get_formspec( ) )
|
||||
end
|
||||
|
||||
elseif fields.add_mark then
|
||||
mark_ver = mark_ver + 1
|
||||
add_bookmark( page_idx )
|
||||
minetest.update_form( player_name, get_formspec( ) )
|
||||
|
||||
elseif fields.del_mark then
|
||||
mark_ver = mark_ver + 1
|
||||
del_bookmark( page_idx )
|
||||
minetest.update_form( player_name, get_formspec( ) )
|
||||
|
||||
elseif fields_marks then -- overcome bug with dropdowns not updating values in older clients
|
||||
page_idx = marks[ fields_marks - 1 ] or 1
|
||||
minetest.update_form( player_name, get_formspec( ) )
|
||||
end
|
||||
end
|
||||
|
||||
-- basic sanity check for proper inputs
|
||||
if not book_scales[ scale ] then scale = config.default_book_scale end
|
||||
if not book_colors[ color ] then color = config.default_book_color end
|
||||
if page_idx < 1 or page_idx > #pages then page_idx = 1 end
|
||||
|
||||
minetest.create_form( nil, player_name, get_formspec( ), on_close, is_preview and minetest.FORMSPEC_SIGSTOP or nil )
|
||||
end
|
||||
|
||||
function open_book_editor( player_name, oldtime, owner, title, scale, style, color, pages, page_idx, vars )
|
||||
|
||||
local function sanitize( buf )
|
||||
return string.trim( string.gsub( buf, ".", { ["\r"] = "\n", ["\t"] = " ", ["\f"] = "?", ["\b"] = "?" } ) ) .. "\n"
|
||||
end
|
||||
|
||||
local function get_formspec( )
|
||||
local book_scales = { "small", "large", "tall", "wide" }
|
||||
local book_colors = { "gray", "brown", "teal", "purple", "olive", "indigo", "maroon", "red", "green", "blue" }
|
||||
local book_styles = { "plain", "fancy", "classic", "vintage" }
|
||||
|
||||
-- book texture filenames:
|
||||
-- books_cover_<style>_<scale>.png
|
||||
-- books_pages_<style>_<scale>.png
|
||||
|
||||
local message = pages[ page_idx ]
|
||||
|
||||
local formspec =
|
||||
"size[10.5,10.0]" ..
|
||||
default.gui_bg ..
|
||||
default.gui_bg_img ..
|
||||
|
||||
"label[0.0,0.1;Book Title:]" ..
|
||||
"field[1.7,0.2;9.1,1.0;title;;" .. fs( title ) .. "]" ..
|
||||
"textarea[0.3,1.2;10.5,6.7;message;Enter the message to display on the page (" .. config.max_book_content_length .. " character limit per book);" .. fs( message ) .. "]" ..
|
||||
|
||||
"label[0.0,7.3;Book Color:]" ..
|
||||
"dropdown[1.6,7.2;1.8,1.0;color;" .. table.concat( book_colors, "," ) .. ";" .. table.get_index( book_colors, color, 1 ) .. ";false]" ..
|
||||
|
||||
"label[3.5,7.3;Book Style:]" ..
|
||||
"dropdown[5.1,7.2;1.8,1.0;style;" .. table.concat( book_styles, "," ) .. ";" .. table.get_index( book_styles, style, 1 ) .. ";false]" ..
|
||||
|
||||
"label[7.0,7.3;Book Scale:]" ..
|
||||
"dropdown[8.6,7.2;1.8,1.0;scale;" .. table.concat( book_scales, "," ) .. ";" .. table.get_index( book_scales, scale, 1 ) .. ";false]" ..
|
||||
|
||||
"button[0.0,8.3;1.0,0.3;cover_page;|<<]" ..
|
||||
"button[1.0,8.3;1.0,0.3;prev_page;<<]" ..
|
||||
"button[2.0,8.3;1.0,0.3;next_page;>>]" ..
|
||||
"button[3.0,8.3;1.0,0.3;final_page;>>|]" ..
|
||||
"button[8.5,8.3;2.0,0.3;add_page;Add Page]"
|
||||
|
||||
if page_idx == 1 then
|
||||
formspec = formspec ..
|
||||
string.format( "label[4.5,8.2;Front Cover]", page_idx == 1 and "Cover" or ( "Page " .. page_idx - 1 ) )
|
||||
else
|
||||
formspec = formspec ..
|
||||
"button[6.5,8.3;2.0,0.3;del_page;Del Page]" ..
|
||||
string.format( "label[4.5,8.2;Page %d of %d]", page_idx - 1, #pages - 1 )
|
||||
end
|
||||
|
||||
formspec = formspec ..
|
||||
"box[0.0,9.0;10.3,0.1;#111111]" ..
|
||||
"button_exit[0.0,9.3;2.0,1.0;cancel;Cancel]" ..
|
||||
"button[6.5,9.3;2.0,1.0;preview;Preview]" ..
|
||||
"button_exit[8.5,9.3;2.0,1.0;publish;Publish]"
|
||||
|
||||
return formspec
|
||||
end
|
||||
|
||||
local function on_close( meta, player, fields )
|
||||
if fields.cancel or fields.quit then return end -- no-op when quitting form
|
||||
|
||||
if fields.preview or fields.publish then
|
||||
if not fields.message or not fields.title or not fields.color or not fields.scale or not fields.style then
|
||||
return
|
||||
elseif string.len( fields.message ) > config.max_book_message_length then
|
||||
minetest.chat_send_player( player_name, "The specified message is too long." )
|
||||
return
|
||||
elseif string.len( fields.title ) > config.max_book_title_length then
|
||||
minetest.chat_send_player( player_name, "The specified title too long." )
|
||||
return
|
||||
end
|
||||
pages[ page_idx ] = sanitize( fields.message ) -- update the respective page content before previewing
|
||||
end
|
||||
|
||||
if fields.preview then
|
||||
open_book_viewer( player_name, oldtime, owner, fields.title, fields.scale, fields.style, fields.color, pages, { }, page_idx, vars, true )
|
||||
|
||||
elseif fields.publish then
|
||||
-- check that book content is within range
|
||||
local length = 0
|
||||
|
||||
for i, v in ipairs( pages ) do
|
||||
length = length + string.len( v )
|
||||
end
|
||||
|
||||
if length > config.max_book_content_length then
|
||||
minetest.chat_send_player( player_name, "The content is too long." )
|
||||
end
|
||||
|
||||
-- save all changes to the itemstack meta
|
||||
local itemstack = player:get_wielded_item( )
|
||||
local player_inv = player:get_inventory( )
|
||||
local data = {
|
||||
oldtime = oldtime,
|
||||
owner = owner,
|
||||
scale = scale,
|
||||
style = style,
|
||||
color = color,
|
||||
title = title,
|
||||
pages = pages,
|
||||
marks = { }, -- editing clears bookmarks
|
||||
}
|
||||
|
||||
if itemstack:get_name( ) == "default:book" then
|
||||
itemstack:take_item( 1 ) -- take from unwritten books
|
||||
player:set_wielded_item( itemstack )
|
||||
|
||||
itemstack = ItemStack( "default:book_written" ) -- get reference to new stack
|
||||
safety_deposit.encrypt_metadata( itemstack, data )
|
||||
|
||||
if player_inv:room_for_item( "main", itemstack ) then
|
||||
player_inv:add_item( "main", itemstack )
|
||||
else
|
||||
minetest.add_item( player:getpos( ), itemstack )
|
||||
end
|
||||
else
|
||||
safety_deposit.encrypt_metadata( itemstack, data )
|
||||
player:set_wielded_item( itemstack )
|
||||
end
|
||||
|
||||
minetest.log( "action", player_name .. " wrote " .. length .. " bytes to a book titled \"" .. string.gsub( fields.title, "[\n\t]", "" ) .. "\"" )
|
||||
minetest.chat_send_player( player_name, "Your book was successfully published!" )
|
||||
|
||||
elseif fields.cover_page then
|
||||
if page_idx > 1 then
|
||||
page_idx = 1
|
||||
minetest.update_form( player_name, get_formspec( ) )
|
||||
end
|
||||
|
||||
elseif fields.final_page then
|
||||
if page_idx < #pages then
|
||||
page_idx = #pages
|
||||
minetest.update_form( player_name, get_formspec( ) )
|
||||
end
|
||||
|
||||
elseif fields.next_page then
|
||||
if page_idx < #pages then
|
||||
page_idx = page_idx + 1
|
||||
minetest.update_form( player_name, get_formspec( ) )
|
||||
end
|
||||
|
||||
elseif fields.prev_page then
|
||||
if page_idx > 1 then
|
||||
page_idx = page_idx - 1
|
||||
minetest.update_form( player_name, get_formspec( ) )
|
||||
end
|
||||
|
||||
elseif fields.add_page then
|
||||
pages[ page_idx ] = fields.message
|
||||
|
||||
table.insert( pages, page_idx + 1, "New Page" )
|
||||
page_idx = page_idx + 1
|
||||
minetest.update_form( player_name, get_formspec( ) )
|
||||
|
||||
elseif fields.del_page then
|
||||
if page_idx > 1 then
|
||||
table.remove( pages, page_idx )
|
||||
page_idx = page_idx - 1
|
||||
minetest.update_form( player_name, get_formspec( ) )
|
||||
end
|
||||
|
||||
elseif fields.color then
|
||||
color = fields.color
|
||||
|
||||
elseif fields.style then
|
||||
style = fields.style
|
||||
|
||||
elseif fields.scale then
|
||||
scale = fields.scale
|
||||
end
|
||||
end
|
||||
|
||||
minetest.create_form( nil, player_name, get_formspec( ), on_close, minetest.FORMSPEC_SIGSTOP )
|
||||
end
|
||||
|
||||
function open_paper_viewer( player_name, oldtime, owner, scale, style, color, subject, message, vars, is_preview )
|
||||
|
||||
local paper_scales = {
|
||||
small = { width = 6.5, height = 9.0 },
|
||||
tall = { width = 6.5, height = 11.0 },
|
||||
large = { width = 8.0, height = 11.0 },
|
||||
wide = { width = 8.0, height = 9.0 },
|
||||
}
|
||||
local paper_colors = {
|
||||
gray = "#333333",
|
||||
brown = "#663300",
|
||||
teal = "#006633",
|
||||
purple = "#330066",
|
||||
olive = "#336600",
|
||||
indigo = "#003366",
|
||||
maroon = "#660033",
|
||||
red = "#660000",
|
||||
green = "#006600",
|
||||
blue = "#000066",
|
||||
}
|
||||
|
||||
local function get_formspec( )
|
||||
local page_width = paper_scales[ scale ].width
|
||||
local page_height = paper_scales[ scale ].height
|
||||
|
||||
local foot_x = ( page_width - 2.0 ) / 2 -- horz pos of footer
|
||||
local foot_y = page_height - 1.2 -- vert pos of footer
|
||||
|
||||
local formspec =
|
||||
string.format( "size[%0.2f,%0.2f]", page_width, page_height ) ..
|
||||
default.gui_bg ..
|
||||
string.format( "background[0.0,0.0;%0.2f,%0.2f;%s;false]", page_width, page_height,
|
||||
fs( "paper_" .. style .. ".png^[colorize:" .. paper_colors[ color ] .. "66^[noalpha" ) ) ..
|
||||
string.format( "%s[%0.2f,%0.2f;2.0,1.0;close;Close]",
|
||||
is_preview and "button" or "button_exit", foot_x, foot_y )
|
||||
|
||||
if subject ~= "" then
|
||||
formspec = formspec ..
|
||||
"label[0.5,0.5;From:]" ..
|
||||
"label[1.6,0.5;" .. owner .. "]" ..
|
||||
"label[0.5,1.0;Date:]" ..
|
||||
"label[1.6,1.0;" .. os.date( "%X", oldtime ) .. "]" ..
|
||||
"label[0.5,1.5;Subject:]" ..
|
||||
"label[1.6,1.5;" .. subject .. "]" ..
|
||||
string.format( "box[0.3,2.1;%0.2f,0.05;#FFFFFF]", page_width - 1.0 )
|
||||
end
|
||||
|
||||
local page_x1 = 0.5
|
||||
local page_y1 = subject == "" and 0.5 or 2.4
|
||||
local page_x2 = page_x1 + page_width - 1.0
|
||||
local page_y2 = subject == "" and page_y1 + page_height - 1.3 or page_y1 + page_height - 3.7
|
||||
|
||||
formspec = formspec ..
|
||||
markup.get_formspec_string( markup.parse_message( message, vars ),
|
||||
page_x1, page_y1, page_x2, page_y2, "#666666", "#CCCCCC" )
|
||||
|
||||
return formspec
|
||||
end
|
||||
|
||||
local function on_close( meta, player, fields )
|
||||
if fields.close then
|
||||
if is_preview then
|
||||
open_paper_editor( player_name, oldtime, owner, scale, style, color, subject, message, vars )
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- basic sanity check for proper inputs
|
||||
if not paper_scales[ scale ] then scale = config.default_paper_scale end
|
||||
if not paper_colors[ color ] then color = config.default_paper_color end
|
||||
|
||||
minetest.create_form( nil, player_name, get_formspec( ), on_close, is_preview and minetest.FORMSPEC_SIGSTOP or nil )
|
||||
end
|
||||
|
||||
function open_paper_editor( player_name, oldtime, owner, scale, style, color, subject, message, vars )
|
||||
|
||||
local function sanitize( buf )
|
||||
return string.trim( string.gsub( buf, ".", { ["\r"] = "\n", ["\t"] = " ", ["\f"] = "?", ["\b"] = "?" } ) ) .. "\n"
|
||||
end
|
||||
|
||||
local function get_formspec( )
|
||||
local paper_scales = { "small", "large", "tall", "wide" }
|
||||
local paper_colors = { "gray", "brown", "teal", "purple", "olive", "indigo", "maroon", "red", "green", "blue" }
|
||||
local paper_styles = { "plain1", "plain2", "fancy1", "fancy2", "fancy3", "fancy4", "classic1", "classic2", "vintage1", "vintage2", "vintage3", "vintage4" }
|
||||
|
||||
local formspec =
|
||||
"size[10.5,9.5]" ..
|
||||
default.gui_bg ..
|
||||
default.gui_bg_img ..
|
||||
|
||||
"label[0.0,0.1;Subject:]" ..
|
||||
"field[1.5,0.2;9.3,1.0;subject;;" .. fs( subject ) .. "]" ..
|
||||
"textarea[0.3,1.2;10.5,7.0;message;Enter the message to display on the page (" .. config.max_paper_message_length .. " character limit per paper);" .. fs( message ) .. "]" ..
|
||||
|
||||
"label[0.0,7.6;Paper Color:]" ..
|
||||
"dropdown[1.6,7.5;1.8,1.0;color;" .. table.concat( paper_colors, "," ) .. ";" .. table.get_index( paper_colors, color, 1 ) .. ";false]" ..
|
||||
|
||||
"label[3.5,7.6;Paper Style:]" ..
|
||||
"dropdown[5.1,7.5;1.8,1.0;style;" .. table.concat( paper_styles, "," ) .. ";" .. table.get_index( paper_styles, style, 1 ) .. ";false]" ..
|
||||
|
||||
"label[7.0,7.6;Paper Scale:]" ..
|
||||
"dropdown[8.6,7.5;1.8,1.0;scale;" .. table.concat( paper_scales, "," ) .. ";" .. table.get_index( paper_scales, scale, 1 ) .. ";false]" ..
|
||||
|
||||
"box[0.0,8.5;10.3,0.1;#111111]" ..
|
||||
"button_exit[0.0,8.8;2.0,1.0;cancel;Cancel]" ..
|
||||
"button[6.5,8.8;2.0,1.0;preview;Preview]" ..
|
||||
"button_exit[8.5,8.8;2.0,1.0;publish;Publish]"
|
||||
|
||||
return formspec
|
||||
end
|
||||
|
||||
local function on_close( meta, player, fields )
|
||||
if fields.cancel then
|
||||
return
|
||||
|
||||
elseif fields.preview or fields.publish then
|
||||
if not fields.message or not fields.subject or not fields.color or not fields.scale or not fields.style then
|
||||
return
|
||||
elseif string.len( fields.message ) > config.max_paper_message_length then
|
||||
minetest.chat_send_player( player_name, "The specified message is too long." )
|
||||
return
|
||||
elseif string.len( fields.subject ) > config.max_paper_subject_length then
|
||||
minetest.chat_send_player( player_name, "The specified subject is too long." )
|
||||
return
|
||||
end
|
||||
message = sanitize( fields.message )
|
||||
end
|
||||
|
||||
if fields.preview then
|
||||
open_paper_viewer( player_name, oldtime, owner, fields.scale, fields.style, fields.color, fields.subject, message, vars, true )
|
||||
|
||||
elseif fields.publish then
|
||||
-- save all changes to the itemstack meta
|
||||
local itemstack = player:get_wielded_item( )
|
||||
local player_inv = player:get_inventory( )
|
||||
local data = {
|
||||
oldtime = oldtime,
|
||||
owner = owner,
|
||||
scale = scale,
|
||||
style = style,
|
||||
color = color,
|
||||
subject = subject,
|
||||
message = message,
|
||||
marks = { }, -- editing clears bookmarks
|
||||
}
|
||||
|
||||
if itemstack:get_name( ) == "default:paper" then
|
||||
itemstack:take_item( 1 ) -- take from unwritten books
|
||||
player:set_wielded_item( itemstack )
|
||||
|
||||
itemstack = ItemStack( "default:paper_written" ) -- get reference to new stack
|
||||
safety_deposit.encrypt_metadata( itemstack, data )
|
||||
|
||||
if player_inv:room_for_item( "main", itemstack ) then
|
||||
player_inv:add_item( "main", itemstack )
|
||||
else
|
||||
minetest.add_item( player:getpos( ), itemstack )
|
||||
end
|
||||
else
|
||||
safety_deposit.encrypt_metadata( itemstack, data )
|
||||
player:set_wielded_item( itemstack )
|
||||
end
|
||||
|
||||
minetest.log( "action", player_name .. " wrote " .. #message .. " bytes to a paper titled \"" .. string.gsub( fields.subject, "[\n\t]", "" ) .. "\"" )
|
||||
minetest.chat_send_player( player_name, "Your paper was successfully published!" )
|
||||
|
||||
elseif fields.color then
|
||||
color = fields.color
|
||||
|
||||
elseif fields.style then
|
||||
style = fields.style
|
||||
|
||||
elseif fields.scale then
|
||||
scale = fields.scale
|
||||
end
|
||||
end
|
||||
|
||||
minetest.create_form( nil, player_name, get_formspec( ), on_close, minetest.FORMSPEC_SIGSTOP )
|
||||
end
|
||||
|
||||
local function open_default_book_editor( player_name, vars )
|
||||
local title = config.default_book_title
|
||||
local scale = config.default_book_scale
|
||||
local style = config.default_book_style
|
||||
local color = config.default_book_color
|
||||
|
||||
open_book_editor( player_name, os.time( ), player_name, title, style, scale, color, { "" }, 1, vars )
|
||||
end
|
||||
|
||||
local function open_default_paper_editor( player_name, vars )
|
||||
local subject = config.default_paper_subject
|
||||
local color = config.default_paper_color
|
||||
local style = config.default_paper_style
|
||||
local scale = config.default_paper_scale
|
||||
|
||||
open_paper_editor( player_name, os.time( ), player_name, scale, style, color, subject, "", vars )
|
||||
end
|
||||
|
||||
-----------------------
|
||||
|
||||
minetest.register_craftitem( ":default:book", {
|
||||
description = "Book",
|
||||
inventory_image = "default_book.png",
|
||||
groups = { book = 1, flammable = 3 },
|
||||
on_use = function ( itemstack, player )
|
||||
local player_name = player:get_player_name( )
|
||||
local vars = markup.get_builtin_vars( player_name )
|
||||
|
||||
open_default_book_editor( player_name, vars )
|
||||
return itemstack
|
||||
end,
|
||||
} )
|
||||
|
||||
minetest.register_craftitem( ":default:book_written", {
|
||||
description = "Book With Text (Sneak and Use to Edit)",
|
||||
inventory_image = "default_book_written.png",
|
||||
groups = { book = 1, not_in_creative_inventory = 1, flammable = 3 },
|
||||
stack_max = 1,
|
||||
on_use = function ( itemstack, player )
|
||||
local player_name = player:get_player_name( )
|
||||
local data = safety_deposit.decrypt_metadata( itemstack )
|
||||
local vars = markup.get_builtin_vars( player_name )
|
||||
|
||||
if not data then
|
||||
-- apparently book is empty, so let's start fresh
|
||||
open_default_book_editor( player_name, vars )
|
||||
return itemstack
|
||||
end
|
||||
|
||||
local oldtime = data.oldtime
|
||||
local owner = data.owner
|
||||
local title = data.title
|
||||
local scale = data.scale
|
||||
local style = data.style
|
||||
local color = data.color
|
||||
local pages = data.pages
|
||||
local marks = data.marks
|
||||
|
||||
-- if book format is obsolete, then open the compatibility viewer
|
||||
if not pages and data.owner and data.title and data.text then
|
||||
open_defbook_viewer( player_name, data.owner, data.title, data.text, data.page, data.page_max )
|
||||
return itemstack
|
||||
|
||||
elseif not oldtime or not owner or not title or not scale or not style or not color or not pages or not marks then
|
||||
minetest.chat_send_player( player_name, "This book is corrupted and cannot be opened." )
|
||||
return itemstack
|
||||
end
|
||||
|
||||
if player:get_player_control( ).sneak and owner == player_name then
|
||||
open_book_editor( player_name, oldtime, owner, title, scale, style, color, pages, 1, vars )
|
||||
else
|
||||
open_book_viewer( player_name, oldtime, owner, title, scale, style, color, pages, marks, 1, vars )
|
||||
end
|
||||
|
||||
return itemstack
|
||||
end,
|
||||
} )
|
||||
|
||||
minetest.register_craftitem( ":default:paper", {
|
||||
description = "Paper",
|
||||
inventory_image = "default_paper.png",
|
||||
groups = { flammable = 3 },
|
||||
|
||||
on_use = function ( itemstack, player )
|
||||
local player_name = player:get_player_name( )
|
||||
local vars = markup.get_builtin_vars( player_name )
|
||||
|
||||
open_default_paper_editor( player_name, vars )
|
||||
return itemstack
|
||||
end,
|
||||
} )
|
||||
|
||||
minetest.register_craftitem( ":default:paper_written", {
|
||||
description = "Paper With Text (Sneak and Use to Edit)",
|
||||
inventory_image = "default_paper.png^default_paper_written.png",
|
||||
groups = { book = 1, not_in_creative_inventory = 1, flammable = 3 },
|
||||
stack_max = 1,
|
||||
on_use = function ( itemstack, player )
|
||||
local player_name = player:get_player_name( )
|
||||
local data = safety_deposit.decrypt_metadata( itemstack )
|
||||
local vars = markup.get_builtin_vars( player_name )
|
||||
|
||||
if not data then
|
||||
-- apparently paper is empty, so let's start fresh
|
||||
open_default_paper_editor( player_name, vars )
|
||||
return itemstack
|
||||
end
|
||||
|
||||
local oldtime = data.oldtime
|
||||
local owner = data.owner
|
||||
local scale = data.scale
|
||||
local style = data.style
|
||||
local color = data.color
|
||||
local subject = data.subject
|
||||
local message = data.message
|
||||
|
||||
if not oldtime or not owner or not scale or not style or not color or not subject or not message then
|
||||
minetest.chat_send_player( player_name, "This paper is corrupted and cannot be opened." )
|
||||
return itemstack
|
||||
end
|
||||
|
||||
if player:get_player_control( ).sneak and owner == player_name then
|
||||
open_paper_editor( player_name, oldtime, owner, scale, style, color, subject, message, vars )
|
||||
else
|
||||
open_paper_viewer( player_name, oldtime, owner, scale, style, color, subject, message, vars )
|
||||
end
|
||||
|
||||
return itemstack
|
||||
end,
|
||||
} )
|
BIN
textures/book_classic_cover.png
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
textures/book_classic_front.png
Normal file
After Width: | Height: | Size: 45 KiB |
BIN
textures/book_fancy_cover.png
Normal file
After Width: | Height: | Size: 8.4 KiB |
BIN
textures/book_fancy_front.png
Normal file
After Width: | Height: | Size: 25 KiB |
BIN
textures/book_plain_cover.png
Normal file
After Width: | Height: | Size: 8.8 KiB |
BIN
textures/book_plain_front.png
Normal file
After Width: | Height: | Size: 28 KiB |
BIN
textures/book_vintage_cover.png
Normal file
After Width: | Height: | Size: 31 KiB |
BIN
textures/book_vintage_front.png
Normal file
After Width: | Height: | Size: 63 KiB |
BIN
textures/default_paper_written.png
Normal file
After Width: | Height: | Size: 239 B |
BIN
textures/paper_classic1.png
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
textures/paper_classic2.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
textures/paper_fancy1.png
Normal file
After Width: | Height: | Size: 18 KiB |
BIN
textures/paper_fancy2.png
Normal file
After Width: | Height: | Size: 20 KiB |
BIN
textures/paper_fancy3.png
Normal file
After Width: | Height: | Size: 21 KiB |
BIN
textures/paper_fancy4.png
Normal file
After Width: | Height: | Size: 21 KiB |
BIN
textures/paper_plain1.png
Normal file
After Width: | Height: | Size: 24 KiB |
BIN
textures/paper_plain2.png
Normal file
After Width: | Height: | Size: 21 KiB |
BIN
textures/paper_vintage1.png
Normal file
After Width: | Height: | Size: 22 KiB |
BIN
textures/paper_vintage2.png
Normal file
After Width: | Height: | Size: 26 KiB |
BIN
textures/paper_vintage3.png
Normal file
After Width: | Height: | Size: 21 KiB |
BIN
textures/paper_vintage4.png
Normal file
After Width: | Height: | Size: 21 KiB |