init.lua 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819
  1. personal_log = {}
  2. local modname = minetest.get_current_modname()
  3. local modpath = minetest.get_modpath(modname)
  4. local ccompass_modpath = minetest.get_modpath("ccompass")
  5. local compassgps_modpath = minetest.get_modpath("compassgps")
  6. local default_modpath = minetest.get_modpath("default")
  7. local unified_inventory_modpath = minetest.get_modpath("unified_inventory")
  8. local sfinv_buttons_modpath = minetest.get_modpath("sfinv_buttons")
  9. local sfinv_modpath = minetest.get_modpath("sfinv")
  10. local mcl_books_modpath = minetest.get_modpath("mcl_books")
  11. local modstore = minetest.get_mod_storage()
  12. local ccompass_recalibration_allowed = minetest.settings:get_bool("ccompass_recalibrate", true)
  13. local ccompass_restrict_target = minetest.settings:get_bool("ccompass_restrict_target", false)
  14. local ccompass_description_prefix = "^Compass to "
  15. local S = minetest.get_translator(modname)
  16. local categories = {
  17. S("Location"),
  18. S("Event"),
  19. S("General"),
  20. }
  21. local LOCATION_CATEGORY = 1
  22. local EVENT_CATEGORY = 2
  23. local GENERAL_CATEGORY = 3
  24. -- used for determining if an item is a ccompass
  25. local ccompass_prefix = "ccompass:"
  26. local ccompass_prefix_length = #ccompass_prefix
  27. local book_unwritten
  28. local book_written
  29. local author_meta_field
  30. if default_modpath then
  31. book_unwritten = "default:book"
  32. book_written = "default:book_written"
  33. author_meta_field = "owner"
  34. elseif mcl_books_modpath then
  35. book_unwritten = "mcl_books:book"
  36. book_written = "mcl_books:written_book"
  37. author_meta_field = "author"
  38. end
  39. local mcl_formspec_itemslot
  40. if mcl_formspec then
  41. mcl_formspec_itemslot = mcl_formspec.get_itemslot_bg
  42. end
  43. --------------------------------------------------------
  44. -- Data store
  45. local function get_state(player_name)
  46. local state = modstore:get(player_name .. "_state")
  47. if state then
  48. state = minetest.deserialize(state)
  49. end
  50. if not state then
  51. state = {category=LOCATION_CATEGORY, entry_selected={0,0,0}, entry_counts={0,0,0}}
  52. end
  53. return state
  54. end
  55. local function save_state(player_name, state)
  56. modstore:set_string(player_name .. "_state", minetest.serialize(state))
  57. end
  58. local function save_entry(player_name, category_index, entry_index, entry_text, topic_text)
  59. if topic_text then
  60. topic_text = topic_text:gsub("\r\n", "\n"):gsub("\r", "\n"):gsub("\n", " ")
  61. modstore:set_string(player_name .. "_category_" .. category_index .. "_entry_" .. entry_index .. "_topic",
  62. topic_text)
  63. end
  64. entry_text = entry_text:gsub("\r\n", "\n"):gsub("\r", "\n")
  65. modstore:set_string(player_name .. "_category_" .. category_index .. "_entry_" .. entry_index .. "_content",
  66. entry_text)
  67. end
  68. local function swap_entry(player_name, state, direction)
  69. local category_index = state.category
  70. local entry_index = state.entry_selected[category_index]
  71. local next_index = entry_index + direction
  72. if next_index < 1 or next_index > state.entry_counts[category_index] then
  73. return
  74. end
  75. local current_topic = modstore:get_string(player_name .. "_category_" .. category_index .. "_entry_" .. entry_index .. "_topic")
  76. local current_content = modstore:get_string(player_name .. "_category_" .. category_index .. "_entry_" .. entry_index .. "_content")
  77. local next_topic = modstore:get_string(player_name .. "_category_" .. category_index .. "_entry_" .. next_index .. "_topic")
  78. local next_content = modstore:get_string(player_name .. "_category_" .. category_index .. "_entry_" .. next_index .. "_content")
  79. save_entry(player_name, category_index, entry_index, next_content, next_topic)
  80. save_entry(player_name, category_index, next_index, current_content, current_topic)
  81. state.entry_selected[category_index] = next_index
  82. save_state(player_name, state)
  83. end
  84. local function delete_entry(player_name, state)
  85. local category_index = state.category
  86. local entry_count = state.entry_counts[category_index]
  87. if entry_count == 0 then
  88. return
  89. end
  90. local entry_index = state.entry_selected[category_index]
  91. for i = entry_index + 1, entry_count do
  92. local topic = modstore:get_string(player_name .. "_category_" .. category_index .. "_entry_" .. i .. "_topic")
  93. local content = modstore:get_string(player_name .. "_category_" .. category_index .. "_entry_" .. i .. "_content")
  94. save_entry(player_name, category_index, i-1, content, topic)
  95. end
  96. modstore:set_string(player_name .. "_category_" .. category_index .. "_entry_" .. entry_count .. "_topic", "")
  97. modstore:set_string(player_name .. "_category_" .. category_index .. "_entry_" .. entry_count .. "_content", "")
  98. entry_count = entry_count - 1
  99. state.entry_counts[category_index] = entry_count
  100. if entry_index > entry_count then
  101. state.entry_selected[category_index] = entry_count
  102. end
  103. save_state(player_name, state)
  104. end
  105. ----------------------------------------------------------------------------------------
  106. -- String functions
  107. local truncate_string = function(target, length)
  108. if target:len() > length then
  109. return target:sub(1,length-2).."..."
  110. end
  111. return target
  112. end
  113. local first_line = function(target)
  114. local first_return = target:find("\n")
  115. if not first_return then
  116. first_return = #target
  117. else
  118. first_return = first_return - 1 -- trim the hard return off
  119. end
  120. return target:sub(1, first_return)
  121. end
  122. ---------------------------------------
  123. -- Reading and writing stuff to items
  124. ----------------------------------------------------------------
  125. -- Export
  126. -- Book parameters
  127. local lpp = 14
  128. local max_text_size = 10000
  129. local max_title_size = 80
  130. local short_title_size = 35
  131. local function write_book(player_name)
  132. local state = get_state(player_name)
  133. local category = state.category
  134. local entry_selected = state.entry_selected[category]
  135. local content = modstore:get_string(player_name .. "_category_" .. category .. "_entry_" .. entry_selected .. "_content")
  136. local topic = modstore:get_string(player_name .. "_category_" .. category .. "_entry_" .. entry_selected .. "_topic")
  137. if state.category ~= 3 then
  138. -- If it's a location or an event, add a little context to the title
  139. topic = topic .. ": " .. first_line(content)
  140. end
  141. local new_book = ItemStack(book_written)
  142. local meta = new_book:get_meta()
  143. meta:set_string(author_meta_field, player_name)
  144. meta:set_string("title", topic:sub(1, max_title_size))
  145. meta:set_string("description", S("\"@1\" by @2", truncate_string(topic, short_title_size), player_name))
  146. meta:set_string("text", content:sub(1, max_text_size))
  147. meta:set_int("page", 1)
  148. meta:set_int("page_max", math.ceil((#content:gsub("[^\n]", "") + 1) / lpp))
  149. return new_book
  150. end
  151. local function write_cgpsmap(player_name)
  152. local state = get_state(player_name)
  153. local category = state.category
  154. if category ~= LOCATION_CATEGORY then
  155. return
  156. end
  157. local entry_selected = state.entry_selected[category]
  158. local content = modstore:get_string(player_name .. "_category_" .. category .. "_entry_" .. entry_selected .. "_content")
  159. local pos_string = modstore:get_string(player_name .. "_category_" .. category .. "_entry_" .. entry_selected .. "_topic")
  160. local meta = minetest.string_to_pos(pos_string)
  161. if not meta then
  162. return
  163. end
  164. meta.bkmrkname = content
  165. local new_map = ItemStack("compassgps:cgpsmap_marked")
  166. -- TODO: set_metadata is a deprecated function, but it is necessary because that's what cgpsmap uses.
  167. new_map:set_metadata(minetest.serialize(meta))
  168. return new_map
  169. end
  170. local function write_ccompass(player_name, old_compass)
  171. local state = get_state(player_name)
  172. local category = state.category
  173. if category ~= LOCATION_CATEGORY then
  174. return
  175. end
  176. local entry_selected = state.entry_selected[category]
  177. local topic = modstore:get_string(player_name .. "_category_" .. category .. "_entry_" .. entry_selected .. "_topic")
  178. local pos = minetest.string_to_pos(topic)
  179. if not pos then
  180. return
  181. end
  182. local content = modstore:get_string(player_name .. "_category_" .. category .. "_entry_" .. entry_selected .. "_content")
  183. content = truncate_string(first_line(content), max_title_size)
  184. local new_ccompass = ItemStack("ccompass:0")
  185. local param = {
  186. target_pos_string = topic,
  187. target_name = content,
  188. playername = player_name
  189. }
  190. ccompass.set_target(new_ccompass, param)
  191. return new_ccompass
  192. end
  193. local function write_item(player_name, itemstack)
  194. local item_name = itemstack:get_name()
  195. if item_name == book_unwritten then
  196. return write_book(player_name)
  197. end
  198. if item_name == "compassgps:cgpsmap" then
  199. return write_cgpsmap(player_name)
  200. end
  201. if item_name:sub(1,ccompass_prefix_length) == ccompass_prefix then
  202. return write_ccompass(player_name, itemstack)
  203. end
  204. end
  205. ----------------------------------------------------------------------------------------
  206. -- Import
  207. local function read_book(itemstack, player_name)
  208. local meta = itemstack:get_meta()
  209. local topic = meta:get_string("title")
  210. local content = meta:get_string("text")
  211. local date_string = topic:match("^%d%d%d%d%-%d%d%-%d%d")
  212. local pos_string = topic:match("^%(%-?[0-9]+,%-?[0-9]+,%-?[0-9]+%)")
  213. local category = GENERAL_CATEGORY
  214. if date_string then
  215. topic = date_string
  216. category = EVENT_CATEGORY
  217. elseif pos_string then
  218. topic = pos_string
  219. category = LOCATION_CATEGORY
  220. end
  221. local state = get_state(player_name)
  222. local entry_index = state.entry_counts[category] + 1
  223. state.entry_counts[category] = entry_index
  224. save_entry(player_name, category, entry_index, content, topic)
  225. save_state(player_name, state)
  226. end
  227. local function read_ccompass(itemstack, player_name)
  228. local meta = itemstack:get_meta()
  229. local topic = meta:get_string("target_pos")
  230. local content = meta:get_string("description")
  231. local prefix_start, prefix_end = content:find(ccompass_description_prefix)
  232. if prefix_end then
  233. content = content:sub(prefix_end+1)
  234. end
  235. local state = get_state(player_name)
  236. local entry_index = state.entry_counts[LOCATION_CATEGORY] + 1
  237. state.entry_counts[LOCATION_CATEGORY] = entry_index
  238. save_entry(player_name, LOCATION_CATEGORY, entry_index, content, topic)
  239. save_state(player_name, state)
  240. end
  241. local function read_cgpsmap(itemstack, player_name)
  242. -- TODO: get_metadata is a deprecated function, but it is necessary because that's what cgpsmap uses.
  243. local meta = minetest.deserialize(itemstack:get_metadata())
  244. if not (meta and meta.x and meta.y and meta.z) then
  245. return
  246. end
  247. local content = meta.bkmrkname or ""
  248. local topic = minetest.pos_to_string(meta)
  249. local state = get_state(player_name)
  250. local entry_index = state.entry_counts[LOCATION_CATEGORY] + 1
  251. state.entry_counts[LOCATION_CATEGORY] = entry_index
  252. save_entry(player_name, LOCATION_CATEGORY, entry_index, content, topic)
  253. save_state(player_name, state)
  254. end
  255. local function read_item(itemstack, player_name)
  256. local item_name = itemstack:get_name()
  257. if item_name == book_written then
  258. read_book(itemstack, player_name)
  259. elseif item_name == "compassgps:cgpsmap_marked" then
  260. read_cgpsmap(itemstack, player_name)
  261. elseif item_name:sub(1,ccompass_prefix_length) == ccompass_prefix then
  262. read_ccompass(itemstack, player_name)
  263. end
  264. end
  265. --------------------------------------------------------------------------
  266. -- Detached inventory
  267. -- Allow or disallow ccompasses based on whether they've got target info in their meta
  268. local function ccompass_permitted_target(itemstack)
  269. if ccompass_restrict_target then
  270. -- We have no idea whether there's an allowed node at this location, so don't allow
  271. -- setting compasses when node type restriction is enabled.
  272. return false
  273. end
  274. if not (itemstack:get_name():sub(1,ccompass_prefix_length) == ccompass_prefix) then
  275. return false
  276. end
  277. local meta = itemstack:get_meta()
  278. local has_pos = minetest.string_to_pos(meta:get_string("target_pos"))
  279. if has_pos and not ccompass_recalibration_allowed then
  280. return false
  281. end
  282. return true
  283. end
  284. local function ccompass_permitted_source(itemstack)
  285. if not itemstack:get_name():sub(1,ccompass_prefix_length) == ccompass_prefix then
  286. return false
  287. end
  288. local meta = itemstack:get_meta()
  289. local has_pos = minetest.string_to_pos(meta:get_string("target_pos"))
  290. if not has_pos then
  291. return false
  292. end
  293. return true
  294. end
  295. local detached_callbacks = {
  296. allow_put = function(inv, listname, index, stack, player)
  297. local stack_name = stack:get_name()
  298. if listname == "export_item" then
  299. if stack_name == book_unwritten then
  300. return 1
  301. end
  302. local player_name = player:get_player_name()
  303. local state = get_state(player_name)
  304. local category = state.category
  305. if category == LOCATION_CATEGORY and
  306. (stack_name == "compassgps:cgpsmap" or
  307. ccompass_permitted_target(stack)) then
  308. return 1
  309. end
  310. return 0
  311. elseif listname == "import_item" then
  312. if stack_name == book_written or
  313. stack_name == "compassgps:cgpsmap_marked" or
  314. ccompass_permitted_source(stack) then
  315. return 1
  316. end
  317. return 0
  318. end
  319. return 0
  320. end,
  321. on_put = function(inv, listname, index, stack, player)
  322. local player_name = player:get_player_name()
  323. if listname == "export_item" then
  324. local new_item = write_item(player_name, stack)
  325. inv:remove_item(listname, stack)
  326. inv:add_item(listname, new_item)
  327. elseif listname == "import_item" then
  328. read_item(stack, player_name)
  329. end
  330. end,
  331. }
  332. local item_invs = {}
  333. local function ensure_detached_inventory(player_name)
  334. if item_invs[player_name] or not(default_modpath or mcl_books_modpath or ccompass_modpath or compassgps_modpath) then
  335. return
  336. end
  337. local inv = minetest.create_detached_inventory("personal_log_"..player_name, detached_callbacks)
  338. inv:set_size("export_item", 1)
  339. inv:set_size("import_item", 1)
  340. item_invs[player_name] = true
  341. end
  342. -- if a player leaves stuff in their detached inventory, try giving it to them when they exit
  343. local function try_return(detached_inv, player_inv, listname)
  344. local item = detached_inv:get_stack(listname, 1)
  345. item = player_inv:add_item("main", item)
  346. detached_inv:set_stack(listname, 1, item) -- if it didn't fit, put it back in detached and hope the player comes back
  347. end
  348. local function return_all_items(player)
  349. local player_name = player:get_player_name()
  350. if item_invs[player_name] then
  351. local player_inv = minetest.get_inventory({type="player", name=player_name})
  352. local detached_inv = minetest.get_inventory({type="detached", name="personal_log_"..player_name})
  353. try_return(detached_inv, player_inv, "export_item")
  354. try_return(detached_inv, player_inv, "import_item")
  355. end
  356. end
  357. ------------------------------------------------------------------------
  358. -- Import/export formspec
  359. local import_mods = {}
  360. local export_generic_mods = {}
  361. local export_location_mods = {}
  362. if default_modpath or mcl_books_modpath then
  363. table.insert(import_mods, S("a book"))
  364. table.insert(export_generic_mods, S("a book"))
  365. table.insert(export_location_mods, S("a book"))
  366. end
  367. if ccompass_modpath then
  368. table.insert(import_mods, S("a calibrated compass"))
  369. if not ccompass_restrict_target then
  370. table.insert(export_location_mods, S("a compass"))
  371. end
  372. end
  373. if compassgps_modpath then
  374. table.insert(import_mods, S("a GPS compass map"))
  375. table.insert(export_location_mods, S("a GPS compass map"))
  376. end
  377. local function aggregate_localized_string(list)
  378. if #list == 1 then
  379. return S("@1", list[1])
  380. end
  381. if #list == 2 then
  382. return S("@1 or @2", list[1], list[2])
  383. end
  384. if #list == 3 then
  385. return S("@1, @2 or @3", list[1], list[2], list[3])
  386. end
  387. end
  388. local import_label = aggregate_localized_string(import_mods)
  389. local export_generic_label = aggregate_localized_string(export_generic_mods)
  390. local export_location_label = aggregate_localized_string(export_location_mods)
  391. local function item_formspec(player_name, category, listname, topic)
  392. local label
  393. if listname == "import_item" then
  394. label = S("Import an entry from @1", import_label)
  395. else
  396. if category == LOCATION_CATEGORY then
  397. label = S('Export "@1" to @2', topic, export_location_label)
  398. else
  399. label = S('Export "@1" to @2', topic, export_generic_label)
  400. end
  401. end
  402. local formspec = "size[8,6]"
  403. .. "label[0,1;" .. label .. "]"
  404. .. "list[detached:personal_log_"..player_name..";"..listname..";3.5,0;1,1;]"
  405. .. "list[current_player;main;0,1.5;8,4;]"
  406. .. "listring[]"
  407. .. "button[3.5,5.5;1,1;back;"..S("Back").."]"
  408. if mcl_formspec_itemslot then
  409. formspec = formspec .. mcl_formspec_itemslot(3.5, 0, 1, 1)
  410. .. mcl_formspec_itemslot(0,1.5,8,4)
  411. end
  412. return formspec
  413. end
  414. ---------------------------------------------------------------
  415. -- Main formspec
  416. local function make_personal_log_formspec(player)
  417. local player_name = player:get_player_name()
  418. local state = get_state(player_name)
  419. local category_index = state.category
  420. ensure_detached_inventory(player_name)
  421. local formspec = {
  422. "formspec_version[2]"
  423. .."size[10,10]"
  424. .."button_exit[9.0,0.25;0.5,0.5;close;X]"
  425. .."dropdown[1.5,0.25;2,0.5;category_select;"
  426. .. table.concat(categories, ",") .. ";"..category_index.."]"
  427. .. "label[0.5,0.5;"..S("Category:").."]"
  428. .. "label[4.5,0.5;"..S("Personal Log Entries").."]"
  429. }
  430. local entries = {}
  431. for i = 1, state.entry_counts[category_index] do
  432. table.insert(entries, modstore:get_string(player_name .. "_category_" .. category_index .. "_entry_" .. i .. "_content"))
  433. end
  434. local entry = ""
  435. local entry_selected = state.entry_selected[category_index]
  436. if entry_selected > 0 then
  437. entry = entries[entry_selected]
  438. end
  439. local topics = {}
  440. for i = 1, state.entry_counts[category_index] do
  441. table.insert(topics, modstore:get_string(player_name .. "_category_" .. category_index .. "_entry_" .. i .. "_topic"))
  442. end
  443. local topic = ""
  444. if entry_selected > 0 then
  445. topic = topics[entry_selected]
  446. end
  447. formspec[#formspec+1] = "tablecolumns[text;text]table[0.5,1.0;9,4.75;log_table;"
  448. for i, entry in ipairs(entries) do
  449. formspec[#formspec+1] = minetest.formspec_escape(truncate_string(topics[i], 30)) .. ","
  450. formspec[#formspec+1] = minetest.formspec_escape(truncate_string(first_line(entry), 30))
  451. formspec[#formspec+1] = ","
  452. end
  453. formspec[#formspec] = ";"..entry_selected.."]" -- don't use +1, this overwrites the last ","
  454. if category_index == GENERAL_CATEGORY then
  455. formspec[#formspec+1] = "textarea[0.5,6.0;9,0.5;topic_data;;" .. minetest.formspec_escape(topic) .. "]"
  456. formspec[#formspec+1] = "textarea[0.5,6.5;9,1.75;entry_data;;".. minetest.formspec_escape(entry) .."]"
  457. else
  458. formspec[#formspec+1] = "textarea[0.5,6.0;9,2.25;entry_data;;".. minetest.formspec_escape(entry) .."]"
  459. end
  460. formspec[#formspec+1] = "container[0.5,8.5]"
  461. .."button[0,0;2,0.5;save;"..S("Save").."]"
  462. .."button[2,0;2,0.5;create;"..S("New").."]"
  463. .."button[4.5,0;2,0.5;move_up;"..S("Move Up").."]"
  464. .."button[4.5,0.75;2,0.5;move_down;"..S("Move Down").."]"
  465. .."button[7,0;2,0.5;delete;"..S("Delete") .."]"
  466. if category_index == LOCATION_CATEGORY and minetest.check_player_privs(player_name, "teleport") then
  467. formspec[#formspec+1] = "button[7,0.75;2,0.5;teleport;"..S("Teleport") .."]"
  468. end
  469. if default_modpath or mcl_books_modpath or ccompass_modpath or compassgps_modpath then
  470. formspec[#formspec+1] = "button[0,0.75;2.0,0.5;copy_to;"..S("Export").."]"
  471. .."button[2,0.75;2.0,0.5;copy_from;"..S("Import").."]"
  472. end
  473. formspec[#formspec+1] = "container_end[]"
  474. return table.concat(formspec)
  475. end
  476. -------------------------------------------
  477. -- Input handlers
  478. minetest.register_on_player_receive_fields(function(player, formname, fields)
  479. if formname ~= "personal_log:item" then
  480. return
  481. end
  482. if fields.back then
  483. return_all_items(player)
  484. minetest.show_formspec(player:get_player_name(),"personal_log:root", make_personal_log_formspec(player))
  485. return
  486. end
  487. if fields.quit then
  488. return_all_items(player)
  489. return
  490. end
  491. end)
  492. local function on_player_receive_fields(player, fields, update_callback)
  493. local player_name = player:get_player_name()
  494. local state = get_state(player_name)
  495. local category = state.category
  496. local entry_selected = state.entry_selected[category]
  497. local valid_entry_selected = entry_selected > 0 and entry_selected <= state.entry_counts[category]
  498. if fields.log_table then
  499. local table_event = minetest.explode_table_event(fields.log_table)
  500. if table_event.type == "CHG" then
  501. state.entry_selected[category] = table_event.row
  502. save_state(player_name, state)
  503. update_callback(player)
  504. return
  505. end
  506. end
  507. if fields.save then
  508. if category == GENERAL_CATEGORY then
  509. save_entry(player_name, category, entry_selected, fields.entry_data, fields.topic_data)
  510. else
  511. save_entry(player_name, category, entry_selected, fields.entry_data)
  512. end
  513. update_callback(player)
  514. return
  515. end
  516. if fields.create then
  517. local content = ""
  518. local general_topic = ""
  519. if entry_selected == 0 then
  520. content = fields.entry_data
  521. general_topic = fields.topic_data
  522. end
  523. local entry_index = state.entry_counts[category] + 1
  524. state.entry_counts[category] = entry_index
  525. state.entry_selected[category] = entry_index
  526. if category == LOCATION_CATEGORY then
  527. local pos = vector.round(player:get_pos())
  528. save_entry(player_name, category, entry_index, content, minetest.pos_to_string(pos))
  529. elseif category == EVENT_CATEGORY then
  530. local current_date = os.date("%Y-%m-%d")
  531. save_entry(player_name, category, entry_index, content, current_date)
  532. else
  533. save_entry(player_name, category, entry_index, content, general_topic)
  534. end
  535. save_state(player_name, state)
  536. update_callback(player)
  537. return
  538. end
  539. if fields.move_up then
  540. swap_entry(player_name, state, -1)
  541. update_callback(player)
  542. return
  543. end
  544. if fields.move_down then
  545. swap_entry(player_name, state, 1)
  546. update_callback(player)
  547. return
  548. end
  549. if fields.delete then
  550. delete_entry(player_name, state)
  551. update_callback(player)
  552. return
  553. end
  554. if fields.teleport
  555. and category == LOCATION_CATEGORY
  556. and valid_entry_selected
  557. and minetest.check_player_privs(player_name, "teleport") then
  558. local pos_string = modstore:get_string(player_name .. "_category_" .. category .. "_entry_" .. entry_selected .. "_topic")
  559. local pos = minetest.string_to_pos(pos_string)
  560. if pos then
  561. player:set_pos(pos)
  562. end
  563. end
  564. if fields.copy_to then
  565. if valid_entry_selected then
  566. local topic = modstore:get_string(player_name .. "_category_" .. category .. "_entry_" .. entry_selected .. "_topic")
  567. if category ~= 3 then
  568. -- If it's a location or an event, add a little context to the title
  569. local content = modstore:get_string(player_name .. "_category_" .. category .. "_entry_" .. entry_selected .. "_content")
  570. topic = S("@1: @2", topic, truncate_string(first_line(content), short_title_size))
  571. end
  572. minetest.show_formspec(player_name, "personal_log:item",
  573. item_formspec(player_name, category, "export_item", topic))
  574. end
  575. return
  576. end
  577. if fields.copy_from then
  578. minetest.show_formspec(player_name, "personal_log:item",
  579. item_formspec(player_name, category, "import_item"))
  580. return
  581. end
  582. -- Do this one last, since it should always be true and we don't want to do it if we don't have to
  583. if fields.category_select then
  584. for i, category in ipairs(categories) do
  585. if category == fields.category_select then
  586. if state.category ~= i then
  587. state.category = i
  588. save_state(player_name, state)
  589. update_callback(player)
  590. return
  591. else
  592. break
  593. end
  594. end
  595. end
  596. end
  597. end
  598. minetest.register_on_player_receive_fields(function(player, formname, fields)
  599. if formname ~= "personal_log:root" then
  600. return
  601. end
  602. on_player_receive_fields(player, fields, function(player)
  603. minetest.show_formspec(player:get_player_name(), "personal_log:root", make_personal_log_formspec(player))
  604. end)
  605. end)
  606. -------------------------------------------------------------------------------------------------------
  607. -- Inventory interface
  608. if minetest.settings:get_bool("personal_log_inventory_button", true) then
  609. -- Unified Inventory
  610. if unified_inventory_modpath then
  611. unified_inventory.register_button("personal_log", {
  612. type = "image",
  613. image = "personal_log_open_book.png",
  614. tooltip = S("Your personal log for keeping track of what happens where"),
  615. action = function(player)
  616. local name = player:get_player_name()
  617. minetest.show_formspec(name,"personal_log:root", make_personal_log_formspec(player))
  618. end,
  619. })
  620. end
  621. -- sfinv_buttons
  622. if sfinv_buttons_modpath then
  623. sfinv_buttons.register_button("personal_log", {
  624. image = "personal_log_open_book.png",
  625. tooltip = S("Your personal log for keeping track of what happens where"),
  626. title = S("Log"),
  627. action = function(player)
  628. local name = player:get_player_name()
  629. minetest.show_formspec(name,"personal_log:root", make_personal_log_formspec(player))
  630. end,
  631. })
  632. elseif sfinv_modpath then
  633. sfinv.register_page("personal_log:personal_log", {
  634. title = S("Log"),
  635. get = function(_, player, context)
  636. local name = player:get_player_name()
  637. minetest.show_formspec(name,"personal_log:root", make_personal_log_formspec(player))
  638. return sfinv.make_formspec(player, context, "button[2.5,3;3,1;open_personal_log;"..S("Open personal log").."]", false)
  639. end,
  640. on_player_receive_fields = function(_, player, _, fields)
  641. local name = player:get_player_name()
  642. if fields.open_personal_log then
  643. minetest.show_formspec(name,"personal_log:root", make_personal_log_formspec(player))
  644. return true
  645. end
  646. end
  647. })
  648. end
  649. end
  650. -----------------------------------------------------------------------------------------------------
  651. -- Craftable item
  652. local craftable_setting = minetest.settings:get_bool("personal_log_craftable_item", false)
  653. if craftable_setting or not (unified_inventory_modpath or sfinv_modpath or sfinv_buttons_modpath) then
  654. minetest.register_craftitem("personal_log:book", {
  655. description = S("Personal Log"),
  656. inventory_image = "personal_log_open_book.png",
  657. groups = {book = 1, flammable = 3},
  658. on_use = function(itemstack, user, pointed_thing)
  659. local name = user:get_player_name()
  660. minetest.show_formspec(name,"personal_log:root", make_personal_log_formspec(user))
  661. end,
  662. })
  663. minetest.register_craft({
  664. output = "personal_log:book",
  665. recipe = {{book_unwritten, book_unwritten}}
  666. })
  667. end
  668. --------------------------------------------------------------------------------------------------------
  669. -- Chat command
  670. local chat_command = minetest.settings:get_bool("personal_log_chat_command", false)
  671. local chat_command_priv = minetest.settings:get_bool("personal_log_chat_command_priviledge", false)
  672. if chat_command then
  673. local privs = nil
  674. if chat_command_priv then
  675. minetest.register_privilege("personal_log", {
  676. description =S("Allows the player to access a personal log via chat command"),
  677. give_to_singleplayer = false,
  678. give_to_admin = true,
  679. })
  680. privs = {personal_log=true}
  681. end
  682. minetest.register_chatcommand("log", {
  683. description = S("Open your personal log"),
  684. privs = privs,
  685. func = function(name, param)
  686. local user = minetest.get_player_by_name(name)
  687. minetest.show_formspec(name,"personal_log:root", make_personal_log_formspec(user))
  688. end,
  689. })
  690. end
  691. ---------------------------------------------------------------------------------------------------------
  692. -- API
  693. local add_entry_for_player = function(player_name, category, content, topic_content)
  694. local state = get_state(player_name)
  695. local entry_index = state.entry_counts[category] + 1
  696. state.entry_counts[category] = entry_index
  697. state.entry_selected[category] = entry_index
  698. save_entry(player_name, category, entry_index, content, topic_content)
  699. save_state(player_name, state)
  700. end
  701. personal_log.add_location_entry = function(player_name, content, pos)
  702. if pos == nil then
  703. local player = minetest.get_player_by_name(player_name)
  704. pos = player:get_pos()
  705. end
  706. add_entry_for_player(player_name, LOCATION_CATEGORY, content, minetest.pos_to_string(pos))
  707. end
  708. personal_log.add_event_entry = function(player_name, content, event_date)
  709. if event_date == nil then
  710. event_date = os.date("%Y-%m-%d")
  711. end
  712. add_entry_for_player(player_name, EVENT_CATEGORY, content, event_date)
  713. end
  714. personal_log.add_general_entry = function(player_name, content, general_topic)
  715. add_entry_for_player(player_name, GENERAL_CATEGORY, content, general_topic)
  716. end