init.lua 26 KB

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