init.lua 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507
  1. if not minetest.global_exists("books") then books = {} end
  2. books.modpath = minetest.get_modpath("books")
  3. dofile(books.modpath .. "/book.lua")
  4. -- These items convert to these nodes when placed in the world.
  5. -- Note: must use "open" book because closed book will be automatically removed.
  6. local items_to_nodes = {
  7. ["books:book_written"] = "books:book_open",
  8. ["books:book_blank"] = "books:book_open",
  9. }
  10. -- Called from inventory-action on the bookshelf node.
  11. function books.put_book_on_table(pos, stack, pname)
  12. local minp = vector.offset(pos, -8, -2, -8)
  13. local maxp = vector.offset(pos, 8, 2, 8)
  14. -- Make sure stack is just one item.
  15. if stack:get_count() ~= 1 then
  16. return false
  17. end
  18. -- Make sure stack is a node.
  19. local nodename = items_to_nodes[stack:get_name()]
  20. if not nodename then
  21. return false
  22. end
  23. -- "under air" shall skip tables which already have books on them.
  24. -- This simplifies the code.
  25. local positions = minetest.find_nodes_in_area_under_air(minp, maxp,
  26. {"xdecor:booktable", "xdecor:table"})
  27. if not positions or #positions == 0 then
  28. return false
  29. end
  30. local tablepos = positions[math.random(1, #positions)]
  31. local airpos = vector.offset(tablepos, 0, 1, 0)
  32. if minetest.get_node(airpos).name ~= "air" then
  33. return false
  34. end
  35. minetest.set_node(airpos, {name=nodename, param2=math.random(0, 3)})
  36. local bookmeta = minetest.get_meta(airpos)
  37. bookmeta:from_table(stack:get_meta():to_table())
  38. bookmeta:set_int("is_library_checkout", 1)
  39. bookmeta:set_string("checked_out_by", pname)
  40. books_placeable.set_closed_infotext(airpos)
  41. local tabletimer = minetest.get_node_timer(tablepos)
  42. tabletimer:start(10.0)
  43. return true
  44. end
  45. -- Called from the booktable node.
  46. function books.book_table_on_timer(pos, elapsed)
  47. local above = vector.offset(pos, 0, 1, 0)
  48. local bnode = minetest.get_node(above)
  49. local bmeta = minetest.get_meta(above)
  50. if bnode.name == "books:book_closed" and bmeta:get_int("is_library_checkout") == 1 then
  51. local title = bmeta:get_string("title")
  52. local pname = bmeta:get_string("checked_out_by")
  53. if title == "" then
  54. title = "Untitled Book"
  55. end
  56. minetest.remove_node(above)
  57. local pref = minetest.get_player_by_name(pname)
  58. if pref and vector.distance(pos, pref:get_pos()) < 32 then
  59. minetest.chat_send_player(pname, "# Server: \"" .. title .. "\" has been returned to the shelf.")
  60. end
  61. return
  62. elseif bnode.name == "books:book_open" and bmeta:get_int("is_library_checkout") == 1 then
  63. -- Check again in some seconds.
  64. return true
  65. end
  66. end
  67. books.get_formspec = function(pos)
  68. local meta = minetest.get_meta(pos)
  69. local donate = "false"
  70. if meta:get_int("allow_donate") ~= 0 then
  71. donate = "true"
  72. end
  73. local formspec =
  74. "size[11,7;]" ..
  75. default.formspec.get_form_colors() ..
  76. default.formspec.get_form_image() ..
  77. default.formspec.get_slot_colors() ..
  78. "list[context;books;0,0.3;8,2;]" ..
  79. "checkbox[8.5,0.0;allow_donate;Accept Donations;" .. donate .. "]" ..
  80. "label[8.5,0.8;Checkout]" ..
  81. "image[8.5,1.3;1,1;books_slot.png]" ..
  82. "list[context;checkout;8.5,1.3;1,1;]" ..
  83. "list[current_player;main;0,2.85;8,1;]" ..
  84. "list[current_player;main;0,4.08;8,3;8]" ..
  85. "listring[context;books]" ..
  86. "listring[current_player;main]" ..
  87. default.get_hotbar_bg(0,2.85)
  88. -- Inventory slots overlay
  89. local bx, by = 0, 0.3
  90. for i = 1, 16 do
  91. if i == 9 then
  92. bx = 0
  93. by = by + 1
  94. end
  95. formspec = formspec .. "image["..bx..","..by..";1,1;books_slot.png]"
  96. bx = bx + 1
  97. end
  98. return formspec
  99. end
  100. function books.on_receive_fields(pos, formname, fields, sender)
  101. local pname = sender:get_player_name()
  102. if minetest.test_protection(pos, pname) then
  103. return
  104. end
  105. local meta = minetest.get_meta(pos)
  106. if fields.allow_donate == "true" then
  107. meta:set_int("allow_donate", 1)
  108. elseif fields.allow_donate == "false" then
  109. meta:set_string("allow_donate", "")
  110. end
  111. meta:set_string("formspec", books.get_formspec(pos))
  112. end
  113. books.on_construct = function(pos)
  114. local meta = minetest.get_meta(pos)
  115. meta:set_string("formspec", books.get_formspec(pos))
  116. local inv = meta:get_inventory()
  117. inv:set_size("books", 8 * 2)
  118. inv:set_size("checkout", 1)
  119. end
  120. function books.on_update_formspec(pos)
  121. local meta = minetest.get_meta(pos)
  122. local inv = meta:get_inventory()
  123. inv:set_size("checkout", 1)
  124. meta:set_string("formspec", books.get_formspec(pos))
  125. end
  126. books.can_dig = function(pos,player)
  127. local inv = minetest.get_meta(pos):get_inventory()
  128. return inv:is_empty("books")
  129. end
  130. function books.update_infotext(pos)
  131. local meta = minetest.get_meta(pos)
  132. local inv = meta:get_inventory()
  133. local list = inv:get_list("books")
  134. local titles = {}
  135. for k, v in ipairs(list) do
  136. if not v:is_empty() then
  137. local imeta = v:get_meta()
  138. local t = imeta:get_string("title")
  139. if t ~= "" then
  140. titles[#titles + 1] = t
  141. end
  142. if #titles >= 5 then
  143. break
  144. end
  145. end
  146. end
  147. local infotext = ""
  148. for k, v in ipairs(titles) do
  149. infotext = infotext .. k .. ": \"" .. v .. "\"\n"
  150. end
  151. meta:set_string("infotext", infotext)
  152. end
  153. books.allow_metadata_inventory_put = function(pos, listname, index, stack, player)
  154. if minetest.get_item_group(stack:get_name(), "book") == 0 then
  155. return 0
  156. end
  157. -- Cannot put something directly in checkout slot.
  158. -- Put something in main inv first, then move it.
  159. -- This lets us keep the code simple.
  160. if listname == "checkout" then
  161. return 0
  162. end
  163. -- Note: users not on protection are allowed to put books on shelf.
  164. -- But they cannot remove them!
  165. local pname = player:get_player_name()
  166. local meta = minetest.get_meta(pos)
  167. if meta:get_int("allow_donate") == 0 then
  168. if minetest.test_protection(pos, pname) then
  169. return 0
  170. end
  171. end
  172. return stack:get_count()
  173. end
  174. books.allow_metadata_inventory_take = function(pos, listname, index, stack, player)
  175. if minetest.get_item_group(stack:get_name(), "book") == 0 then
  176. return 0
  177. end
  178. -- Cannot take directly from checkout slot.
  179. -- That slot should always be empty anyway.
  180. if listname == "checkout" then
  181. return 0
  182. end
  183. local pname = player:get_player_name()
  184. -- Users not on protection cannot remove anything.
  185. if minetest.test_protection(pos, pname) then
  186. return 0
  187. end
  188. return stack:get_count()
  189. end
  190. books.allow_metadata_inventory_move = function(pos, from_list, from_index, to_list, to_index, count, player)
  191. if from_list == "checkout" then
  192. return 0
  193. end
  194. local pname = player:get_player_name()
  195. -- Users not on protection are not allowed to move books around, except to the checkout slot.
  196. if to_list ~= "checkout" then
  197. if minetest.test_protection(pos, pname) then
  198. return 0
  199. end
  200. end
  201. return count
  202. end
  203. function books.on_update_infotext(pos)
  204. books.update_infotext(pos)
  205. end
  206. -- Use this callback hook to update all book descriptions.
  207. function books.on_update_entity(pos)
  208. local meta = minetest.get_meta(pos)
  209. local inv = meta:get_inventory()
  210. local list = inv:get_list("books")
  211. for k, v in ipairs(list) do
  212. if not v:is_empty() then
  213. local imeta = v:get_meta()
  214. if v:get_name() == "books:book_written" then
  215. local t = imeta:get_string("title")
  216. local a = imeta:get_string("owner")
  217. -- Don't bother triming the title if the trailing dots would make it longer
  218. if #t > books.SHORT_TITLE_SIZE + 3 then
  219. t = t:sub(1, books.SHORT_TITLE_SIZE) .. "..."
  220. end
  221. local desc = "\"" .. t .. "\" By <" .. rename.gpn(a) .. ">"
  222. imeta:set_string("description", desc)
  223. end
  224. end
  225. end
  226. inv:set_list("books", list)
  227. end
  228. books.on_metadata_inventory_move = function(pos, from_list, from_index, to_list, to_index, count, player)
  229. local pname = player:get_player_name()
  230. local meta = minetest.get_meta(pos)
  231. local inv = meta:get_inventory()
  232. -- Not a storage slot. Put item back in main inv, and put temporary book on table!
  233. if to_list == "checkout" then
  234. local stack = inv:get_stack("checkout", 1)
  235. inv:set_stack(from_list, from_index, stack)
  236. inv:set_stack("checkout", 1, ItemStack(""))
  237. local imeta = stack:get_meta()
  238. local title = imeta:get_string("title")
  239. if title == "" then
  240. title = "Untitled Book"
  241. end
  242. if books.put_book_on_table(pos, stack, pname) then
  243. minetest.chat_send_player(pname, "# Server: You checked out \"" .. title .. "\". Look for it on a nearby table.")
  244. else
  245. minetest.chat_send_player(pname, "# Server: You were unable to checkout out \"" .. title .. "\".")
  246. end
  247. end
  248. minetest.log("action", pname .. " moves stuff in bookshelf at " .. minetest.pos_to_string(pos))
  249. books.update_infotext(pos)
  250. end
  251. books.on_metadata_inventory_put = function(pos, listname, index, stack, player)
  252. minetest.log("action", player:get_player_name() .. " moves stuff to bookshelf at " .. minetest.pos_to_string(pos))
  253. books.update_infotext(pos)
  254. end
  255. books.on_metadata_inventory_take = function(pos, listname, index, stack, player)
  256. minetest.log("action", player:get_player_name() .. " takes stuff from bookshelf at " .. minetest.pos_to_string(pos))
  257. books.update_infotext(pos)
  258. end
  259. books.on_blast = function(pos)
  260. local drops = {}
  261. default.get_inventory_drops(pos, "books", drops)
  262. drops[#drops+1] = "books:bookshelf"
  263. minetest.remove_node(pos)
  264. return drops
  265. end
  266. if not books.run_once then
  267. local bookshelf_groups = utility.dig_groups("furniture", {flammable = 3})
  268. local bookshelf_sounds = default.node_sound_wood_defaults()
  269. minetest.register_node("books:bookshelf", {
  270. description = "Bookshelf",
  271. tiles = {
  272. "default_wood.png",
  273. "default_wood.png",
  274. "default_wood.png",
  275. "default_wood.png",
  276. "books_bookshelf.png",
  277. "books_bookshelf.png"
  278. },
  279. paramtype2 = "facedir",
  280. groups = bookshelf_groups,
  281. sounds = bookshelf_sounds,
  282. on_construct = function(...)
  283. return books.on_construct(...) end,
  284. can_dig = function(...)
  285. return books.can_dig(...) end,
  286. on_receive_fields = function(...)
  287. return books.on_receive_fields(...) end,
  288. allow_metadata_inventory_put = function(...)
  289. return books.allow_metadata_inventory_put(...) end,
  290. allow_metadata_inventory_take = function(...)
  291. return books.allow_metadata_inventory_take(...) end,
  292. allow_metadata_inventory_move = function(...)
  293. return books.allow_metadata_inventory_move(...) end,
  294. on_metadata_inventory_move = function(...)
  295. return books.on_metadata_inventory_move(...) end,
  296. on_metadata_inventory_put = function(...)
  297. return books.on_metadata_inventory_put(...) end,
  298. on_metadata_inventory_take = function(...)
  299. return books.on_metadata_inventory_take(...) end,
  300. on_blast = function(...)
  301. return books.on_blast(...) end,
  302. _on_update_infotext = function(...)
  303. return books.on_update_infotext(...) end,
  304. _on_update_entity = function(...)
  305. return books.on_update_entity(...) end,
  306. _on_update_formspec = function(...)
  307. return books.on_update_formspec(...) end,
  308. })
  309. minetest.register_node("books:bookshelf_empty", {
  310. description = "Empty Bookshelf",
  311. tiles = {
  312. "default_wood.png",
  313. "default_wood.png",
  314. "default_wood.png",
  315. "default_wood.png",
  316. "books_bookshelf_empty.png",
  317. "books_bookshelf_empty.png"
  318. },
  319. paramtype2 = "facedir",
  320. groups = bookshelf_groups,
  321. sounds = bookshelf_sounds,
  322. })
  323. minetest.register_craft({
  324. type = "fuel",
  325. recipe = "books:bookshelf",
  326. burntime = 30,
  327. })
  328. minetest.register_craft({
  329. output = 'books:bookshelf_empty',
  330. recipe = {
  331. {'group:wood', 'group:wood', 'group:wood'},
  332. {'books:book_blank', 'books:book_blank', 'books:book_blank'},
  333. {'group:wood', 'group:wood', 'group:wood'},
  334. }
  335. })
  336. minetest.register_craft({
  337. type = "fuel",
  338. recipe = "books:bookshelf_empty",
  339. burntime = 26,
  340. })
  341. minetest.register_craft({
  342. output = "books:bookshelf",
  343. type = "shapeless",
  344. recipe = {"books:bookshelf_empty", "books:book_blank"},
  345. })
  346. minetest.register_alias("default:bookshelf", "books:bookshelf")
  347. minetest.register_alias("bookshelf:bookshelf", "books:bookshelf")
  348. minetest.register_alias("moreblocks:empty_bookshelf", "books:bookshelf_empty")
  349. minetest.register_alias("default:book", "books:book_blank")
  350. minetest.register_alias("default:book_written", "books:book_written")
  351. minetest.register_on_player_receive_fields(function(...) books.on_player_receive_fields(...) end)
  352. minetest.register_on_craft(function(...) books.on_craft(...) end)
  353. minetest.register_craftitem("books:book_blank", {
  354. description = "Book (Blank)",
  355. inventory_image = "default_book.png",
  356. groups = {book = 1, flammable = 3},
  357. on_use = function(...) return books.book_on_use(...) end,
  358. })
  359. minetest.register_craftitem("books:book_written", {
  360. description = "Book (Written)",
  361. inventory_image = "default_book_written.png",
  362. groups = {book = 1, not_in_creative_inventory = 1, flammable = 3},
  363. stack_max = 1,
  364. on_use = function(...) return books.book_on_use(...) end,
  365. })
  366. minetest.register_craft({
  367. type = "shapeless",
  368. output = "books:book_written",
  369. recipe = {"books:book_blank", "books:book_written"}
  370. })
  371. minetest.register_craft({
  372. output = 'books:book_blank',
  373. recipe = {
  374. {'default:paper'},
  375. {'default:paper'},
  376. {'default:paper'},
  377. }
  378. })
  379. -- Reloadable.
  380. local name = "books:core"
  381. local file = books.modpath .. "/init.lua"
  382. reload.register_file(name, file, false)
  383. books.run_once = true
  384. end