7 doc
.VERSION
.STRING
= doc
.VERSION
.MAJOR
.."."..doc
.VERSION
.MINOR
.."."..doc
.VERSION
.PATCH
11 doc
.data
.categories
= {}
12 doc
.data
.category_order
= {}
15 -- Space for additional APIs
18 --[[ Core API functions ]]
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
)
37 function doc
.new_entry(category_id
, entry_id
, def
)
38 local cat
= doc
.data
.categories
[category_id
]
40 local hidden
= def
.hidden
or (def
.hidden
== nil and cat
.def
.hide_entries_by_default
)
42 cat
.hidden_count
= cat
.hidden_count
+ 1
45 cat
.entry_count
= doc
.data
.categories
[category_id
].entry_count
+ 1
46 cat
.entries
[entry_id
] = def
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
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
67 doc
.mark_entry_as_revealed(playername
, category_id
, entry_id
)
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
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
))
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()
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
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
114 return doc
.data
.players
[playername
].stored_data
.revealed
[category_id
][entry_id
] == true
121 -- Returns category definition
122 function doc
.get_category_definition(category_id
)
123 if doc
.data
.categories
[category_id
] == nil then
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
134 local entry
, _
= doc
.get_entry(category_id
, entry_id
)
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())
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())
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())
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
)
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
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
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
)
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
233 local count
= playerdata
.stored_data
.viewed_count
[category_id
]
235 playerdata
.stored_data
.viewed
[category_id
] = {}
237 playerdata
.stored_data
.viewed_count
[category_id
] = 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
250 local count
= playerdata
.stored_data
.revealed_count
[category_id
]
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
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
267 local total
= doc
.get_entry_count(category_id
)
268 local rcount
= playerdata
.stored_data
.revealed_count
[category_id
]
269 if rcount
== nil then
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)
281 local linebreaker_single
= function(text
)
282 local linelength
= 80
283 local remain
= linelength
286 local split
= function(s
)
288 for w
in string.gmatch(s
, "%S+") do
294 for _
, word
in ipairs(split(text
)) do
295 if string.len(word
) + 1 > remain
then
296 table.insert(res
, table.concat(line
, " "))
298 remain
= linelength
- string.len(word
)
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
)
312 for s
in string.gmatch(text
, "([^\n]*)\n") do
313 s
= linebreaker_single(s
)
317 -- Remove last newline
318 if string.len(out
) >= 1 then
319 out
= string.sub(out
, 1, string.len(out
) - 1)
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", ",")
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
).."]"
342 doc
.entry_builders
.formspec
= function(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")
353 minetest
.log("action", "[doc] doc.mt opened.")
354 local string = file
:read()
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()
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")
379 file
:write(savestring
)
381 minetest
.log("action", "[doc] Wrote player data into "..filepath
..".")
383 minetest
.log("error", "[doc] Failed to write player data into "..filepath
..".")
387 minetest
.register_on_leaveplayer(function(player
)
391 minetest
.register_on_shutdown(function()
392 minetest
.log("action", "[doc] Server shuts down. Rescuing player data into doc.mt.")
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:]"
408 for c
=1,#doc
.data
.category_order
do
409 local id
= doc
.data
.category_order
[c
]
410 local data
= doc
.data
.categories
[id
]
412 local button
= "button[0,"..y
..";3,1;doc_button_category_"..id
..";"..minetest
.formspec_escape(data
.def
.name
).."]"
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
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]"
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]"
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
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
)
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;"
474 doc
.data
.players
[playername
].entry_ids
= {}
475 local entries
= doc
.get_sorted_entry_names(cid
)
476 doc
.data
.players
[playername
].catsel_list
= {}
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
485 local viewedprefix
= "#00FFFF"
486 if doc
.entry_viewed(playername
, cid
, eid
) then
488 viewedprefix
= "#FFFFFF"
490 entry_textlist
= entry_textlist
.. viewedprefix
.. minetest
.formspec_escape(edata
.name
) .. ","
491 counter
= counter
+ 1
495 entry_textlist
= string.sub(entry_textlist
, 1, #entry_textlist
-1)
497 local catsel
= doc
.data
.players
[playername
].catsel
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
506 formstring
= doc
.data
.players
[playername
].entry_textlist
511 function doc
.get_sorted_entry_names(cid
)
512 local sort_table
= {}
513 local entry_table
= {}
514 local cat
= doc
.data
.categories
[cid
]
516 -- Helper function to extract the entry ID out of the output table
517 local extract
= function(entry_table
)
519 for k
,v
in pairs(entry_table
) do
521 table.insert(eids
, eid
)
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
)
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
)
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
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
)
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]"
574 formstring
= "label[0,0;Help > "..doc
.data
.categories
[id
].def
.name
.."]"
575 local total
= doc
.get_entry_count(id
)
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]"
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
593 formstring
= formstring
.. "\n"
594 formstring
= formstring
.. "Hidden entries: "..hidden
.."]"
596 formstring
= formstring
.. "]"
599 formstring
= formstring
.. "All entries read.]"
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]"
610 function doc
.formspec_entry_navigation(category_id
, entry_id
)
611 if doc
.get_entry_count(category_id
) < 1 then
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]"
622 function doc
.formspec_entry(category_id
, entry_id
)
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]"
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]"
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
)
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
656 cid
= doc
.data
.players
[playername
].category
657 eid
= doc
.data
.players
[playername
].entry
659 contents
= doc
.formspec_main()
662 contents
= doc
.formspec_category(cid
, playername
)
663 subformname
= "category"
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
)
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
)
687 elseif(formname
== "doc:category") then
688 if fields
["doc_button_goto_entry"] then
689 local cid
= doc
.data
.players
[playername
].category
692 local eids
, catsel
= doc
.data
.players
[playername
].entry_ids
, doc
.data
.players
[playername
].catsel
693 if eids
~= nil and catsel
~= nil then
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
713 local eids
, catsel
= doc
.data
.players
[playername
].entry_ids
, event
.index
714 if eids
~= nil and catsel
~= nil then
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", {
763 description
= "Open documentation system.",
765 func
= function(playername
, param
)
766 doc
.show_doc(playername
)
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
= {}
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
] = {}
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
802 playerdata
.stored_data
.revealed
[cid
][eid
] = true
804 if playerdata
.stored_data
.revealed
[cid
][eid
] and entry
.hidden
then
808 playerdata
.stored_data
.viewed_count
[cid
] = vc
809 playerdata
.stored_data
.revealed_count
[cid
] = rc
814 minetest
.register_on_leaveplayer(function(player
)
815 doc
.data
.players
[player
:get_player_name()] = nil
818 ---[[ Add buttons for inventory mods ]]
820 if minetest
.get_modpath("unified_inventory") ~= nil then
821 unified_inventory
.register_button("doc", {
823 image
= "doc_button_icon_hires.png",
824 tooltip
= "Documentation System",
825 action
= function(player
)
826 doc
.show_doc(player
:get_player_name())