init.lua 26 KB

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