Change license to MIT License
[minetest_doc.git] / init.lua
blob3df8b64a80f7d63eb2e5d7220888ca634750f38b
1 doc = {}
3 doc.VERSION = {}
4 doc.VERSION.MAJOR = 0
5 doc.VERSION.MINOR = 5
6 doc.VERSION.PATCH = 0
7 doc.VERSION.STRING = doc.VERSION.MAJOR.."."..doc.VERSION.MINOR.."."..doc.VERSION.PATCH
10 doc.data = {}
11 doc.data.categories = {}
12 doc.data.category_order = {}
13 doc.data.players = {}
15 -- Space for additional APIs
16 doc.sub = {}
18 --[[ Core API functions ]]
20 -- Add a new category
21 function doc.new_category(id, def)
22 if doc.data.categories[id] == nil and id ~= nil then
23 doc.data.categories[id] = {}
24 doc.data.categories[id].entries = {}
25 doc.data.categories[id].entry_count = 0
26 doc.data.categories[id].hidden_count = 0
27 doc.data.categories[id].def = def
28 doc.data.categories[id].entry_aliases = {}
29 table.insert(doc.data.category_order, id)
30 return true
31 else
32 return false
33 end
34 end
36 -- Add a new entry
37 function doc.new_entry(category_id, entry_id, def)
38 local cat = doc.data.categories[category_id]
39 if cat ~= nil then
40 local hidden = def.hidden or (def.hidden == nil and cat.def.hide_entries_by_default)
41 if hidden then
42 cat.hidden_count = cat.hidden_count + 1
43 def.hidden = hidden
44 end
45 cat.entry_count = doc.data.categories[category_id].entry_count + 1
46 cat.entries[entry_id] = def
47 return true
48 else
49 return false
50 end
51 end
53 -- Marks a particular entry as viewed by a certain player, which also
54 -- automatically reveals it
55 function doc.mark_entry_as_viewed(playername, category_id, entry_id)
56 local entry, entry_id = doc.get_entry(category_id, entry_id)
57 if doc.data.players[playername].stored_data.viewed[category_id] == nil then
58 doc.data.players[playername].stored_data.viewed[category_id] = {}
59 doc.data.players[playername].stored_data.viewed_count[category_id] = 0
60 end
61 if doc.entry_exists(category_id, entry_id) and doc.data.players[playername].stored_data.viewed[category_id][entry_id] ~= true then
62 doc.data.players[playername].stored_data.viewed[category_id][entry_id] = true
63 doc.data.players[playername].stored_data.viewed_count[category_id] = doc.data.players[playername].stored_data.viewed_count[category_id] + 1
64 -- Needed because viewed entries get a different color
65 doc.data.players[playername].entry_textlist_needs_updating = true
66 end
67 doc.mark_entry_as_revealed(playername, category_id, entry_id)
68 end
70 -- Marks a particular entry as revealed/unhidden by a certain player
71 function doc.mark_entry_as_revealed(playername, category_id, entry_id)
72 local entry, entry_id = doc.get_entry(category_id, entry_id)
73 if doc.data.players[playername].stored_data.revealed[category_id] == nil then
74 doc.data.players[playername].stored_data.revealed[category_id] = {}
75 doc.data.players[playername].stored_data.revealed_count[category_id] = doc.get_entry_count(category_id) - doc.data.categories[category_id].hidden_count
76 end
77 if doc.entry_exists(category_id, entry_id) and entry.hidden and doc.data.players[playername].stored_data.revealed[category_id][entry_id] ~= true then
78 doc.data.players[playername].stored_data.revealed[category_id][entry_id] = true
79 doc.data.players[playername].stored_data.revealed_count[category_id] = doc.data.players[playername].stored_data.revealed_count[category_id] + 1
80 -- Needed because a new entry is added to the list of visible entries
81 doc.data.players[playername].entry_textlist_needs_updating = true
82 if minetest.get_modpath("central_message") ~= nil then
83 local cat = doc.data.categories[category_id]
84 cmsg.push_message_player(minetest.get_player_by_name(playername), string.format("New help entry unlocked: %s > %s", cat.def.name, entry.name))
85 end
86 -- To avoid sound spamming, don't play sound more than once per second
87 local last_sound = doc.data.players[playername].last_reveal_sound
88 if last_sound == nil or os.difftime(os.time(), last_sound) >= 1 then
89 -- Play notification sound
90 minetest.sound_play({ name = "doc_reveal", gain = 0.2 }, { to_player = playername })
91 doc.data.players[playername].last_reveal_sound = os.time()
92 end
93 end
94 end
96 -- Returns true if the specified entry has been viewed by the player
97 function doc.entry_viewed(playername, category_id, entry_id)
98 local entry, entry_id = doc.get_entry(category_id, entry_id)
99 if doc.data.players[playername].stored_data.viewed[category_id] == nil then
100 return false
101 else
102 return doc.data.players[playername].stored_data.viewed[category_id][entry_id] == true
106 -- Returns true if the specified entry is hidden from the player
107 function doc.entry_revealed(playername, category_id, entry_id)
108 local entry, entry_id = doc.get_entry(category_id, entry_id)
109 local hidden = doc.data.categories[category_id].entries[entry_id].hidden
110 if doc.data.players[playername].stored_data.revealed[category_id] == nil then
111 return not hidden
112 else
113 if hidden then
114 return doc.data.players[playername].stored_data.revealed[category_id][entry_id] == true
115 else
116 return true
121 -- Returns category definition
122 function doc.get_category_definition(category_id)
123 if doc.data.categories[category_id] == nil then
124 return nil
126 return doc.data.categories[category_id].def
129 -- Returns entry definition
130 function doc.get_entry_definition(category_id, entry_id)
131 if not doc.entry_exists(category_id, entry_id) then
132 return nil
134 local entry, _ = doc.get_entry(category_id, entry_id)
135 return entry
138 -- Opens the main documentation formspec for the player
139 function doc.show_doc(playername)
140 if doc.get_category_count() <= 0 then
141 minetest.show_formspec(playername, "doc:error_no_categories", doc.formspec_error_no_categories())
142 return
144 local formspec = doc.formspec_core()..doc.formspec_main()
145 minetest.show_formspec(playername, "doc:main", formspec)
148 -- Opens the documentation formspec for the player at the specified category
149 function doc.show_category(playername, category_id)
150 if doc.get_category_count() <= 0 then
151 minetest.show_formspec(playername, "doc:error_no_categories", doc.formspec_error_no_categories())
152 return
154 doc.data.players[playername].catsel = nil
155 doc.data.players[playername].category = category_id
156 doc.data.players[playername].entry = nil
157 local formspec = doc.formspec_core(2)..doc.formspec_category(category_id, playername)
158 minetest.show_formspec(playername, "doc:category", formspec)
161 -- Opens the documentation formspec for the player showing the specified entry in a category
162 function doc.show_entry(playername, category_id, entry_id, ignore_hidden)
163 if doc.get_category_count() <= 0 then
164 minetest.show_formspec(playername, "doc:error_no_categories", doc.formspec_error_no_categories())
165 return
167 local entry, entry_id = doc.get_entry(category_id, entry_id)
168 if ignore_hidden or doc.entry_revealed(playername, category_id, entry_id) then
169 local playerdata = doc.data.players[playername]
170 playerdata.category = category_id
171 playerdata.entry = entry_id
173 doc.mark_entry_as_viewed(playername, category_id, entry_id)
174 playerdata.entry_textlist_needs_updating = true
175 doc.generate_entry_list(category_id, playername)
177 playerdata.catsel = playerdata.catsel_list[entry_id]
179 local formspec = doc.formspec_core(3)..doc.formspec_entry(category_id, entry_id)
180 minetest.show_formspec(playername, "doc:entry", formspec)
181 else
182 minetest.show_formspec(playername, "doc:error_hidden", doc.formspec_error_hidden(category_id, entry_id))
186 -- Returns true if and only if:
187 -- * The specified category exists
188 -- * This category contains the specified entry
189 function doc.entry_exists(category_id, entry_id)
190 if doc.data.categories[category_id] ~= nil then
191 if doc.data.categories[category_id].entries[entry_id] ~= nil then
192 -- Entry exists
193 return true
194 else
195 -- Entry of this ID does not exist, so we check if there's an alis for it
196 return doc.data.categories[category_id].entry_aliases[entry_id] ~= nil
198 else
199 return false
203 -- Adds aliases for an entry. Attempting to open an entry by an alias name
204 -- results in opening the entry of the original name.
205 -- Aliases are true within one category only.
206 function doc.add_entry_aliases(category_id, entry_id, aliases)
207 for a=1,#aliases do
208 doc.data.categories[category_id].entry_aliases[aliases[a]] = entry_id
212 -- Same as above, but only adds one alias
213 function doc.add_entry_alias(category_id, entry_id, alias)
214 doc.data.categories[category_id].entry_aliases[alias] = entry_id
217 -- Returns number of categories
218 function doc.get_category_count()
219 return #doc.data.category_order
222 -- Returns number of entries in category
223 function doc.get_entry_count(category_id)
224 return doc.data.categories[category_id].entry_count
227 -- Returns how many entries have been viewed by the player
228 function doc.get_viewed_count(playername, category_id)
229 local playerdata = doc.data.players[playername]
230 if playerdata == nil then
231 return nil
233 local count = playerdata.stored_data.viewed_count[category_id]
234 if count == nil then
235 playerdata.stored_data.viewed[category_id] = {}
236 count = 0
237 playerdata.stored_data.viewed_count[category_id] = count
238 return count
239 else
240 return count
244 -- Returns how many entries have been revealed by the player
245 function doc.get_revealed_count(playername, category_id)
246 local playerdata = doc.data.players[playername]
247 if playerdata == nil then
248 return nil
250 local count = playerdata.stored_data.revealed_count[category_id]
251 if count == nil then
252 playerdata.stored_data.revealed[category_id] = {}
253 count = doc.get_entry_count(category_id) - doc.data.categories[category_id].hidden_count
254 playerdata.stored_data.revealed_count[category_id] = count
255 return count
256 else
257 return count
261 -- Returns how many entries are hidden from the player
262 function doc.get_hidden_count(playername, category_id)
263 local playerdata = doc.data.players[playername]
264 if playerdata == nil then
265 return nil
267 local total = doc.get_entry_count(category_id)
268 local rcount = playerdata.stored_data.revealed_count[category_id]
269 if rcount == nil then
270 return total
271 else
272 return total - rcount
276 -- Template function templates, to be used for build_formspec in doc.new_category
277 doc.entry_builders = {}
279 -- Inserts line breaks into a single paragraph and collapses all whitespace (including newlines)
280 -- into spaces
281 local linebreaker_single = function(text)
282 local linelength = 80
283 local remain = linelength
284 local res = {}
285 local line = {}
286 local split = function(s)
287 local res = {}
288 for w in string.gmatch(s, "%S+") do
289 res[#res+1] = w
291 return res
294 for _, word in ipairs(split(text)) do
295 if string.len(word) + 1 > remain then
296 table.insert(res, table.concat(line, " "))
297 line = { word }
298 remain = linelength - string.len(word)
299 else
300 table.insert(line, word)
301 remain = remain - (string.len(word) + 1)
305 table.insert(res, table.concat(line, " "))
306 return table.concat(res, "\n")
309 -- Inserts automatic line breaks into an entire text and preserves existing newlines
310 local linebreaker = function(text)
311 local out = ""
312 for s in string.gmatch(text, "([^\n]*)\n") do
313 s = linebreaker_single(s)
314 out = out .. s
315 out = out .. "\n"
317 -- Remove last newline
318 if string.len(out) >= 1 then
319 out = string.sub(out, 1, string.len(out) - 1)
321 return out
324 -- Inserts text suitable for a textlist (including automatic word-wrap)
325 local text_for_textlist = function(text)
326 text = linebreaker(text)
327 text = minetest.formspec_escape(text)
328 text = string.gsub(text, "\n", ",")
329 return text
332 -- Freeform text
333 doc.entry_builders.text = function(data)
334 -- TODO: Wait for Minetest to provide a native widget for scrollable read-only text with automatic line breaks.
335 -- Currently, all of this had to be hacked into this script manually by using/abusing the table widget
336 return "tablecolumns[text]"..
337 "tableoptions[background=#00000000;highlight=#00000000;border=false]"..
338 "table[0,0.5;11.8,8;text;"..text_for_textlist(data).."]"
341 -- Direct formspec
342 doc.entry_builders.formspec = function(data)
343 return data
346 --[[ Internal stuff ]]
348 -- Loading and saving player data
350 local filepath = minetest.get_worldpath().."/doc.mt"
351 local file = io.open(filepath, "r")
352 if file then
353 minetest.log("action", "[doc] doc.mt opened.")
354 local string = file:read()
355 io.close(file)
356 if(string ~= nil) then
357 local savetable = minetest.deserialize(string)
358 for name, players_stored_data in pairs(savetable.players_stored_data) do
359 doc.data.players[name] = {}
360 doc.data.players[name].stored_data = players_stored_data
362 minetest.debug("[doc] doc.mt successfully read.")
367 function doc.save_to_file()
368 local savetable = {}
369 savetable.players_stored_data = {}
370 for name, playerdata in pairs(doc.data.players) do
371 savetable.players_stored_data[name] = playerdata.stored_data
374 local savestring = minetest.serialize(savetable)
376 local filepath = minetest.get_worldpath().."/doc.mt"
377 local file = io.open(filepath, "w")
378 if file then
379 file:write(savestring)
380 io.close(file)
381 minetest.log("action", "[doc] Wrote player data into "..filepath..".")
382 else
383 minetest.log("error", "[doc] Failed to write player data into "..filepath..".")
387 minetest.register_on_leaveplayer(function(player)
388 doc.save_to_file()
389 end)
391 minetest.register_on_shutdown(function()
392 minetest.log("action", "[doc] Server shuts down. Rescuing player data into doc.mt.")
393 doc.save_to_file()
394 end)
396 --[[ Functions for internal use ]]
398 function doc.formspec_core(tab)
399 if tab == nil then tab = 1 else tab = tostring(tab) end
400 return "size[12,9]tabheader[0,0;doc_header;Category list,Entry list,Entry;"..tab..";true;false]"
403 function doc.formspec_main()
404 local formstring = "label[0,0;This is the Documentation System, Version "..doc.VERSION.STRING..".\n"
405 if doc.get_category_count() >= 1 then
406 formstring = formstring .. "Please select a category you wish to learn more about:]"
407 local y = 1
408 for c=1,#doc.data.category_order do
409 local id = doc.data.category_order[c]
410 local data = doc.data.categories[id]
411 -- Category buton
412 local button = "button[0,"..y..";3,1;doc_button_category_"..id..";"..minetest.formspec_escape(data.def.name).."]"
413 local tooltip = ""
414 -- Optional description
415 if data.def.description ~= nil then
416 tooltip = "tooltip[doc_button_category_"..id..";"..minetest.formspec_escape(data.def.description).."]"
418 formstring = formstring .. button .. tooltip
419 y = y + 1
422 return formstring
425 function doc.formspec_error_no_categories()
426 local formstring = "size[8,6]textarea[0.25,0;8,6;;"
427 formstring = formstring .. minetest.formspec_escape(
428 [=[This is the Documentation System, Version ]=]..doc.VERSION.STRING..[=[.
430 ERROR: No help available.
432 No categories have been registered, but the Documentation System is useless without them.
433 The Documentation System does not come with help contents on its own, it needs additional mods to add help content.
434 Please make sure such mods are enabled on for this world, and try again.]=])
435 formstring = formstring .. ";]button_exit[3,5;2,1;okay;OK]"
436 return formstring
439 function doc.formspec_error_hidden(category_id, entry_id)
440 local formstring = "size[8,6]textarea[0.25,0;8,6;;"
441 formstring = formstring .. minetest.formspec_escape(
442 string.format([=[This is the Documentation System, Version %s.
444 ERROR: Access denied.
446 Sorry, access to the requested entry has been denied; this entry is secret. You may unlock access by more playing. Figure out on your own how to unlock this entry.]=],
447 doc.VERSION.STRING, doc.data.categories[category_id].def.name, doc.data.categories[category_id].entries[entry_id].name))
448 formstring = formstring .. ";]button_exit[3,5;2,1;okay;OK]"
449 return formstring
452 -- Returns the entry definition and true entry ID of an entry, taking aliases into account
453 function doc.get_entry(category_id, entry_id)
454 local category = doc.data.categories[category_id]
455 local entry = category.entries[entry_id]
456 local resolved_entry_id = entry_id
457 if entry == nil then
458 resolved_entry_id = doc.data.categories[category_id].entry_aliases[entry_id]
459 if resolved_entry_id ~= nil then
460 entry = category.entries[resolved_entry_id]
463 return entry, resolved_entry_id
466 function doc.generate_entry_list(cid, playername)
467 local formstring
468 if doc.data.players[playername].entry_textlist == nil
469 or doc.data.players[playername].catsel_list == nil
470 or doc.data.players[playername].category ~= cid
471 or doc.data.players[playername].entry_textlist_needs_updating == true then
472 local entry_textlist = "textlist[0,1;11,7;doc_catlist;"
473 local counter = 0
474 doc.data.players[playername].entry_ids = {}
475 local entries = doc.get_sorted_entry_names(cid)
476 doc.data.players[playername].catsel_list = {}
477 for i=1, #entries do
478 local eid = entries[i]
479 local edata = doc.data.categories[cid].entries[eid]
480 if doc.entry_revealed(playername, cid, eid) then
481 table.insert(doc.data.players[playername].entry_ids, eid)
482 doc.data.players[playername].catsel_list[eid] = counter + 1
483 -- Colorize entries based on viewed status
484 -- Not viewed: Cyan
485 local viewedprefix = "#00FFFF"
486 if doc.entry_viewed(playername, cid, eid) then
487 -- Viewed: White
488 viewedprefix = "#FFFFFF"
490 entry_textlist = entry_textlist .. viewedprefix .. minetest.formspec_escape(edata.name) .. ","
491 counter = counter + 1
494 if counter >= 1 then
495 entry_textlist = string.sub(entry_textlist, 1, #entry_textlist-1)
497 local catsel = doc.data.players[playername].catsel
498 if catsel then
499 entry_textlist = entry_textlist .. ";"..catsel
501 entry_textlist = entry_textlist .. "]"
502 doc.data.players[playername].entry_textlist = entry_textlist
503 formstring = entry_textlist
504 doc.data.players[playername].entry_textlist_needs_updating = false
505 else
506 formstring = doc.data.players[playername].entry_textlist
508 return formstring
511 function doc.get_sorted_entry_names(cid)
512 local sort_table = {}
513 local entry_table = {}
514 local cat = doc.data.categories[cid]
515 local used_eids = {}
516 -- Helper function to extract the entry ID out of the output table
517 local extract = function(entry_table)
518 local eids = {}
519 for k,v in pairs(entry_table) do
520 local eid = v.eid
521 table.insert(eids, eid)
523 return eids
525 -- Predefined sorting
526 if cat.def.sorting == "custom" then
527 for i=1,#cat.def.sorting_data do
528 local new_entry = table.copy(cat.entries[cat.def.sorting_data[i]])
529 new_entry.eid = cat.def.sorting_data[i]
530 table.insert(entry_table, new_entry)
531 used_eids[cat.def.sorting_data[i]] = true
534 for eid,entry in pairs(cat.entries) do
535 local new_entry = table.copy(entry)
536 new_entry.eid = eid
537 if not used_eids[eid] then
538 table.insert(entry_table, new_entry)
540 table.insert(sort_table, entry.name)
542 if cat.def.sorting == "custom" then
543 return extract(entry_table)
544 else
545 table.sort(sort_table)
547 local reverse_sort_table = table.copy(sort_table)
548 for i=1, #sort_table do
549 reverse_sort_table[sort_table[i]] = i
551 local comp
552 if cat.def.sorting ~= "nosort" then
553 -- Sorting by user function
554 if cat.def.sorting == "function" then
555 comp = cat.def.sorting_data
556 -- Alphabetic sorting
557 elseif cat.def.sorting == "abc" or cat.def.sorting == nil then
558 comp = function(e1, e2)
559 if reverse_sort_table[e1.name] < reverse_sort_table[e2.name] then return true else return false end
562 table.sort(entry_table, comp)
565 return extract(entry_table)
568 function doc.formspec_category(id, playername)
569 local formstring
570 if id == nil then
571 formstring = "label[0,0.5;You haven't chosen a category yet. Please choose one in the category list first.]"
572 formstring = formstring .. "button[0,1.5;3,1;doc_button_goto_main;Go to category list]"
573 else
574 formstring = "label[0,0;Help > "..doc.data.categories[id].def.name.."]"
575 local total = doc.get_entry_count(id)
576 if total >= 1 then
577 local revealed = doc.get_revealed_count(playername, id)
578 if revealed == 0 then
579 formstring = formstring .. "label[0,0.5;Currently all entries in this category are hidden from you.\nUnlock new entries by proceeding in the game.]"
580 formstring = formstring .. "button[0,1.5;3,1;doc_button_goto_main;Go to category list]"
581 else
582 formstring = formstring .. "label[0,0.5;This category has the following entries:]"
583 formstring = formstring .. doc.generate_entry_list(id, playername)
584 formstring = formstring .. "button[0,8;3,1;doc_button_goto_entry;Show entry]"
585 formstring = formstring .. "label[8,8;Number of entries: "..total.."\n"
586 local viewed = doc.get_viewed_count(playername, id)
587 local hidden = total - revealed
588 local new = total - viewed - hidden
589 -- TODO/FIXME: Check if number of hidden/viewed entries is always correct
590 if viewed < total then
591 formstring = formstring .. "New entries: "..new
592 if hidden > 0 then
593 formstring = formstring .. "\n"
594 formstring = formstring .. "Hidden entries: "..hidden.."]"
595 else
596 formstring = formstring .. "]"
598 else
599 formstring = formstring .. "All entries read.]"
602 else
603 formstring = formstring .. "label[0,0.5;This category is empty.]"
604 formstring = formstring .. "button[0,1.5;3,1;doc_button_goto_main;Go to category list]"
607 return formstring
610 function doc.formspec_entry_navigation(category_id, entry_id)
611 if doc.get_entry_count(category_id) < 1 then
612 return ""
614 local formstring = ""
615 formstring = formstring .. "button[10,8.5;1,1;doc_button_goto_prev;<]"
616 formstring = formstring .. "button[11,8.5;1,1;doc_button_goto_next;>]"
617 formstring = formstring .. "tooltip[doc_button_goto_prev;Show previous entry]"
618 formstring = formstring .. "tooltip[doc_button_goto_next;Show next entry]"
619 return formstring
622 function doc.formspec_entry(category_id, entry_id)
623 local formstring
624 if category_id == nil then
625 formstring = "label[0,0;You haven't chosen a category yet. Please choose one in the category list first.]"
626 formstring = formstring .. "button[0,1;3,1;doc_button_goto_main;Go to category list]"
627 elseif entry_id == nil then
628 formstring = "label[0,0;Help > "..doc.data.categories[category_id].def.name.." > (No Entry)]"
629 if doc.get_entry_count(category_id) >= 1 then
630 formstring = formstring .. "label[0,0.5;You haven't chosen an entry yet. Please choose one in the entry list first.]"
631 formstring = formstring .. "button[0,1.5;3,1;doc_button_goto_category;Go to entry list]"
632 else
633 formstring = formstring .. "label[0,0.5;This category does not have any entries.]"
634 formstring = formstring .. "button[0,1.5;3,1;doc_button_goto_main;Go to category list]"
636 else
638 local category = doc.data.categories[category_id]
639 local entry = doc.get_entry(category_id, entry_id)
641 formstring = "label[0,0;Help > "..category.def.name.." > "..entry.name.."]"
642 formstring = formstring .. category.def.build_formspec(entry.data)
643 formstring = formstring .. doc.formspec_entry_navigation(category_id, entry_id)
645 return formstring
648 function doc.process_form(player,formname,fields)
649 local playername = player:get_player_name()
650 --[[ process clicks on the tab header ]]
651 if(formname == "doc:main" or formname == "doc:category" or formname == "doc:entry") then
652 if fields.doc_header ~= nil then
653 local tab = tonumber(fields.doc_header)
654 local formspec, subformname, contents
655 local cid, eid
656 cid = doc.data.players[playername].category
657 eid = doc.data.players[playername].entry
658 if(tab==1) then
659 contents = doc.formspec_main()
660 subformname = "main"
661 elseif(tab==2) then
662 contents = doc.formspec_category(cid, playername)
663 subformname = "category"
664 elseif(tab==3) then
665 contents = doc.formspec_entry(cid, eid)
666 if cid ~= nil and eid ~= nil then
667 doc.mark_entry_as_viewed(playername, cid, eid)
669 subformname = "entry"
671 formspec = doc.formspec_core(tab)..contents
672 minetest.show_formspec(playername, "doc:" .. subformname, formspec)
673 return
676 if(formname == "doc:main") then
677 for id,_ in pairs(doc.data.categories) do
678 if fields["doc_button_category_"..id] then
679 local formspec = doc.formspec_core(2)..doc.formspec_category(id, playername)
680 doc.data.players[playername].catsel = nil
681 doc.data.players[playername].category = id
682 doc.data.players[playername].entry = nil
683 minetest.show_formspec(playername, "doc:category", formspec)
684 break
687 elseif(formname == "doc:category") then
688 if fields["doc_button_goto_entry"] then
689 local cid = doc.data.players[playername].category
690 if cid ~= nil then
691 local eid = nil
692 local eids, catsel = doc.data.players[playername].entry_ids, doc.data.players[playername].catsel
693 if eids ~= nil and catsel ~= nil then
694 eid = eids[catsel]
696 local formspec = doc.formspec_core(3)..doc.formspec_entry(cid, eid)
697 minetest.show_formspec(playername, "doc:entry", formspec)
698 doc.mark_entry_as_viewed(playername, cid, eid)
701 if fields["doc_button_goto_main"] then
702 local formspec = doc.formspec_core(1)..doc.formspec_main()
703 minetest.show_formspec(playername, "doc:main", formspec)
705 if fields["doc_catlist"] then
706 local event = minetest.explode_textlist_event(fields["doc_catlist"])
707 if event.type == "CHG" then
708 doc.data.players[playername].catsel = event.index
709 doc.data.players[playername].entry = doc.data.players[playername].entry_ids[event.index]
710 elseif event.type == "DCL" then
711 local cid = doc.data.players[playername].category
712 local eid = nil
713 local eids, catsel = doc.data.players[playername].entry_ids, event.index
714 if eids ~= nil and catsel ~= nil then
715 eid = eids[catsel]
717 local formspec = doc.formspec_core(3)..doc.formspec_entry(cid, eid)
718 minetest.show_formspec(playername, "doc:entry", formspec)
719 doc.mark_entry_as_viewed(playername, cid, eid)
722 elseif(formname == "doc:entry") then
723 if fields["doc_button_goto_main"] then
724 local formspec = doc.formspec_core(1)..doc.formspec_main()
725 minetest.show_formspec(playername, "doc:main", formspec)
726 elseif fields["doc_button_goto_category"] then
727 local formspec = doc.formspec_core(2)..doc.formspec_category(doc.data.players[playername].category, playername)
728 minetest.show_formspec(playername, "doc:category", formspec)
729 elseif fields["doc_button_goto_next"] then
730 if doc.data.players[playername].catsel == nil then return end -- emergency exit
731 local eids = doc.data.players[playername].entry_ids
732 local cid = doc.data.players[playername].category
733 local new_catsel= doc.data.players[playername].catsel + 1
734 local new_eid = eids[new_catsel]
735 if #eids > 1 and new_catsel <= #eids then
736 local formspec = doc.formspec_core(3)..doc.formspec_entry(cid, new_eid)
737 minetest.show_formspec(playername, "doc:entry", formspec)
738 doc.mark_entry_as_viewed(playername, cid, new_eid)
739 doc.data.players[playername].catsel = new_catsel
740 doc.data.players[playername].entry = new_eid
742 elseif fields["doc_button_goto_prev"] then
743 if doc.data.players[playername].catsel == nil then return end -- emergency exit
744 local eids = doc.data.players[playername].entry_ids
745 local cid = doc.data.players[playername].category
746 local new_catsel= doc.data.players[playername].catsel - 1
747 local new_eid = eids[new_catsel]
748 if #eids > 1 and new_catsel >= 1 then
749 local formspec = doc.formspec_core(3)..doc.formspec_entry(cid, new_eid)
750 minetest.show_formspec(playername, "doc:entry", formspec)
751 doc.mark_entry_as_viewed(playername, cid, new_eid)
752 doc.data.players[playername].catsel = new_catsel
753 doc.data.players[playername].entry = new_eid
759 minetest.register_on_player_receive_fields(doc.process_form)
761 minetest.register_chatcommand("doc", {
762 params = "",
763 description = "Open documentation system.",
764 privs = {},
765 func = function(playername, param)
766 doc.show_doc(playername)
767 end,
771 minetest.register_on_joinplayer(function(player)
772 local playername = player:get_player_name()
773 local playerdata = doc.data.players[playername]
774 if playerdata == nil then
775 -- Initialize player data
776 doc.data.players[playername] = {}
777 playerdata = doc.data.players[playername]
778 -- Table for persistant data
779 playerdata.stored_data = {}
780 -- Contains viewed entries
781 playerdata.stored_data.viewed = {}
782 -- Count viewed entries
783 playerdata.stored_data.viewed_count = {}
784 -- Contains revealed/unhidden entries
785 playerdata.stored_data.revealed = {}
786 -- Count revealed entries
787 playerdata.stored_data.revealed_count = {}
788 else
789 -- Completely rebuild viewed and revealed counts from scratch
790 for cid, cat in pairs(doc.data.categories) do
791 if playerdata.stored_data.viewed[cid] == nil then
792 playerdata.stored_data.viewed[cid] = {}
794 if playerdata.stored_data.revealed[cid] == nil then
795 playerdata.stored_data.revealed[cid] = {}
797 local vc = 0
798 local rc = doc.get_entry_count(cid) - doc.data.categories[cid].hidden_count
799 for eid, entry in pairs(cat.entries) do
800 if playerdata.stored_data.viewed[cid][eid] then
801 vc = vc + 1
802 playerdata.stored_data.revealed[cid][eid] = true
804 if playerdata.stored_data.revealed[cid][eid] and entry.hidden then
805 rc = rc + 1
808 playerdata.stored_data.viewed_count[cid] = vc
809 playerdata.stored_data.revealed_count[cid] = rc
812 end)
814 minetest.register_on_leaveplayer(function(player)
815 doc.data.players[player:get_player_name()] = nil
816 end)
818 ---[[ Add buttons for inventory mods ]]
819 -- Unified Inventory
820 if minetest.get_modpath("unified_inventory") ~= nil then
821 unified_inventory.register_button("doc", {
822 type = "image",
823 image = "doc_button_icon_hires.png",
824 tooltip = "Documentation System",
825 action = function(player)
826 doc.show_doc(player:get_player_name())
827 end,