init.lua 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820
  1. -- File shall be reloadable.
  2. if not minetest.global_exists("memorandum") then memorandum = {} end
  3. memorandum.modpath = minetest.get_modpath("memorandum")
  4. local MAX_LETTER_SIZE = 5000
  5. local MAX_AUTHOR_SIZE = 128
  6. -- Localize for performance.
  7. local vector_round = vector.round
  8. local math_floor = math.floor
  9. local math_random = math.random
  10. local collisionbox_sheet = {
  11. -1/2, -- Left.
  12. -1/2, -- Bottom.
  13. -1/2, -- Front.
  14. 1/2, -- Right.
  15. -7/16, -- Top.
  16. 1/2, -- Back.
  17. }
  18. local collisionbox_glass = {
  19. -1/4, -- Left.
  20. -1/2, -- Bottom.
  21. -1/4, -- Front.
  22. 1/4, -- Right.
  23. 4/10, -- Top.
  24. 1/4, -- Back.
  25. }
  26. local param2_walldirections = {
  27. 8, -- South.
  28. 17, -- West.
  29. 6, -- North.
  30. 15, -- East.
  31. }
  32. -- Check if player may interact with memorandum at a location.
  33. memorandum.is_protected = function(pos, name)
  34. local nn = minetest.get_node(pos).name
  35. if nn == "memorandum:letter_written" or nn == "memorandum:message" then -- Special nodes. Author may always dig/place/interact.
  36. local meta = minetest.get_meta(pos)
  37. local author = meta:get_string("signed") or ""
  38. if author == name then
  39. return -- Not protected if player is the author.
  40. end
  41. end
  42. return minetest.test_protection(pos, name)
  43. end
  44. -- Extract information from itemstack meta.
  45. memorandum.extract_metainfo = function(text)
  46. local newformat = false
  47. if string.find(text, ":JSON$") then
  48. newformat = true
  49. end
  50. if newformat == true then
  51. text = string.gsub(text, ":JSON$", "")
  52. local data = minetest.parse_json(text)
  53. -- Ensure all parameters exist, or chose defaults.
  54. data = data or {}
  55. data.message = data.message or ""
  56. data.author = data.author or ""
  57. return data
  58. else
  59. -- Deserialize according to the old format.
  60. -- This allows us to support old memorandum items in the world.
  61. local scnt = string.sub(text, -2, -1)
  62. local mssg = ""
  63. local sgnd = ""
  64. if scnt == "00" then
  65. mssg = string.sub(text, 1, -3)
  66. sgnd = ""
  67. elseif tonumber(scnt) == nil then -- to support previous versions
  68. mssg = string.sub(text, 37, -1)
  69. sgnd = ""
  70. else
  71. mssg = string.sub(text, 1, -scnt -3)
  72. sgnd = string.sub(text, -scnt-2, -3)
  73. end
  74. return {message=mssg, author=sgnd}
  75. end
  76. end
  77. -- Serialize info from metadata into a string.
  78. memorandum.insert_metainfo = function(meta)
  79. local message = meta:get_string("text") or ""
  80. local author = meta:get_string("signed") or ""
  81. local serialized = minetest.write_json({message=message, author=author})
  82. -- Tag string as JSON data. The now-depreciated data format can never have this string at the end.
  83. -- This means we can use it to determine if data is in the old format or the new format, when loading.
  84. serialized = serialized .. ":JSON"
  85. return serialized
  86. end
  87. -- API function. Intended to be used to generate a string that
  88. -- can be used as metadata, when creating a memorandum itemstack.
  89. -- This is called from the Email-GUI mod, for instance.
  90. memorandum.compose_metadata = function(data)
  91. local message = data.text or ""
  92. local author = data.signed or ""
  93. message = message:sub(1, MAX_LETTER_SIZE)
  94. author = author:sub(1, MAX_AUTHOR_SIZE)
  95. local serialized = minetest.write_json({message=message, author=author})
  96. serialized = serialized .. ":JSON"
  97. return serialized
  98. end
  99. -- Player has right-clicked a memorandum node.
  100. memorandum.on_rightclick = function(pos, node, clicker, itemstack, pt)
  101. local pname = clicker:get_player_name()
  102. local meta = minetest.get_meta(pos)
  103. local info = {
  104. text = meta:get_string("text"),
  105. signed = meta:get_string("signed"),
  106. edit = meta:get_int("edit"),
  107. }
  108. local formspec = memorandum.get_formspec(info)
  109. local tag = minetest.pos_to_string(pos)
  110. if memorandum.check_explosive_runes(pname, pos, info.signed, info.text) then
  111. -- Prevent repeat explosion.
  112. minetest.remove_node(pos)
  113. end
  114. minetest.show_formspec(pname, "memorandum:main_" .. tag, formspec)
  115. end
  116. memorandum.on_player_receive_fields = function(player, formname, fields)
  117. if string.sub(formname, 1, 16) ~= "memorandum:main_" then
  118. return
  119. end
  120. local tag = string.sub(formname, 17)
  121. local pos = minetest.string_to_pos(tag)
  122. if not pos then
  123. return true
  124. end
  125. -- Delegate fields to the appropriate input function.
  126. local node = minetest.get_node(pos)
  127. if node.name == "memorandum:letter_written" then
  128. memorandum.on_letter_written_input(pos, fields, player)
  129. elseif node.name == "memorandum:letter_empty" then
  130. memorandum.on_letter_empty_input(pos, fields, player)
  131. end
  132. return true
  133. end
  134. -- Obtain a formspec string. Possible formspecs are "write", "edit", and "view".
  135. memorandum.get_formspec = function(info)
  136. if info.edit == 2 then
  137. -- Obtain formspec which allows writing.
  138. local formspec = "size[10,5]" ..
  139. default.gui_bg ..
  140. default.gui_slots ..
  141. default.gui_bg_img ..
  142. "label[0,0;Write letter below.]" ..
  143. "textarea[0.3,0.5;10,3;text;;" .. minetest.formspec_escape(info.text) .. "]" ..
  144. "field[0.3,4.3;4,1;signed;Sign Letter (Optional);" .. minetest.formspec_escape(info.signed) .. "]" ..
  145. "item_image[7,4;1,1;default:paper]" ..
  146. "button_exit[8,4;2,1;done;Done]"
  147. -- ^^^ Edit formspec field names are used, `text` & `signed`.
  148. -- The other formspecs *must not* use these fieldnames.
  149. return formspec
  150. elseif info.edit == 0 then
  151. -- Obtain formspec which allows viewing.
  152. local formspec = "size[10,8]" ..
  153. default.gui_bg ..
  154. default.gui_slots ..
  155. default.gui_bg_img ..
  156. "label[0,0;On this sheet of paper is written a message:]" ..
  157. "textarea[0.3,1;10,6;letter;;" .. minetest.formspec_escape(info.text) .. "]" ..
  158. "item_image[7,7;1,1;memorandum:letter]" ..
  159. "button_exit[8,7;2,1;exit;Close]" ..
  160. "label[0,7.3;Letters can be edited with an eraser.]"
  161. if type(info.signed) == "string" and info.signed ~= "" then
  162. formspec = formspec .. "label[0,7;Signed by <" .. minetest.formspec_escape(info.signed) .. ">]"
  163. else
  164. formspec = formspec .. "label[0,7;Letter is not signed.]"
  165. end
  166. return formspec
  167. elseif info.edit == 1 then
  168. -- Obtain 'edit' formspec (this formspec does not actually handle edit permissions).
  169. local formspec = "size[10,5]" ..
  170. default.gui_bg ..
  171. default.gui_slots ..
  172. default.gui_bg_img ..
  173. "label[0,0;Edit letter below:]" ..
  174. "textarea[0.3,0.5;10,3;text;;" .. minetest.formspec_escape(info.text) .. "]" ..
  175. "field[0.3,4.3;4,1;signed;Edit Signature;" .. minetest.formspec_escape(info.signed) .. "]" ..
  176. "item_image[7,4;1,1;memorandum:letter]" ..
  177. "button_exit[8,4;2,1;exit;Done]"
  178. -- ^^^ Edit formspec field names are used, `text` & `signed`.
  179. -- The other formspecs *must not* use these fieldnames.
  180. return formspec
  181. end
  182. return ""
  183. end
  184. -- This is called when default paper is converted to memorandum (an in-world node).
  185. memorandum.on_paper_initialize = function(itemstack, placer, pointed_thing)
  186. if not placer then return end
  187. if not placer:is_player() then return end
  188. -- Pass-through rightclicks if target node defines it.
  189. -- This mimics the behavior of `minetest.item_place()`.
  190. if pointed_thing.type == "node" then
  191. local under = pointed_thing.under
  192. local node = minetest.get_node(under)
  193. if node.name ~= "ignore" then
  194. local ndef = minetest.registered_nodes[node.name]
  195. if ndef and ndef.on_rightclick then
  196. return ndef.on_rightclick(under, node, placer, itemstack, pointed_thing)
  197. end
  198. end
  199. end
  200. local above = pointed_thing.above
  201. -- Ensure we are replacing air.
  202. if minetest.get_node(above).name ~= "air" then return end
  203. -- Do not place empty paper if protected.
  204. if memorandum.is_protected(above, placer:get_player_name()) then
  205. return
  206. end
  207. -- Place empty letter in world.
  208. local under = pointed_thing.under
  209. local facedir = minetest.dir_to_facedir(placer:get_look_dir())
  210. if (above.x ~= under.x) or (above.z ~= under.z) then
  211. minetest.set_node(above, {name="memorandum:letter_empty", param2=param2_walldirections[facedir+1]})
  212. else
  213. minetest.set_node(above, {name="memorandum:letter_empty", param2=facedir})
  214. end
  215. dirtspread.on_environment(above)
  216. droplift.notify(above)
  217. itemstack:take_item()
  218. return itemstack
  219. end
  220. -- This is called when memorandum is initialized into an in-world node state.
  221. memorandum.on_letter_empty_initialize = function(pos)
  222. local meta = minetest.get_meta(pos)
  223. meta:set_string("infotext", "A Sheet of Paper")
  224. meta:set_int("edit", 2) -- 2 means write new letter (previously blank).
  225. meta:mark_as_private("edit")
  226. end
  227. -- Called when an empty letter receives formspec data.
  228. memorandum.on_letter_empty_input = function(pos, fields, sender)
  229. if not (fields.text and fields.signed) then return end
  230. if fields.text == "" then return end -- If no text was received, the letter remains empty.
  231. -- Do not allow player to edit memorandum if protected.
  232. if memorandum.is_protected(pos, sender:get_player_name()) then
  233. minetest.chat_send_player(sender:get_player_name(), "# Server: This memorandum cannot be written.")
  234. return
  235. end
  236. -- Player does not need an eraser to write on blank paper,
  237. -- so no check to see if player is wielding eraser.
  238. local facedir = minetest.get_node(pos).param2
  239. minetest.add_node(pos, {name="memorandum:letter_written", param2=facedir})
  240. local meta = minetest.get_meta(pos)
  241. meta:set_string("text", fields.text:sub(1, MAX_LETTER_SIZE))
  242. meta:set_string("signed", fields.signed:sub(1, MAX_AUTHOR_SIZE))
  243. meta:set_string("owner", sender:get_player_name()) -- Record REAL author.
  244. meta:set_int("edit", 0)
  245. meta:set_string("infotext", "A Sheet of Paper (Written)")
  246. meta:mark_as_private({"text", "signed", "edit", "owner"})
  247. end
  248. -- Called when an already-written letter (existing as a node) receives fields from user.
  249. -- This only happens when the letter is to be edited.
  250. memorandum.on_letter_written_input = function(pos, fields, sender)
  251. if not sender then return end
  252. if not sender:is_player() then return end
  253. -- These fields can only be set in the presence of an enabled editing formspec.
  254. -- The viewing formspecs do not have these fields.
  255. if not fields.text then return end
  256. if not fields.signed then return end
  257. local sendername = sender:get_player_name()
  258. local item = sender:get_wielded_item()
  259. if not item then return end
  260. if item:get_name() ~= "memorandum:eraser" then
  261. minetest.chat_send_player(sendername, "# Server: You are not wielding an eraser.")
  262. return
  263. end
  264. -- Do not edit memorandum if protected.
  265. if memorandum.is_protected(pos, sendername) then
  266. minetest.chat_send_player(sendername, "# Server: Memorandum is protected.")
  267. return
  268. end
  269. local meta = minetest.get_meta(pos)
  270. local signer = meta:get_string("signed")
  271. -- Don't allow letter to be modified if signer is not player and signer isn't blank.
  272. -- We need this check to prevent a situation in which a second player edits a letter that
  273. -- was erased by a first player. However, two or more players can still edit a letter that isn't signed by any of them.
  274. if not (rename.grn(signer) == sendername or signer == "") then
  275. minetest.chat_send_player(sendername, "# Server: This paper is guarded by its signature.")
  276. return
  277. end
  278. if meta:get_int("edit") ~= 1 then
  279. minetest.chat_send_player(sendername, "# Server: Letter was not being edited! It cannot be changed now.")
  280. return
  281. end
  282. if fields.text == "" then
  283. -- If the letter was erased, replace it with an empty letter.
  284. -- This, if dug, converts back to default paper.
  285. local facedir = minetest.get_node(pos).param2
  286. minetest.swap_node(pos, {name="memorandum:letter_empty", param2=facedir})
  287. meta:set_string("infotext", "A Sheet of Paper")
  288. meta:set_int("edit", 2) -- Means may be edited again as if fresh paper.
  289. else
  290. meta:set_string("infotext", "A Sheet of Paper (Written)")
  291. meta:set_int("edit", 0)
  292. end
  293. meta:set_string("text", fields.text:sub(1, MAX_LETTER_SIZE))
  294. meta:set_string("signed", fields.signed:sub(1, MAX_AUTHOR_SIZE))
  295. meta:set_string("owner", sendername)
  296. meta:mark_as_private({"text", "signed", "edit"})
  297. minetest.chat_send_player(sendername, "# Server: Memorandum successfully edited.")
  298. end
  299. -- Called when an empty letter is dug, in which case it should convert back to default paper.
  300. memorandum.on_letter_empty_dig = function(pos, node, digger)
  301. if not digger then return end
  302. if not digger:is_player() then return end
  303. -- Do not dig empty paper if protected.
  304. if memorandum.is_protected(pos, digger:get_player_name()) then
  305. return
  306. end
  307. local inv = digger:get_inventory()
  308. inv:add_item("main", {name="default:paper", count=1, wear=0, metadata=""})
  309. minetest.remove_node(pos)
  310. end
  311. -- Called when a written letter craftitem needs to show a formspec with the letter's contents.
  312. memorandum.show_message_formspec = function(player, message, author)
  313. minetest.show_formspec(player, "memorandum:reading", memorandum.get_formspec({signed=author, text=message, edit=0}))
  314. end
  315. -- Called when a written letter is 'used' while held as a craftitem.
  316. -- It should show a formspec to the player displaying the letter's contents.
  317. memorandum.on_letter_item_use = function(itemstack, user, pointed_thing)
  318. if not user then return end
  319. if not user:is_player() then return end
  320. local text = itemstack:get_metadata()
  321. local data = memorandum.extract_metainfo(text)
  322. local player = user:get_player_name()
  323. if memorandum.check_explosive_runes(player, user:get_pos(), data.author, data.message) then
  324. itemstack:take_item()
  325. end
  326. memorandum.show_message_formspec(player, data.message, data.author)
  327. return itemstack
  328. end
  329. -- Called when a written letter craftitem is to be placed in the world.
  330. memorandum.on_letter_item_place = function(itemstack, placer, pointed_thing)
  331. if not placer then return end
  332. if not placer:is_player() then return end
  333. -- Pass-through rightclicks if target node defines it.
  334. -- This mimics the behavior of `minetest.item_place()`.
  335. if pointed_thing.type == "node" then
  336. local under = pointed_thing.under
  337. local node = minetest.get_node(under)
  338. if node.name ~= "ignore" then
  339. local ndef = minetest.registered_nodes[node.name]
  340. if ndef and ndef.on_rightclick then
  341. return ndef.on_rightclick(under, node, placer, itemstack, pointed_thing)
  342. end
  343. end
  344. end
  345. -- We specifically allow players to place written letters in other player's protected areas.
  346. -- This allows players to post messages to each other without requiring mailboxes.
  347. -- Note that no one will be able to dig the letter except the owner of the area, or the player who placed it.
  348. local above = pointed_thing.above
  349. local under = pointed_thing.under
  350. local facedir = minetest.dir_to_facedir(placer:get_look_dir())
  351. -- Ensure we are replacing air.
  352. if minetest.get_node(above).name ~= "air" then return end
  353. if (above.x ~= under.x) or (above.z ~= under.z) then
  354. minetest.add_node(above, {name="memorandum:letter_written", param2=param2_walldirections[facedir+1]})
  355. else
  356. minetest.add_node(above, {name="memorandum:letter_written", param2=facedir})
  357. end
  358. local text = itemstack:get_metadata()
  359. local data = memorandum.extract_metainfo(text)
  360. local meta = minetest.get_meta(above)
  361. meta:set_string("infotext", "A Sheet of Paper (Written)")
  362. meta:set_string("text", data.message)
  363. meta:set_string("signed", data.author)
  364. meta:mark_as_private({"text", "signed"})
  365. itemstack:take_item()
  366. return itemstack
  367. end
  368. -- Called when a written letter (existing as a node) is being dug.
  369. memorandum.on_letter_written_dig = function(pos, node, digger)
  370. if not digger then return end
  371. if not digger:is_player() then return end
  372. -- Do not remove memorandum if protected.
  373. if memorandum.is_protected(pos, digger:get_player_name()) then
  374. return
  375. end
  376. local meta = minetest.get_meta(pos)
  377. local serialized = memorandum.insert_metainfo(meta)
  378. local item = digger:get_wielded_item()
  379. local inv = digger:get_inventory()
  380. -- If dug using a glass bottle, put the letter in the bottle.
  381. -- Otherwise, the letter becomes a regular craftitem.
  382. if item:get_name() == "vessels:glass_bottle" then
  383. inv:remove_item("main", "vessels:glass_bottle")
  384. inv:add_item("main", {name="memorandum:message", count=1, wear=0, metadata=serialized})
  385. else
  386. inv:add_item("main", {name="memorandum:letter", count=1, wear=0, metadata=serialized})
  387. end
  388. minetest.remove_node(pos)
  389. end
  390. -- Wear out an eraser on each use.
  391. memorandum.add_eraser_wear = function(itemstack, user, pointed_thing, uses)
  392. itemstack:add_wear(65535/(uses-1))
  393. return itemstack
  394. end
  395. -- Called when an eraser is used.
  396. memorandum.on_eraser_use = function(itemstack, user, pointed_thing)
  397. if not user then return end
  398. if not user:is_player() then return end
  399. if not pointed_thing.under then return end
  400. local pos = pointed_thing.under
  401. local node = minetest.get_node(pos)
  402. local player = user:get_player_name()
  403. -- Do not erase memorandum if protected.
  404. if memorandum.is_protected(pos, player) then
  405. minetest.chat_send_player(player, "# Server: Memorandum is guarded against editing or erasure.")
  406. return
  407. end
  408. -- Ensure we are pointing at an erasable letter.
  409. if node.name ~= "memorandum:letter_written" then return end
  410. local meta = minetest.get_meta(pos)
  411. local signer = meta:get_string("signed")
  412. -- Don't allow letter to be modified if signer is not player and signer isn't blank.
  413. if not (rename.grn(signer) == player or signer == "") then
  414. minetest.chat_send_player(player, "# Server: This paper is guarded by its signature.")
  415. return
  416. end
  417. meta:set_int("edit", 1) -- 1 means edit already-written letter.
  418. meta:mark_as_private("edit")
  419. itemstack = memorandum.add_eraser_wear(itemstack, user, pointed_thing, 30)
  420. minetest.chat_send_player(player, "# Server: Memorandum may be edited. Rightclick to edit, press 'Done' to confirm your edits.")
  421. return itemstack
  422. end
  423. -- Called when message-in-a-bottle is used.
  424. memorandum.on_message_use = function(itemstack, user, pointed_thing)
  425. if not user then return end
  426. if not user:is_player() then return end
  427. if not pointed_thing.under then return end
  428. local pos = pointed_thing.above
  429. if minetest.get_node(pos).name ~= "air" then return end
  430. -- We specifically allow players to place written letters in other player's protected areas.
  431. -- This allows players to post messages to each other without requiring mailboxes.
  432. -- Note that no one will be able to dig the letter except the owner of the area, or the player who placed it.
  433. minetest.add_node(pos, {name="memorandum:letter_written", param2=math_random(0, 3)})
  434. local text = itemstack:get_metadata()
  435. local data = memorandum.extract_metainfo(text)
  436. local meta = minetest.get_meta(pos)
  437. meta:set_string("infotext", "A Sheet of Paper (Written)")
  438. meta:set_string("text", data.message)
  439. meta:set_string("signed", data.author)
  440. meta:mark_as_private({"text", "signed"})
  441. itemstack:take_item()
  442. user:get_inventory():add_item("main", {name="vessels:glass_bottle", count=1, wear=0, metadata=""})
  443. return itemstack
  444. end
  445. -- Called when message-in-a-bottle is placed.
  446. memorandum.on_message_place = function(itemstack, placer, pointed_thing)
  447. if not placer then return end
  448. if not placer:is_player() then return end
  449. -- Pass-through rightclicks if target node defines it.
  450. -- This mimics the behavior of `minetest.item_place()`.
  451. if pointed_thing.type == "node" then
  452. local under = pointed_thing.under
  453. local node = minetest.get_node(under)
  454. if node.name ~= "ignore" then
  455. local ndef = minetest.registered_nodes[node.name]
  456. if ndef and ndef.on_rightclick then
  457. return ndef.on_rightclick(under, node, placer, itemstack, pointed_thing)
  458. end
  459. end
  460. end
  461. local pos = pointed_thing.above
  462. if minetest.get_node(pos).name ~= "air" then return end
  463. -- We specifically allow players to place written letters in other player's protected areas.
  464. -- This allows players to post messages to each other without requiring mailboxes.
  465. -- Note that no one will be able to dig the letter except the owner of the area, or the player who placed it.
  466. minetest.set_node(pos, {name="memorandum:message"})
  467. local text = itemstack:get_metadata()
  468. local data = memorandum.extract_metainfo(text)
  469. local meta = minetest.get_meta(pos)
  470. meta:set_string("text", data.message)
  471. meta:set_string("signed", data.author)
  472. meta:set_string("infotext", "Bottle With Message")
  473. meta:mark_as_private({"text", "signed"})
  474. dirtspread.on_environment(pos)
  475. droplift.notify(pos)
  476. itemstack:take_item()
  477. return itemstack
  478. end
  479. -- Called when message-in-a-bottle is being dug.
  480. memorandum.on_message_dig = function(pos, node, digger)
  481. if not digger then return end
  482. if not digger:is_player() then return end
  483. -- Do not allow dig if protected.
  484. -- The author of the message may always dig.
  485. if memorandum.is_protected(pos, digger:get_player_name()) then
  486. return
  487. end
  488. local meta = minetest.get_meta(pos)
  489. local serialized = memorandum.insert_metainfo(meta)
  490. local inv = digger:get_inventory()
  491. inv:add_item("main", {name="memorandum:message", count=1, wear=0, metadata=serialized})
  492. minetest.remove_node(pos)
  493. end
  494. -- Running gag.
  495. function memorandum.check_explosive_runes(pname, pos, author, text)
  496. -- Validate arguments.
  497. if type(pname) ~= "string" or type(pos) ~= "table" or type(author) ~= "string" or type(text) ~= "string" then
  498. return
  499. end
  500. -- Explosive runes never explode on the writer.
  501. if pname == author then
  502. return
  503. end
  504. text = text:lower()
  505. if text:find("explosive") and text:find("rune") then
  506. local p = vector_round({x=pos.x, y=pos.y, z=pos.z})
  507. local d = {
  508. radius = math_random(1, math_random(1, 4)),
  509. damage_radius = math_random(5, 15),
  510. ignore_protection = false,
  511. disable_drops = true,
  512. ignore_on_blast = false,
  513. }
  514. local t = math_floor(text:len() / 10)
  515. minetest.after((t / 2.0), function() minetest.sound_play("tnt_ignite", {pos = pos}, true) end)
  516. minetest.after(t, function() tnt.boom(p, d) end)
  517. -- Indicates the memorandum should be removed.
  518. -- Either from player's inventory, or from the world if placed as a node.
  519. -- If memorandum would explode, we must always remove it to prevent a repeat.
  520. return true
  521. end
  522. end
  523. -- Code which runs only on first load.
  524. if not memorandum.run_once then
  525. -- Allow paper to be placed in the world, as an empty letter node.
  526. minetest.override_item("default:paper", {
  527. on_place = function(...) return memorandum.on_paper_initialize(...) end,
  528. })
  529. -- An empty letter is the intermediate state between default paper and a written letter.
  530. -- Note that the empty letter can exist only as a node, if dug it reverts to default paper.
  531. minetest.register_node("memorandum:letter_empty", {
  532. drawtype = "nodebox",
  533. tiles = {
  534. "memorandum_letter_empty.png",
  535. "memorandum_letter_empty.png^[transformFY" -- mirror
  536. },
  537. paramtype = "light",
  538. paramtype2 = "facedir",
  539. sunlight_propagates = true,
  540. walkable = false,
  541. node_box = {
  542. type = "fixed",
  543. fixed = collisionbox_sheet,
  544. },
  545. groups = utility.dig_groups("item", {not_in_creative_inventory=1}),
  546. sounds = default.node_sound_leaves_defaults(),
  547. on_construct = function(...) return memorandum.on_letter_empty_initialize(...) end,
  548. on_rightclick = function(...) return memorandum.on_rightclick(...) end,
  549. on_dig = function(...) return memorandum.on_letter_empty_dig(...) end,
  550. })
  551. -- A written letter craftitem. Can be read.
  552. minetest.register_craftitem("memorandum:letter", {
  553. description = "Written Letter",
  554. inventory_image = "default_paper.png^memorandum_letters.png",
  555. stack_max = 1,
  556. groups = {not_in_creative_inventory=1},
  557. on_use = function(...) return memorandum.on_letter_item_use(...) end,
  558. on_place = function(...) return memorandum.on_letter_item_place(...) end,
  559. })
  560. -- A written letter, in-world node version.
  561. -- The in-world node version can be edited using an eraser, in addition to being read.
  562. minetest.register_node("memorandum:letter_written", {
  563. drawtype = "nodebox",
  564. tiles = {
  565. "memorandum_letter_empty.png^memorandum_letter_text.png",
  566. "memorandum_letter_empty.png^[transformFY" -- mirror
  567. },
  568. paramtype = "light",
  569. paramtype2 = "facedir",
  570. sunlight_propagates = true,
  571. walkable = false,
  572. node_box = {
  573. type = "fixed",
  574. fixed = collisionbox_sheet
  575. },
  576. groups = utility.dig_groups("item", {not_in_creative_inventory=1}),
  577. sounds = default.node_sound_leaves_defaults(),
  578. on_rightclick = function(...) return memorandum.on_rightclick(...) end,
  579. on_dig = function(...) return memorandum.on_letter_written_dig(...) end,
  580. })
  581. -- An eraser, used to edit letters than have already been written.
  582. minetest.register_tool("memorandum:eraser", {
  583. description = "Writing Eraser",
  584. inventory_image = "memorandum_eraser.png",
  585. wield_image = "memorandum_eraser.png^[transformR90",
  586. wield_scale = {x = 0.5, y = 0.5, z = 1},
  587. groups = {not_repaired_by_anvil = 1},
  588. on_use = function(...) return memorandum.on_eraser_use(...) end,
  589. })
  590. -- Message-in-a-bottle. This is created by digging a written letter node while wielding a glass vessel.
  591. minetest.register_node("memorandum:message", {
  592. description = "Message In A Bottle",
  593. drawtype = "plantlike",
  594. tiles = {"vessels_glass_bottle.png^memorandum_message.png"},
  595. inventory_image = "vessels_glass_bottle.png^memorandum_message.png",
  596. wield_image = "vessels_glass_bottle.png^memorandum_message.png",
  597. paramtype = "light",
  598. selection_box = {
  599. type = "fixed",
  600. fixed = collisionbox_glass,
  601. },
  602. stack_max = 1,
  603. walkable = false,
  604. groups = utility.dig_groups("item", {vessel=1, attached_node=1, not_in_creative_inventory=1}),
  605. movement_speed_multiplier = default.SLOW_SPEED_PLANTS,
  606. --sounds = default.node_sound_glass_defaults(),
  607. on_use = function(...) return memorandum.on_message_use(...) end,
  608. on_place = function(...) return memorandum.on_message_place(...) end,
  609. on_dig = function(...) return memorandum.on_message_dig(...) end,
  610. })
  611. if minetest.get_modpath("farming") ~= nil then
  612. minetest.register_craft({
  613. type = "shapeless",
  614. output = "memorandum:eraser",
  615. recipe = {"farming:bread"},
  616. })
  617. end
  618. if minetest.get_modpath("candles") ~= nil then
  619. minetest.register_craft({
  620. type = "shapeless",
  621. output = "memorandum:eraser",
  622. recipe = {"candles:wax"},
  623. })
  624. end
  625. if minetest.get_modpath("bees") ~= nil then
  626. minetest.register_craft({
  627. type = "shapeless",
  628. output = "memorandum:eraser",
  629. recipe = {"bees:honey_comb"},
  630. })
  631. end
  632. if minetest.get_modpath("technic") ~= nil then
  633. minetest.register_craft({
  634. type = "shapeless",
  635. output = "memorandum:eraser",
  636. recipe = {"technic:raw_latex"},
  637. })
  638. end
  639. minetest.register_on_player_receive_fields(function(...)
  640. return memorandum.on_player_receive_fields(...)
  641. end)
  642. -- Reloadable.
  643. local name = "memorandum:core"
  644. local file = memorandum.modpath .. "/init.lua"
  645. reload.register_file(name, file, false)
  646. memorandum.run_once = true
  647. end