init.lua 28 KB

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