1 -- Boilerplate to support localized strings if intllib mod is installed.
3 if minetest
.get_modpath("intllib") then
6 S
= function(s
,a
,...)a
={a
,...}return s
:gsub("@(%d+)",function(n
)return a
[tonumber(n
)]end)end
8 F
= function(f
) return minetest
.formspec_escape(S(f
)) end
10 -- Compability for 0.4.14 or earlier
13 colorize
= core
.colorize
15 colorize
= function(color
, text
) return text
end
20 -- Some informational variables
21 -- DO NOT CHANGE THEM AFTERWARDS AT RUNTIME!
23 -- Version number (follows the SemVer specification 2.0.0)
26 doc
.VERSION
.MINOR
= 11
28 doc
.VERSION
.STRING
= doc
.VERSION
.MAJOR
.."."..doc
.VERSION
.MINOR
.."."..doc
.VERSION
.PATCH
30 -- Formspec information
33 doc
.FORMSPEC
.WIDTH
= 15
34 doc
.FORMSPEC
.HEIGHT
= 10.5
36 --[[ Recommended bounding box coordinates for widgets to be placed in entry pages. Make sure
37 all entry widgets are completely inside these coordinates to avoid overlapping. ]]
38 doc
.FORMSPEC
.ENTRY_START_X
= 0
39 doc
.FORMSPEC
.ENTRY_START_Y
= 0.5
40 doc
.FORMSPEC
.ENTRY_END_X
= doc
.FORMSPEC
.WIDTH
41 doc
.FORMSPEC
.ENTRY_END_Y
= doc
.FORMSPEC
.HEIGHT
- 0.5
42 doc
.FORMSPEC
.ENTRY_WIDTH
= doc
.FORMSPEC
.ENTRY_END_X
- doc
.FORMSPEC
.ENTRY_START_X
43 doc
.FORMSPEC
.ENTRY_HEIGHT
= doc
.FORMSPEC
.ENTRY_END_Y
- doc
.FORMSPEC
.ENTRY_START_Y
45 --TODO: Use container formspec element later
47 -- Internal helper variables
48 local DOC_INTRO
= S("This is the help.")
50 local COLOR_NOT_VIEWED
= "#00FFFF" -- cyan
51 local COLOR_VIEWED
= "#FFFFFF" -- white
52 local COLOR_HIDDEN
= "#999999" -- gray
53 local COLOR_ERROR
= "#FF0000" -- red
55 local CATEGORYFIELDSIZE
= {
56 WIDTH
= math
.ceil(doc
.FORMSPEC
.WIDTH
/ 4),
57 HEIGHT
= math
.floor(doc
.FORMSPEC
.HEIGHT
-1),
60 -- Maximum characters per line in the text widget
61 local TEXT_LINELENGTH
= 80
64 doc
.data
.categories
= {}
66 -- Default order (includes categories of other mods from the Docuentation System modpack)
67 doc
.data
.category_order
= {"basics", "nodes", "tools", "craftitems", "advanced"}
68 doc
.data
.category_count
= 0
71 -- Space for additional APIs
75 local set_category_order_was_called
= false
77 --[[ Core API functions ]]
80 function doc
.add_category(id
, def
)
81 if doc
.data
.categories
[id
] == nil and id
~= nil then
82 doc
.data
.categories
[id
] = {}
83 doc
.data
.categories
[id
].entries
= {}
84 doc
.data
.categories
[id
].entry_count
= 0
85 doc
.data
.categories
[id
].hidden_count
= 0
86 doc
.data
.categories
[id
].def
= def
87 -- Determine order position
89 for i
=1,#doc
.data
.category_order
do
90 if doc
.data
.category_order
[i
] == id
then
95 if order_id
== nil then
96 table.insert(doc
.data
.category_order
, id
)
97 doc
.data
.categories
[id
].order_position
= #doc
.data
.category_order
99 doc
.data
.categories
[id
].order_position
= order_id
101 doc
.data
.category_count
= doc
.data
.category_count
+ 1
109 function doc
.add_entry(category_id
, entry_id
, def
)
110 local cat
= doc
.data
.categories
[category_id
]
112 local hidden
= def
.hidden
or (def
.hidden
== nil and cat
.def
.hide_entries_by_default
)
114 cat
.hidden_count
= cat
.hidden_count
+ 1
117 cat
.entry_count
= doc
.data
.categories
[category_id
].entry_count
+ 1
118 if def
.name
== nil or def
.name
== "" then
119 minetest
.log("warning", "[doc] Nameless entry added. Entry ID: "..entry_id
)
121 cat
.entries
[entry_id
] = def
128 -- Marks a particular entry as viewed by a certain player, which also
129 -- automatically reveals it
130 function doc
.mark_entry_as_viewed(playername
, category_id
, entry_id
)
131 local entry
, category_id
, entry_id
= doc
.get_entry(category_id
, entry_id
)
135 if doc
.data
.players
[playername
].stored_data
.viewed
[category_id
] == nil then
136 doc
.data
.players
[playername
].stored_data
.viewed
[category_id
] = {}
137 doc
.data
.players
[playername
].stored_data
.viewed_count
[category_id
] = 0
139 if doc
.entry_exists(category_id
, entry_id
) and doc
.data
.players
[playername
].stored_data
.viewed
[category_id
][entry_id
] ~= true then
140 doc
.data
.players
[playername
].stored_data
.viewed
[category_id
][entry_id
] = true
141 doc
.data
.players
[playername
].stored_data
.viewed_count
[category_id
] = doc
.data
.players
[playername
].stored_data
.viewed_count
[category_id
] + 1
142 -- Needed because viewed entries get a different color
143 doc
.data
.players
[playername
].entry_textlist_needs_updating
= true
145 doc
.mark_entry_as_revealed(playername
, category_id
, entry_id
)
148 -- Marks a particular entry as revealed/unhidden by a certain player
149 function doc
.mark_entry_as_revealed(playername
, category_id
, entry_id
)
150 local entry
, category_id
, entry_id
= doc
.get_entry(category_id
, entry_id
)
154 if doc
.data
.players
[playername
].stored_data
.revealed
[category_id
] == nil then
155 doc
.data
.players
[playername
].stored_data
.revealed
[category_id
] = {}
156 doc
.data
.players
[playername
].stored_data
.revealed_count
[category_id
] = doc
.get_entry_count(category_id
) - doc
.data
.categories
[category_id
].hidden_count
158 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
159 doc
.data
.players
[playername
].stored_data
.revealed
[category_id
][entry_id
] = true
160 doc
.data
.players
[playername
].stored_data
.revealed_count
[category_id
] = doc
.data
.players
[playername
].stored_data
.revealed_count
[category_id
] + 1
161 -- Needed because a new entry is added to the list of visible entries
162 doc
.data
.players
[playername
].entry_textlist_needs_updating
= true
163 if minetest
.get_modpath("central_message") ~= nil then
164 local cat
= doc
.data
.categories
[category_id
]
165 cmsg
.push_message_player(minetest
.get_player_by_name(playername
), S("New help entry unlocked: @1 > @2", cat
.def
.name
, entry
.name
))
167 -- To avoid sound spamming, don't play sound more than once per second
168 local last_sound
= doc
.data
.players
[playername
].last_reveal_sound
169 if last_sound
== nil or os
.difftime(os
.time(), last_sound
) >= 1 then
170 -- Play notification sound
171 minetest
.sound_play({ name
= "doc_reveal", gain
= 0.2 }, { to_player
= playername
})
172 doc
.data
.players
[playername
].last_reveal_sound
= os
.time()
178 function doc
.mark_all_entries_as_revealed(playername
)
179 -- Has at least 1 new entry been revealed?
180 local reveal1
= false
181 for category_id
, category
in pairs(doc
.data
.categories
) do
182 if doc
.data
.players
[playername
].stored_data
.revealed
[category_id
] == nil then
183 doc
.data
.players
[playername
].stored_data
.revealed
[category_id
] = {}
184 doc
.data
.players
[playername
].stored_data
.revealed_count
[category_id
] = doc
.get_entry_count(category_id
) - doc
.data
.categories
[category_id
].hidden_count
186 for entry_id
, _
in pairs(category
.entries
) do
187 if doc
.data
.players
[playername
].stored_data
.revealed
[category_id
][entry_id
] ~= true then
188 doc
.data
.players
[playername
].stored_data
.revealed
[category_id
][entry_id
] = true
189 doc
.data
.players
[playername
].stored_data
.revealed_count
[category_id
] = doc
.data
.players
[playername
].stored_data
.revealed_count
[category_id
] + 1
197 -- Needed because new entries are added to player's view on entry list
198 doc
.data
.players
[playername
].entry_textlist_needs_updating
= true
200 msg
= S("All help entries revealed!")
202 -- Play notification sound (ignore sound limit intentionally)
203 minetest
.sound_play({ name
= "doc_reveal", gain
= 0.2 }, { to_player
= playername
})
204 doc
.data
.players
[playername
].last_reveal_sound
= os
.time()
206 msg
= S("All help entries are already revealed.")
209 if minetest
.get_modpath("central_message") ~= nil then
210 cmsg
.push_message_player(minetest
.get_player_by_name(playername
), msg
)
212 minetest
.chat_send_player(playername
, msg
)
216 -- Returns true if the specified entry has been viewed by the player
217 function doc
.entry_viewed(playername
, category_id
, entry_id
)
218 local entry
, category_id
, entry_id
= doc
.get_entry(category_id
, entry_id
)
219 if doc
.data
.players
[playername
].stored_data
.viewed
[category_id
] == nil then
222 return doc
.data
.players
[playername
].stored_data
.viewed
[category_id
][entry_id
] == true
226 -- Returns true if the specified entry is hidden from the player
227 function doc
.entry_revealed(playername
, category_id
, entry_id
)
228 local entry
, category_id
, entry_id
= doc
.get_entry(category_id
, entry_id
)
229 local hidden
= doc
.data
.categories
[category_id
].entries
[entry_id
].hidden
230 if doc
.data
.players
[playername
].stored_data
.revealed
[category_id
] == nil then
234 return doc
.data
.players
[playername
].stored_data
.revealed
[category_id
][entry_id
] == true
241 -- Returns category definition
242 function doc
.get_category_definition(category_id
)
243 if doc
.data
.categories
[category_id
] == nil then
246 return doc
.data
.categories
[category_id
].def
249 -- Returns entry definition
250 function doc
.get_entry_definition(category_id
, entry_id
)
251 if not doc
.entry_exists(category_id
, entry_id
) then
254 local entry
, _
, _
= doc
.get_entry(category_id
, entry_id
)
258 -- Opens the main documentation formspec for the player
259 function doc
.show_doc(playername
)
260 if doc
.get_category_count() <= 0 then
261 minetest
.show_formspec(playername
, "doc:error_no_categories", doc
.formspec_error_no_categories())
264 local formspec
= doc
.formspec_core()..doc
.formspec_main(playername
)
265 minetest
.show_formspec(playername
, "doc:main", formspec
)
268 -- Opens the documentation formspec for the player at the specified category
269 function doc
.show_category(playername
, category_id
)
270 if doc
.get_category_count() <= 0 then
271 minetest
.show_formspec(playername
, "doc:error_no_categories", doc
.formspec_error_no_categories())
274 doc
.data
.players
[playername
].catsel
= nil
275 doc
.data
.players
[playername
].category
= category_id
276 doc
.data
.players
[playername
].entry
= nil
277 local formspec
= doc
.formspec_core(2)..doc
.formspec_category(category_id
, playername
)
278 minetest
.show_formspec(playername
, "doc:category", formspec
)
281 -- Opens the documentation formspec for the player showing the specified entry in a category
282 function doc
.show_entry(playername
, category_id
, entry_id
, ignore_hidden
)
283 if doc
.get_category_count() <= 0 then
284 minetest
.show_formspec(playername
, "doc:error_no_categories", doc
.formspec_error_no_categories())
287 local entry
, category_id
, entry_id
= doc
.get_entry(category_id
, entry_id
)
288 if ignore_hidden
or doc
.entry_revealed(playername
, category_id
, entry_id
) then
289 local playerdata
= doc
.data
.players
[playername
]
290 playerdata
.category
= category_id
291 playerdata
.entry
= entry_id
293 doc
.mark_entry_as_viewed(playername
, category_id
, entry_id
)
294 playerdata
.entry_textlist_needs_updating
= true
295 doc
.generate_entry_list(category_id
, playername
)
297 playerdata
.catsel
= playerdata
.catsel_list
[entry_id
]
298 playerdata
.galidx
= 1
300 local formspec
= doc
.formspec_core(3)..doc
.formspec_entry(category_id
, entry_id
, playername
)
301 minetest
.show_formspec(playername
, "doc:entry", formspec
)
303 minetest
.show_formspec(playername
, "doc:error_hidden", doc
.formspec_error_hidden(category_id
, entry_id
))
307 -- Returns true if and only if:
308 -- * The specified category exists
309 -- * This category contains the specified entry
310 -- Aliases are taken into account
311 function doc
.entry_exists(category_id
, entry_id
)
312 return doc
.get_entry(category_id
, entry_id
) ~= nil
315 -- Sets the order of categories in the category list
316 function doc
.set_category_order(categories
)
317 local reverse_categories
= {}
318 for cid
=1,#categories
do
319 reverse_categories
[categories
[cid]]
= cid
321 doc
.data
.category_order
= categories
322 for cid
, cat
in pairs(doc
.data
.categories
) do
323 if reverse_categories
[cid
] == nil then
324 table.insert(doc
.data
.category_order
, cid
)
327 reverse_categories
= {}
328 for cid
=1, #doc
.data
.category_order
do
329 reverse_categories
[categories
[cid]]
= cid
332 for cid
, cat
in pairs(doc
.data
.categories
) do
333 cat
.order_position
= reverse_categories
[cid
]
335 if set_category_order_was_called
then
336 minetest
.log("warning", "[doc] doc.set_category_order was called again!")
338 set_category_order_was_called
= true
341 -- Adds an alias for an entry. Attempting to open an entry by an alias name
342 -- results in opening the entry of the original name.
343 function doc
.add_entry_alias(category_id_orig
, entry_id_orig
, category_id_alias
, entry_id_alias
)
344 if not doc
.data
.aliases
[category_id_alias
] then
345 doc
.data
.aliases
[category_id_alias
] = {}
347 doc
.data
.aliases
[category_id_alias
][entry_id_alias
] = { category_id
= category_id_orig
, entry_id
= entry_id_orig
}
350 -- Returns number of categories
351 function doc
.get_category_count()
352 return doc
.data
.category_count
355 -- Returns number of entries in category
356 function doc
.get_entry_count(category_id
)
357 return doc
.data
.categories
[category_id
].entry_count
360 -- Returns how many entries have been viewed by the player
361 function doc
.get_viewed_count(playername
, category_id
)
362 local playerdata
= doc
.data
.players
[playername
]
363 if playerdata
== nil then
366 local count
= playerdata
.stored_data
.viewed_count
[category_id
]
368 playerdata
.stored_data
.viewed
[category_id
] = {}
370 playerdata
.stored_data
.viewed_count
[category_id
] = count
377 -- Returns how many entries have been revealed by the player
378 function doc
.get_revealed_count(playername
, category_id
)
379 local playerdata
= doc
.data
.players
[playername
]
380 if playerdata
== nil then
383 local count
= playerdata
.stored_data
.revealed_count
[category_id
]
385 playerdata
.stored_data
.revealed
[category_id
] = {}
386 count
= doc
.get_entry_count(category_id
) - doc
.data
.categories
[category_id
].hidden_count
387 playerdata
.stored_data
.revealed_count
[category_id
] = count
394 -- Returns how many entries are hidden from the player
395 function doc
.get_hidden_count(playername
, category_id
)
396 local playerdata
= doc
.data
.players
[playername
]
397 if playerdata
== nil then
400 local total
= doc
.get_entry_count(category_id
)
401 local rcount
= playerdata
.stored_data
.revealed_count
[category_id
]
402 if rcount
== nil then
405 return total
- rcount
409 -- Returns the currently viewed entry and/or category of the player
410 function doc
.get_selection(playername
)
411 local playerdata
= doc
.data
.players
[playername
]
412 if playerdata
~= nil then
413 local cat
= playerdata
.category
415 local entry
= playerdata
.entry
429 -- Template function templates, to be used for build_formspec in doc.add_category
430 doc
.entry_builders
= {}
432 -- Inserts line breaks into a single paragraph and collapses all whitespace (including newlines)
434 local linebreaker_single
= function(text
, linelength
)
435 if linelength
== nil then
436 linelength
= TEXT_LINELENGTH
438 local remain
= linelength
441 local split
= function(s
)
443 for w
in string.gmatch(s
, "%S+") do
449 for _
, word
in ipairs(split(text
)) do
450 if string.len(word
) + 1 > remain
then
451 table.insert(res
, table.concat(line
, " "))
453 remain
= linelength
- string.len(word
)
455 table.insert(line
, word
)
456 remain
= remain
- (string.len(word
) + 1)
460 table.insert(res
, table.concat(line
, " "))
461 return table.concat(res
, "\n")
464 -- Inserts automatic line breaks into an entire text and preserves existing newlines
465 local linebreaker
= function(text
, linelength
)
467 for s
in string.gmatch(text
, "([^\n]*)") do
468 local l
= linebreaker_single(s
, linelength
)
470 if(string.len(l
) == 0) then
474 -- Remove last newline
475 if string.len(out
) >= 1 then
476 out
= string.sub(out
, 1, string.len(out
) - 1)
481 -- Inserts text suitable for a textlist (including automatic word-wrap)
482 local text_for_textlist
= function(text
, linelength
)
483 text
= linebreaker(text
, linelength
)
484 text
= minetest
.formspec_escape(text
)
485 text
= string.gsub(text
, "\n", ",")
489 -- Scrollable freeform text
490 doc
.entry_builders
.text
= function(data
)
491 local formstring
= doc
.widgets
.text(data
, doc
.FORMSPEC
.ENTRY_START_X
, doc
.FORMSPEC
.ENTRY_START_Y
, doc
.FORMSPEC
.ENTRY_WIDTH
- 0.2, doc
.FORMSPEC
.ENTRY_HEIGHT
)
495 -- Scrollable freeform text with an optional standard gallery (3 rows, 3:2 aspect ratio)
496 doc
.entry_builders
.text_and_gallery
= function(data
, playername
)
497 -- How much height the image gallery “steals” from the text widget
498 local stolen_height
= 0
499 local formstring
= ""
500 -- Only add the gallery if images are in the data, otherwise, the text widget gets all of the space
501 if data
.images
~= nil then
503 gallery
, stolen_height
= doc
.widgets
.gallery(data
.images
, playername
, nil, doc
.FORMSPEC
.ENTRY_END_Y
+ 0.2, nil, nil, nil, nil, false)
504 formstring
= formstring
.. gallery
506 formstring
= formstring
.. doc
.widgets
.text(data
.text
,
507 doc
.FORMSPEC
.ENTRY_START_X
,
508 doc
.FORMSPEC
.ENTRY_START_Y
,
509 doc
.FORMSPEC
.ENTRY_WIDTH
- 0.2,
510 doc
.FORMSPEC
.ENTRY_HEIGHT
- stolen_height
)
518 -- Scrollable freeform text
519 doc
.widgets
.text
= function(data
, x
, y
, width
, height
)
521 x
= doc
.FORMSPEC
.ENTRY_START_X
524 y
= doc
.FORMSPEC
.ENTRY_START_Y
527 width
= doc
.FORMSPEC
.ENTRY_WIDTH
529 if height
== nil then
530 height
= doc
.FORMSPEC
.ENTRY_HEIGHT
532 local baselength
= TEXT_LINELENGTH
533 local widget_basewidth
= doc
.FORMSPEC
.WIDTH
534 local linelength
= math
.max(20, math
.floor(baselength
* (width
/ widget_basewidth
)))
536 local widget_id
= "doc_widget_text"..text_id
537 text_id
= text_id
+ 1
538 -- TODO: Wait for Minetest to provide a native widget for scrollable read-only text with automatic line breaks.
539 -- Currently, all of this had to be hacked into this script manually by using/abusing the table widget
540 local formstring
= "tablecolumns[text]"..
541 "tableoptions[background=#000000FF;highlight=#000000FF;border=false]"..
542 "table["..tostring(x
)..","..tostring(y
)..";"..tostring(width
)..","..tostring(height
)..";"..widget_id
..";"..text_for_textlist(data
, linelength
).."]"
543 return formstring
, widget_id
547 -- Currently, only one gallery per entry is supported. TODO: Add support for multiple galleries in an entry (low priority)
548 doc
.widgets
.gallery
= function(imagedata
, playername
, x
, y
, aspect_ratio
, width
, rows
, align_left
, align_top
)
549 if playername
== nil then return nil end -- emergency exit
551 local formstring
= ""
555 if align_left
== false then
556 x
= doc
.FORMSPEC
.ENTRY_END_X
558 x
= doc
.FORMSPEC
.ENTRY_START_X
562 if align_top
== false then
563 y
= doc
.FORMSPEC
.ENTRY_END_Y
565 y
= doc
.FORMSPEC
.ENTRY_START_Y
568 if width
== nil then width
= doc
.FORMSPEC
.ENTRY_WIDTH
end
569 if rows
== nil then rows
= 3 end
571 if align_left
== false then
575 local imageindex
= doc
.data
.players
[playername
].galidx
576 doc
.data
.players
[playername
].maxgalidx
= #imagedata
577 doc
.data
.players
[playername
].galrows
= rows
579 if aspect_ratio
== nil then aspect_ratio
= (2/3) end
581 local totalimagewidth
, iw
, ih
583 local buttonoffset
= 0
584 if #imagedata
> rows
then
585 totalimagewidth
= width
- bw
*2
586 iw
= totalimagewidth
/ rows
587 ih
= iw
* aspect_ratio
588 if align_top
== false then
593 if imageindex
> 1 then
594 formstring
= formstring
.. "button["..x
..","..y
..";"..bw
..","..ih
..";doc_button_gallery_prev;"..F("<").."]"
596 tt
= F("Show previous image")
598 tt
= F("Show previous gallery page")
600 formstring
= formstring
.. "tooltip[doc_button_gallery_prev;"..tt
.."]"
602 if (imageindex
+ rows
) <= #imagedata
then
603 local rightx
= buttonoffset
+ (x
+ rows
* iw
)
604 formstring
= formstring
.. "button["..rightx
..","..y
..";"..bw
..","..ih
..";doc_button_gallery_next;"..F(">").."]"
606 tt
= F("Show next image")
608 tt
= F("Show next gallery page")
610 formstring
= formstring
.. "tooltip[doc_button_gallery_next;"..tt
.."]"
614 totalimagewidth
= width
615 iw
= totalimagewidth
/ rows
616 ih
= iw
* aspect_ratio
617 if align_top
== false then
621 for i
=imageindex
, math
.min(#imagedata
, (imageindex
-1)+rows
) do
622 local xoffset
= buttonoffset
+ (x
+ pos
* iw
)
623 local nx
= xoffset
- 0.2
625 if imagedata
[i
].imagetype
== "item" then
626 formstring
= formstring
.. "item_image["..xoffset
..","..y
..";"..iw
..","..ih
..";"..imagedata
[i
].image
.."]"
628 formstring
= formstring
.. "image["..xoffset
..","..y
..";"..iw
..","..ih
..";"..imagedata
[i
].image
.."]"
630 formstring
= formstring
.. "label["..nx
..","..ny
..";"..i
.."]"
635 return formstring
, ih
639 doc
.entry_builders
.formspec
= function(data
)
643 --[[ Internal stuff ]]
645 -- Loading and saving player data
647 local filepath
= minetest
.get_worldpath().."/doc.mt"
648 local file
= io
.open(filepath
, "r")
650 minetest
.log("action", "[doc] doc.mt opened.")
651 local string = file
:read()
653 if(string ~= nil) then
654 local savetable
= minetest
.deserialize(string)
655 for name
, players_stored_data
in pairs(savetable
.players_stored_data
) do
656 doc
.data
.players
[name
] = {}
657 doc
.data
.players
[name
].stored_data
= players_stored_data
659 minetest
.debug("[doc] doc.mt successfully read.")
664 function doc
.save_to_file()
666 savetable
.players_stored_data
= {}
667 for name
, playerdata
in pairs(doc
.data
.players
) do
668 savetable
.players_stored_data
[name
] = playerdata
.stored_data
671 local savestring
= minetest
.serialize(savetable
)
673 local filepath
= minetest
.get_worldpath().."/doc.mt"
674 local file
= io
.open(filepath
, "w")
676 file
:write(savestring
)
678 minetest
.log("action", "[doc] Wrote player data into "..filepath
..".")
680 minetest
.log("error", "[doc] Failed to write player data into "..filepath
..".")
684 minetest
.register_on_leaveplayer(function(player
)
688 minetest
.register_on_shutdown(function()
689 minetest
.log("action", "[doc] Server shuts down. Player data is about to be saved.")
693 --[[ Functions for internal use ]]
695 function doc
.formspec_core(tab
)
696 if tab
== nil then tab
= 1 else tab
= tostring(tab
) end
697 return "size["..doc
.FORMSPEC
.WIDTH
..","..doc
.FORMSPEC
.HEIGHT
.."]tabheader[0,0;doc_header;"..
698 minetest
.formspec_escape(S("Category list")) .. "," ..
699 minetest
.formspec_escape(S("Entry list")) .. "," ..
700 minetest
.formspec_escape(S("Entry")) .. ";"
701 ..tab
..";true;true]" ..
705 function doc
.formspec_main(playername
)
706 local formstring
= "label[0,0;"..minetest
.formspec_escape(DOC_INTRO
) .. "\n"
707 if doc
.get_category_count() >= 1 then
708 formstring
= formstring
.. F("Please select a category you wish to learn more about:").."]"
709 if doc
.get_category_count() <= (CATEGORYFIELDSIZE
.WIDTH
* CATEGORYFIELDSIZE
.HEIGHT
) then
712 -- Show all categories in order
713 for c
=1,#doc
.data
.category_order
do
714 local id
= doc
.data
.category_order
[c
]
715 local data
= doc
.data
.categories
[id
]
716 local bw
= doc
.FORMSPEC
.WIDTH
/ math
.floor(((doc
.data
.category_count
-1) / CATEGORYFIELDSIZE
.HEIGHT
)+1)
717 -- Skip categories which do not exist
720 local button
= "button["..((x
-1)*bw
)..","..y
..";"..bw
..",1;doc_button_category_"..id
..";"..minetest
.formspec_escape(data
.def
.name
).."]"
722 -- Optional description
723 if data
.def
.description
~= nil then
724 tooltip
= "tooltip[doc_button_category_"..id
..";"..minetest
.formspec_escape(data
.def
.description
).."]"
726 formstring
= formstring
.. button
.. tooltip
728 if y
> CATEGORYFIELDSIZE
.HEIGHT
then
735 formstring
= formstring
.. "textlist[0,1;"..(doc
.FORMSPEC
.WIDTH
-0.2)..","..(doc
.FORMSPEC
.HEIGHT
-2)..";doc_mainlist;"
736 for c
=1,#doc
.data
.category_order
do
737 local id
= doc
.data
.category_order
[c
]
738 local data
= doc
.data
.categories
[id
]
739 formstring
= formstring
.. minetest
.formspec_escape(data
.def
.name
)
740 if c
< #doc
.data
.category_order
then
741 formstring
= formstring
.. ","
744 local sel
= doc
.data
.categories
[doc
.data
.players
[playername
].category
]
746 formstring
= formstring
.. ";"
747 formstring
= formstring
.. doc
.data
.categories
[doc
.data
.players
[playername
].category
].order_position
749 formstring
= formstring
.. "]"
750 formstring
= formstring
.. "button[0,"..(doc
.FORMSPEC
.HEIGHT
-1)..";3,1;doc_button_goto_category;"..F("Show category").."]"
753 formstring
= formstring
.. "]"
758 function doc
.formspec_error_no_categories()
759 local formstring
= "size[8,6]textarea[0.25,0;8,6;;"
760 formstring
= formstring
..
761 minetest
.formspec_escape(
762 colorize(COLOR_ERROR
, S("Error: No help available.")) .. "\n\n" ..
763 S("No categories have been registered, but they are required to provide help.\nThe Documentation System [doc] does not come with help contents on its own, it needs additional mods to add help content. Please make sure such mods are enabled on for this world, and try again.")) .. "\n\n" ..
764 S("Recommended mods: doc_basics, doc_items, doc_identifier, doc_encyclopedia.")
765 formstring
= formstring
.. ";]button_exit[3,5;2,1;okay;"..F("OK").."]"
769 function doc
.formspec_error_hidden(category_id
, entry_id
)
770 local formstring
= "size[8,6]textarea[0.25,0;8,6;;"
771 formstring
= formstring
.. minetest
.formspec_escape(
772 colorize(COLOR_ERROR
, S("Error: Access denied.")) .. "\n\n" ..
773 S("Access to the requested entry has been denied; this entry is secret. You may unlock access by progressing in the game. Figure out on your own how to unlock this entry."))
774 formstring
= formstring
.. ";]button_exit[3,5;2,1;okay;"..F("OK").."]"
778 -- Returns the entry definition and true entry ID of an entry, taking aliases into account
779 function doc
.get_entry(category_id
, entry_id
)
780 local category
= doc
.data
.categories
[category_id
]
782 if category
~= nil then
783 entry
= category
.entries
[entry_id
]
785 if category
== nil or entry
== nil then
786 local c_alias
= doc
.data
.aliases
[category_id
]
788 local alias
= c_alias
[entry_id
]
790 category_id
= alias
.category_id
791 entry_id
= alias
.entry_id
792 category
= doc
.data
.categories
[category_id
]
794 entry
= category
.entries
[entry_id
]
805 return entry
, category_id
, entry_id
808 function doc
.generate_entry_list(cid
, playername
)
810 if doc
.data
.players
[playername
].entry_textlist
== nil
811 or doc
.data
.players
[playername
].catsel_list
== nil
812 or doc
.data
.players
[playername
].category
~= cid
813 or doc
.data
.players
[playername
].entry_textlist_needs_updating
== true then
814 local entry_textlist
= "textlist[0,1;"..(doc
.FORMSPEC
.WIDTH
-0.2)..","..(doc
.FORMSPEC
.HEIGHT
-2)..";doc_catlist;"
816 doc
.data
.players
[playername
].entry_ids
= {}
817 local entries
= doc
.get_sorted_entry_names(cid
)
818 doc
.data
.players
[playername
].catsel_list
= {}
820 local eid
= entries
[i
]
821 local edata
= doc
.data
.categories
[cid
].entries
[eid
]
822 if doc
.entry_revealed(playername
, cid
, eid
) then
823 table.insert(doc
.data
.players
[playername
].entry_ids
, eid
)
824 doc
.data
.players
[playername
].catsel_list
[eid
] = counter
+ 1
825 -- Colorize entries based on viewed status
826 local viewedprefix
= COLOR_NOT_VIEWED
827 local name
= edata
.name
828 if name
== nil or name
== "" then
829 name
= S("Nameless entry (@1)", eid
)
830 if doc
.entry_viewed(playername
, cid
, eid
) then
831 viewedprefix
= "#FF4444"
833 viewedprefix
= COLOR_ERROR
835 elseif doc
.entry_viewed(playername
, cid
, eid
) then
836 viewedprefix
= COLOR_VIEWED
838 entry_textlist
= entry_textlist
.. viewedprefix
.. minetest
.formspec_escape(name
) .. ","
839 counter
= counter
+ 1
843 entry_textlist
= string.sub(entry_textlist
, 1, #entry_textlist
-1)
845 local catsel
= doc
.data
.players
[playername
].catsel
847 entry_textlist
= entry_textlist
.. ";"..catsel
849 entry_textlist
= entry_textlist
.. "]"
850 doc
.data
.players
[playername
].entry_textlist
= entry_textlist
851 formstring
= entry_textlist
852 doc
.data
.players
[playername
].entry_textlist_needs_updating
= false
854 formstring
= doc
.data
.players
[playername
].entry_textlist
859 function doc
.get_sorted_entry_names(cid
)
860 local sort_table
= {}
861 local entry_table
= {}
862 local cat
= doc
.data
.categories
[cid
]
864 -- Helper function to extract the entry ID out of the output table
865 local extract
= function(entry_table
)
867 for k
,v
in pairs(entry_table
) do
869 table.insert(eids
, eid
)
873 -- Predefined sorting
874 if cat
.def
.sorting
== "custom" then
875 for i
=1,#cat
.def
.sorting_data
do
876 local new_entry
= table.copy(cat
.entries
[cat
.def
.sorting_data
[i]]
)
877 new_entry
.eid
= cat
.def
.sorting_data
[i
]
878 table.insert(entry_table
, new_entry
)
879 used_eids
[cat
.def
.sorting_data
[i]]
= true
882 for eid
,entry
in pairs(cat
.entries
) do
883 local new_entry
= table.copy(entry
)
885 if not used_eids
[eid
] then
886 table.insert(entry_table
, new_entry
)
888 table.insert(sort_table
, entry
.name
)
890 if cat
.def
.sorting
== "custom" then
891 return extract(entry_table
)
893 table.sort(sort_table
)
895 local reverse_sort_table
= table.copy(sort_table
)
896 for i
=1, #sort_table
do
897 reverse_sort_table
[sort_table
[i]]
= i
900 if cat
.def
.sorting
~= "nosort" then
901 -- Sorting by user function
902 if cat
.def
.sorting
== "function" then
903 comp
= cat
.def
.sorting_data
904 -- Alphabetic sorting
905 elseif cat
.def
.sorting
== "abc" or cat
.def
.sorting
== nil then
906 comp
= function(e1
, e2
)
907 if reverse_sort_table
[e1
.name
] < reverse_sort_table
[e2
.name
] then return true else return false end
910 table.sort(entry_table
, comp
)
913 return extract(entry_table
)
916 function doc
.formspec_category(id
, playername
)
919 formstring
= "label[0,0;"..F("Help > (No Category)") .. "]"
920 formstring
= formstring
.. "label[0,0.5;"..F("You haven't chosen a category yet. Please choose one in the category list first.").."]"
921 formstring
= formstring
.. "button[0,1;3,1;doc_button_goto_main;"..F("Go to category list").."]"
923 formstring
= "label[0,0;"..minetest
.formspec_escape(S("Help > @1", doc
.data
.categories
[id
].def
.name
)).."]"
924 local total
= doc
.get_entry_count(id
)
926 local revealed
= doc
.get_revealed_count(playername
, id
)
927 if revealed
== 0 then
928 formstring
= formstring
.. "label[0,0.5;"..F("Currently all entries in this category are hidden from you.\nUnlock new entries by progressing in the game.").."]"
929 formstring
= formstring
.. "button[0,1.5;3,1;doc_button_goto_main;"..F("Go to category list").."]"
931 formstring
= formstring
.. "label[0,0.5;"..F("This category has the following entries:").."]"
932 formstring
= formstring
.. doc
.generate_entry_list(id
, playername
)
933 formstring
= formstring
.. "button[0,"..(doc
.FORMSPEC
.HEIGHT
-1)..";3,1;doc_button_goto_entry;"..F("Show entry").."]"
934 formstring
= formstring
.. "label["..(doc
.FORMSPEC
.WIDTH
-4)..","..(doc
.FORMSPEC
.HEIGHT
-1)..";"..minetest
.formspec_escape(S("Number of entries: @1", total
)).."\n"
935 local viewed
= doc
.get_viewed_count(playername
, id
)
936 local hidden
= total
- revealed
937 local new
= total
- viewed
- hidden
938 -- TODO/FIXME: Check if number of hidden/viewed entries is always correct
939 if viewed
< total
then
940 formstring
= formstring
.. colorize(COLOR_NOT_VIEWED
, minetest
.formspec_escape(S("New entries: @1", new
)))
942 formstring
= formstring
.. "\n"
943 formstring
= formstring
.. colorize(COLOR_HIDDEN
, minetest
.formspec_escape(S("Hidden entries: @1", hidden
))).."]"
945 formstring
= formstring
.. "]"
948 formstring
= formstring
.. F("All entries read.").."]"
952 formstring
= formstring
.. "label[0,0.5;"..F("This category is empty.").."]"
953 formstring
= formstring
.. "button[0,1.5;3,1;doc_button_goto_main;"..F("Go to category list").."]"
959 function doc
.formspec_entry_navigation(category_id
, entry_id
)
960 if doc
.get_entry_count(category_id
) < 1 then
963 local formstring
= ""
964 formstring
= formstring
.. "button["..(doc
.FORMSPEC
.WIDTH
-2)..","..(doc
.FORMSPEC
.HEIGHT
-0.5)..";1,1;doc_button_goto_prev;"..F("<").."]"
965 formstring
= formstring
.. "button["..(doc
.FORMSPEC
.WIDTH
-1)..","..(doc
.FORMSPEC
.HEIGHT
-0.5)..";1,1;doc_button_goto_next;"..F(">").."]"
966 formstring
= formstring
.. "tooltip[doc_button_goto_prev;"..F("Show previous entry").."]"
967 formstring
= formstring
.. "tooltip[doc_button_goto_next;"..F("Show next entry").."]"
971 function doc
.formspec_entry(category_id
, entry_id
, playername
)
973 if category_id
== nil then
974 formstring
= "label[0,0;"..F("Help > (No Category)") .. "]"
975 formstring
= formstring
.. "label[0,0.5;"..F("You haven't chosen a category yet. Please choose one in the category list first.").."]"
976 formstring
= formstring
.. "button[0,1;3,1;doc_button_goto_main;"..F("Go to category list").."]"
977 elseif entry_id
== nil then
978 formstring
= "label[0,0;"..minetest
.formspec_escape(S("Help > @1 > (No Entry)", doc
.data
.categories
[category_id
].def
.name
)) .. "]"
979 if doc
.get_entry_count(category_id
) >= 1 then
980 formstring
= formstring
.. "label[0,0.5;"..F("You haven't chosen an entry yet. Please choose one in the entry list first.").."]"
981 formstring
= formstring
.. "button[0,1.5;3,1;doc_button_goto_category;"..F("Go to entry list").."]"
983 formstring
= formstring
.. "label[0,0.5;"..F("This category does not have any entries.").."]"
984 formstring
= formstring
.. "button[0,1.5;3,1;doc_button_goto_main;"..F("Go to category list").."]"
988 local category
= doc
.data
.categories
[category_id
]
989 local entry
= doc
.get_entry(category_id
, entry_id
)
990 local ename
= entry
.name
991 if ename
== nil or ename
== "" then
992 ename
= S("Nameless entry (@1)", entry_id
)
994 formstring
= "label[0,0;"..minetest
.formspec_escape(S("Help > @1 > @2", category
.def
.name
, ename
)).."]"
995 formstring
= formstring
.. category
.def
.build_formspec(entry
.data
, playername
)
996 formstring
= formstring
.. doc
.formspec_entry_navigation(category_id
, entry_id
)
1001 function doc
.process_form(player
,formname
,fields
)
1002 local playername
= player
:get_player_name()
1003 --[[ process clicks on the tab header ]]
1004 if(formname
== "doc:main" or formname
== "doc:category" or formname
== "doc:entry") then
1005 if fields
.doc_header
~= nil then
1006 local tab
= tonumber(fields
.doc_header
)
1007 local formspec
, subformname
, contents
1009 cid
= doc
.data
.players
[playername
].category
1010 eid
= doc
.data
.players
[playername
].entry
1012 contents
= doc
.formspec_main(playername
)
1013 subformname
= "main"
1015 contents
= doc
.formspec_category(cid
, playername
)
1016 subformname
= "category"
1018 doc
.data
.players
[playername
].galidx
= 1
1019 contents
= doc
.formspec_entry(cid
, eid
, playername
)
1020 if cid
~= nil and eid
~= nil then
1021 doc
.mark_entry_as_viewed(playername
, cid
, eid
)
1023 subformname
= "entry"
1025 formspec
= doc
.formspec_core(tab
)..contents
1026 minetest
.show_formspec(playername
, "doc:" .. subformname
, formspec
)
1030 if(formname
== "doc:main") then
1031 for cid
,_
in pairs(doc
.data
.categories
) do
1032 if fields
["doc_button_category_"..cid
] then
1033 doc
.data
.players
[playername
].catsel
= nil
1034 doc
.data
.players
[playername
].category
= cid
1035 doc
.data
.players
[playername
].entry
= nil
1036 doc
.data
.players
[playername
].entry_textlist_needs_updating
= true
1037 local formspec
= doc
.formspec_core(2)..doc
.formspec_category(cid
, playername
)
1038 minetest
.show_formspec(playername
, "doc:category", formspec
)
1042 if fields
["doc_mainlist"] then
1043 local event
= minetest
.explode_textlist_event(fields
["doc_mainlist"])
1044 local cid
= doc
.data
.category_order
[event
.index
]
1046 if event
.type == "CHG" then
1047 doc
.data
.players
[playername
].catsel
= nil
1048 doc
.data
.players
[playername
].category
= cid
1049 doc
.data
.players
[playername
].entry
= nil
1050 doc
.data
.players
[playername
].entry_textlist_needs_updating
= true
1051 elseif event
.type == "DCL" then
1052 doc
.data
.players
[playername
].catsel
= nil
1053 doc
.data
.players
[playername
].category
= cid
1054 doc
.data
.players
[playername
].entry
= nil
1055 doc
.data
.players
[playername
].entry_textlist_needs_updating
= true
1056 local formspec
= doc
.formspec_core(2)..doc
.formspec_category(cid
, playername
)
1057 minetest
.show_formspec(playername
, "doc:category", formspec
)
1061 if fields
["doc_button_goto_category"] then
1062 local cid
= doc
.data
.players
[playername
].category
1063 doc
.data
.players
[playername
].catsel
= nil
1064 doc
.data
.players
[playername
].entry
= nil
1065 doc
.data
.players
[playername
].entry_textlist_needs_updating
= true
1066 local formspec
= doc
.formspec_core(2)..doc
.formspec_category(cid
, playername
)
1067 minetest
.show_formspec(playername
, "doc:category", formspec
)
1069 elseif(formname
== "doc:category") then
1070 if fields
["doc_button_goto_entry"] then
1071 local cid
= doc
.data
.players
[playername
].category
1074 local eids
, catsel
= doc
.data
.players
[playername
].entry_ids
, doc
.data
.players
[playername
].catsel
1075 if eids
~= nil and catsel
~= nil then
1078 doc
.data
.players
[playername
].galidx
= 1
1079 local formspec
= doc
.formspec_core(3)..doc
.formspec_entry(cid
, eid
, playername
)
1080 minetest
.show_formspec(playername
, "doc:entry", formspec
)
1081 doc
.mark_entry_as_viewed(playername
, cid
, eid
)
1084 if fields
["doc_button_goto_main"] then
1085 local formspec
= doc
.formspec_core(1)..doc
.formspec_main(playername
)
1086 minetest
.show_formspec(playername
, "doc:main", formspec
)
1088 if fields
["doc_catlist"] then
1089 local event
= minetest
.explode_textlist_event(fields
["doc_catlist"])
1090 if event
.type == "CHG" then
1091 doc
.data
.players
[playername
].catsel
= event
.index
1092 doc
.data
.players
[playername
].entry
= doc
.data
.players
[playername
].entry_ids
[event
.index
]
1093 doc
.data
.players
[playername
].entry_textlist_needs_updating
= true
1094 elseif event
.type == "DCL" then
1095 local cid
= doc
.data
.players
[playername
].category
1097 local eids
, catsel
= doc
.data
.players
[playername
].entry_ids
, event
.index
1098 if eids
~= nil and catsel
~= nil then
1101 doc
.mark_entry_as_viewed(playername
, cid
, eid
)
1102 doc
.data
.players
[playername
].entry_textlist_needs_updating
= true
1103 doc
.data
.players
[playername
].galidx
= 1
1104 local formspec
= doc
.formspec_core(3)..doc
.formspec_entry(cid
, eid
, playername
)
1105 minetest
.show_formspec(playername
, "doc:entry", formspec
)
1108 elseif(formname
== "doc:entry") then
1109 if fields
["doc_button_goto_main"] then
1110 local formspec
= doc
.formspec_core(1)..doc
.formspec_main(playername
)
1111 minetest
.show_formspec(playername
, "doc:main", formspec
)
1112 elseif fields
["doc_button_goto_category"] then
1113 local formspec
= doc
.formspec_core(2)..doc
.formspec_category(doc
.data
.players
[playername
].category
, playername
)
1114 minetest
.show_formspec(playername
, "doc:category", formspec
)
1115 elseif fields
["doc_button_goto_next"] then
1116 if doc
.data
.players
[playername
].catsel
== nil then return end -- emergency exit
1117 local eids
= doc
.data
.players
[playername
].entry_ids
1118 local cid
= doc
.data
.players
[playername
].category
1119 local new_catsel
= doc
.data
.players
[playername
].catsel
+ 1
1120 local new_eid
= eids
[new_catsel
]
1121 if #eids
> 1 and new_catsel
<= #eids
then
1122 doc
.mark_entry_as_viewed(playername
, cid
, new_eid
)
1123 doc
.data
.players
[playername
].catsel
= new_catsel
1124 doc
.data
.players
[playername
].entry
= new_eid
1125 doc
.data
.players
[playername
].galidx
= 1
1126 local formspec
= doc
.formspec_core(3)..doc
.formspec_entry(cid
, new_eid
, playername
)
1127 minetest
.show_formspec(playername
, "doc:entry", formspec
)
1129 elseif fields
["doc_button_goto_prev"] then
1130 if doc
.data
.players
[playername
].catsel
== nil then return end -- emergency exit
1131 local eids
= doc
.data
.players
[playername
].entry_ids
1132 local cid
= doc
.data
.players
[playername
].category
1133 local new_catsel
= doc
.data
.players
[playername
].catsel
- 1
1134 local new_eid
= eids
[new_catsel
]
1135 if #eids
> 1 and new_catsel
>= 1 then
1136 doc
.mark_entry_as_viewed(playername
, cid
, new_eid
)
1137 doc
.data
.players
[playername
].catsel
= new_catsel
1138 doc
.data
.players
[playername
].entry
= new_eid
1139 doc
.data
.players
[playername
].galidx
= 1
1140 local formspec
= doc
.formspec_core(3)..doc
.formspec_entry(cid
, new_eid
, playername
)
1141 minetest
.show_formspec(playername
, "doc:entry", formspec
)
1143 elseif fields
["doc_button_gallery_prev"] then
1144 local cid
, eid
= doc
.get_selection(playername
)
1145 if doc
.data
.players
[playername
].galidx
- doc
.data
.players
[playername
].galrows
> 0 then
1146 doc
.data
.players
[playername
].galidx
= doc
.data
.players
[playername
].galidx
- doc
.data
.players
[playername
].galrows
1148 local formspec
= doc
.formspec_core(3)..doc
.formspec_entry(cid
, eid
, playername
)
1149 minetest
.show_formspec(playername
, "doc:entry", formspec
)
1150 elseif fields
["doc_button_gallery_next"] then
1151 local cid
, eid
= doc
.get_selection(playername
)
1152 if doc
.data
.players
[playername
].galidx
+ doc
.data
.players
[playername
].galrows
<= doc
.data
.players
[playername
].maxgalidx
then
1153 doc
.data
.players
[playername
].galidx
= doc
.data
.players
[playername
].galidx
+ doc
.data
.players
[playername
].galrows
1155 local formspec
= doc
.formspec_core(3)..doc
.formspec_entry(cid
, eid
, playername
)
1156 minetest
.show_formspec(playername
, "doc:entry", formspec
)
1159 if fields
["doc_inventory_plus"] and minetest
.get_modpath("inventory_plus") then
1160 doc
.show_doc(playername
)
1166 minetest
.register_on_player_receive_fields(doc
.process_form
)
1168 minetest
.register_chatcommand("helpform", {
1170 description
= S("Open a window providing help entries about Minetest and more"),
1172 func
= function(playername
, param
)
1173 doc
.show_doc(playername
)
1178 minetest
.register_on_joinplayer(function(player
)
1179 local playername
= player
:get_player_name()
1180 local playerdata
= doc
.data
.players
[playername
]
1181 if playerdata
== nil then
1182 -- Initialize player data
1183 doc
.data
.players
[playername
] = {}
1184 playerdata
= doc
.data
.players
[playername
]
1185 -- Gallery index, stores current index of first displayed image in a gallery
1186 playerdata
.galidx
= 1
1187 -- Maximum gallery index (index of last image in gallery)
1188 playerdata
.maxgalidx
= 1
1189 -- Number of rows in an gallery of the current entry
1190 playerdata
.galrows
= 1
1191 -- Table for persistant data
1192 playerdata
.stored_data
= {}
1193 -- Contains viewed entries
1194 playerdata
.stored_data
.viewed
= {}
1195 -- Count viewed entries
1196 playerdata
.stored_data
.viewed_count
= {}
1197 -- Contains revealed/unhidden entries
1198 playerdata
.stored_data
.revealed
= {}
1199 -- Count revealed entries
1200 playerdata
.stored_data
.revealed_count
= {}
1202 -- Completely rebuild viewed and revealed counts from scratch
1203 for cid
, cat
in pairs(doc
.data
.categories
) do
1204 if playerdata
.stored_data
.viewed
[cid
] == nil then
1205 playerdata
.stored_data
.viewed
[cid
] = {}
1207 if playerdata
.stored_data
.revealed
[cid
] == nil then
1208 playerdata
.stored_data
.revealed
[cid
] = {}
1211 local rc
= doc
.get_entry_count(cid
) - doc
.data
.categories
[cid
].hidden_count
1212 for eid
, entry
in pairs(cat
.entries
) do
1213 if playerdata
.stored_data
.viewed
[cid
][eid
] then
1215 playerdata
.stored_data
.revealed
[cid
][eid
] = true
1217 if playerdata
.stored_data
.revealed
[cid
][eid
] and entry
.hidden
then
1221 playerdata
.stored_data
.viewed_count
[cid
] = vc
1222 playerdata
.stored_data
.revealed_count
[cid
] = rc
1226 -- Add button for Inventory++
1227 if minetest
.get_modpath("inventory_plus") ~= nil then
1228 inventory_plus
.register_button(player
, "doc_inventory_plus", S("Help"))
1232 ---[[ Add buttons for inventory mods ]]
1233 local button_action
= function(player
)
1234 doc
.show_doc(player
:get_player_name())
1237 -- Unified Inventory
1238 if minetest
.get_modpath("unified_inventory") ~= nil then
1239 unified_inventory
.register_button("doc", {
1241 image
= "doc_button_icon_hires.png",
1242 tooltip
= S("Help"),
1243 action
= button_action
,
1248 if minetest
.get_modpath("sfinv_buttons") ~= nil then
1249 sfinv_buttons
.register_button("doc", {
1250 image
= "doc_button_icon_lores.png",
1251 tooltip
= S("Collection of help texts"),
1253 action
= button_action
,
1258 minetest
.register_privilege("help_reveal", {
1259 description
= S("Allows you to reveal all hidden help entries with /help_reveal"),
1260 give_to_singleplayer
= false
1263 minetest
.register_chatcommand("help_reveal", {
1265 description
= S("Reveal all hidden help entries to you"),
1266 privs
= { help_reveal
= true },
1267 func
= function(name
, param
)
1268 doc
.mark_all_entries_as_revealed(name
)