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 -- Returns the entry definition and true entry ID of an entry, taking aliases into account
78 local function get_entry(category_id
, entry_id
)
79 local category
= doc
.data
.categories
[category_id
]
81 if category
~= nil then
82 entry
= category
.entries
[entry_id
]
84 if category
== nil or entry
== nil then
85 local c_alias
= doc
.data
.aliases
[category_id
]
87 local alias
= c_alias
[entry_id
]
89 category_id
= alias
.category_id
90 entry_id
= alias
.entry_id
91 category
= doc
.data
.categories
[category_id
]
93 entry
= category
.entries
[entry_id
]
104 return entry
, category_id
, entry_id
107 --[[ Core API functions ]]
109 -- Add a new category
110 function doc
.add_category(id
, def
)
111 if doc
.data
.categories
[id
] == nil and id
~= nil then
112 doc
.data
.categories
[id
] = {}
113 doc
.data
.categories
[id
].entries
= {}
114 doc
.data
.categories
[id
].entry_count
= 0
115 doc
.data
.categories
[id
].hidden_count
= 0
116 doc
.data
.categories
[id
].def
= def
117 -- Determine order position
119 for i
=1,#doc
.data
.category_order
do
120 if doc
.data
.category_order
[i
] == id
then
125 if order_id
== nil then
126 table.insert(doc
.data
.category_order
, id
)
127 doc
.data
.categories
[id
].order_position
= #doc
.data
.category_order
129 doc
.data
.categories
[id
].order_position
= order_id
131 doc
.data
.category_count
= doc
.data
.category_count
+ 1
139 function doc
.add_entry(category_id
, entry_id
, def
)
140 local cat
= doc
.data
.categories
[category_id
]
142 local hidden
= def
.hidden
or (def
.hidden
== nil and cat
.def
.hide_entries_by_default
)
144 cat
.hidden_count
= cat
.hidden_count
+ 1
147 cat
.entry_count
= doc
.data
.categories
[category_id
].entry_count
+ 1
148 if def
.name
== nil or def
.name
== "" then
149 minetest
.log("warning", "[doc] Nameless entry added. Entry ID: "..entry_id
)
151 cat
.entries
[entry_id
] = def
158 -- Marks a particular entry as viewed by a certain player, which also
159 -- automatically reveals it
160 function doc
.mark_entry_as_viewed(playername
, category_id
, entry_id
)
161 local entry
, category_id
, entry_id
= get_entry(category_id
, entry_id
)
165 if doc
.data
.players
[playername
].stored_data
.viewed
[category_id
] == nil then
166 doc
.data
.players
[playername
].stored_data
.viewed
[category_id
] = {}
167 doc
.data
.players
[playername
].stored_data
.viewed_count
[category_id
] = 0
169 if doc
.entry_exists(category_id
, entry_id
) and doc
.data
.players
[playername
].stored_data
.viewed
[category_id
][entry_id
] ~= true then
170 doc
.data
.players
[playername
].stored_data
.viewed
[category_id
][entry_id
] = true
171 doc
.data
.players
[playername
].stored_data
.viewed_count
[category_id
] = doc
.data
.players
[playername
].stored_data
.viewed_count
[category_id
] + 1
172 -- Needed because viewed entries get a different color
173 doc
.data
.players
[playername
].entry_textlist_needs_updating
= true
175 doc
.mark_entry_as_revealed(playername
, category_id
, entry_id
)
178 -- Marks a particular entry as revealed/unhidden by a certain player
179 function doc
.mark_entry_as_revealed(playername
, category_id
, entry_id
)
180 local entry
, category_id
, entry_id
= get_entry(category_id
, entry_id
)
184 if doc
.data
.players
[playername
].stored_data
.revealed
[category_id
] == nil then
185 doc
.data
.players
[playername
].stored_data
.revealed
[category_id
] = {}
186 doc
.data
.players
[playername
].stored_data
.revealed_count
[category_id
] = doc
.get_entry_count(category_id
) - doc
.data
.categories
[category_id
].hidden_count
188 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
189 doc
.data
.players
[playername
].stored_data
.revealed
[category_id
][entry_id
] = true
190 doc
.data
.players
[playername
].stored_data
.revealed_count
[category_id
] = doc
.data
.players
[playername
].stored_data
.revealed_count
[category_id
] + 1
191 -- Needed because a new entry is added to the list of visible entries
192 doc
.data
.players
[playername
].entry_textlist_needs_updating
= true
193 if minetest
.get_modpath("central_message") ~= nil then
194 local cat
= doc
.data
.categories
[category_id
]
195 cmsg
.push_message_player(minetest
.get_player_by_name(playername
), S("New help entry unlocked: @1 > @2", cat
.def
.name
, entry
.name
))
197 -- To avoid sound spamming, don't play sound more than once per second
198 local last_sound
= doc
.data
.players
[playername
].last_reveal_sound
199 if last_sound
== nil or os
.difftime(os
.time(), last_sound
) >= 1 then
200 -- Play notification sound
201 minetest
.sound_play({ name
= "doc_reveal", gain
= 0.2 }, { to_player
= playername
})
202 doc
.data
.players
[playername
].last_reveal_sound
= os
.time()
208 function doc
.mark_all_entries_as_revealed(playername
)
209 -- Has at least 1 new entry been revealed?
210 local reveal1
= false
211 for category_id
, category
in pairs(doc
.data
.categories
) do
212 if doc
.data
.players
[playername
].stored_data
.revealed
[category_id
] == nil then
213 doc
.data
.players
[playername
].stored_data
.revealed
[category_id
] = {}
214 doc
.data
.players
[playername
].stored_data
.revealed_count
[category_id
] = doc
.get_entry_count(category_id
) - doc
.data
.categories
[category_id
].hidden_count
216 for entry_id
, _
in pairs(category
.entries
) do
217 if doc
.data
.players
[playername
].stored_data
.revealed
[category_id
][entry_id
] ~= true then
218 doc
.data
.players
[playername
].stored_data
.revealed
[category_id
][entry_id
] = true
219 doc
.data
.players
[playername
].stored_data
.revealed_count
[category_id
] = doc
.data
.players
[playername
].stored_data
.revealed_count
[category_id
] + 1
227 -- Needed because new entries are added to player's view on entry list
228 doc
.data
.players
[playername
].entry_textlist_needs_updating
= true
230 msg
= S("All help entries revealed!")
232 -- Play notification sound (ignore sound limit intentionally)
233 minetest
.sound_play({ name
= "doc_reveal", gain
= 0.2 }, { to_player
= playername
})
234 doc
.data
.players
[playername
].last_reveal_sound
= os
.time()
236 msg
= S("All help entries are already revealed.")
239 if minetest
.get_modpath("central_message") ~= nil then
240 cmsg
.push_message_player(minetest
.get_player_by_name(playername
), msg
)
242 minetest
.chat_send_player(playername
, msg
)
246 -- Returns true if the specified entry has been viewed by the player
247 function doc
.entry_viewed(playername
, category_id
, entry_id
)
248 local entry
, category_id
, entry_id
= get_entry(category_id
, entry_id
)
249 if doc
.data
.players
[playername
].stored_data
.viewed
[category_id
] == nil then
252 return doc
.data
.players
[playername
].stored_data
.viewed
[category_id
][entry_id
] == true
256 -- Returns true if the specified entry is hidden from the player
257 function doc
.entry_revealed(playername
, category_id
, entry_id
)
258 local entry
, category_id
, entry_id
= get_entry(category_id
, entry_id
)
259 local hidden
= doc
.data
.categories
[category_id
].entries
[entry_id
].hidden
260 if doc
.data
.players
[playername
].stored_data
.revealed
[category_id
] == nil then
264 return doc
.data
.players
[playername
].stored_data
.revealed
[category_id
][entry_id
] == true
271 -- Returns category definition
272 function doc
.get_category_definition(category_id
)
273 if doc
.data
.categories
[category_id
] == nil then
276 return doc
.data
.categories
[category_id
].def
279 -- Returns entry definition
280 function doc
.get_entry_definition(category_id
, entry_id
)
281 if not doc
.entry_exists(category_id
, entry_id
) then
284 local entry
, _
, _
= get_entry(category_id
, entry_id
)
288 -- Opens the main documentation formspec for the player
289 function doc
.show_doc(playername
)
290 if doc
.get_category_count() <= 0 then
291 minetest
.show_formspec(playername
, "doc:error_no_categories", doc
.formspec_error_no_categories())
294 local formspec
= doc
.formspec_core()..doc
.formspec_main(playername
)
295 minetest
.show_formspec(playername
, "doc:main", formspec
)
298 -- Opens the documentation formspec for the player at the specified category
299 function doc
.show_category(playername
, category_id
)
300 if doc
.get_category_count() <= 0 then
301 minetest
.show_formspec(playername
, "doc:error_no_categories", doc
.formspec_error_no_categories())
304 doc
.data
.players
[playername
].catsel
= nil
305 doc
.data
.players
[playername
].category
= category_id
306 doc
.data
.players
[playername
].entry
= nil
307 local formspec
= doc
.formspec_core(2)..doc
.formspec_category(category_id
, playername
)
308 minetest
.show_formspec(playername
, "doc:category", formspec
)
311 -- Opens the documentation formspec for the player showing the specified entry in a category
312 function doc
.show_entry(playername
, category_id
, entry_id
, ignore_hidden
)
313 if doc
.get_category_count() <= 0 then
314 minetest
.show_formspec(playername
, "doc:error_no_categories", doc
.formspec_error_no_categories())
317 local entry
, category_id
, entry_id
= get_entry(category_id
, entry_id
)
318 if ignore_hidden
or doc
.entry_revealed(playername
, category_id
, entry_id
) then
319 local playerdata
= doc
.data
.players
[playername
]
320 playerdata
.category
= category_id
321 playerdata
.entry
= entry_id
323 doc
.mark_entry_as_viewed(playername
, category_id
, entry_id
)
324 playerdata
.entry_textlist_needs_updating
= true
325 doc
.generate_entry_list(category_id
, playername
)
327 playerdata
.catsel
= playerdata
.catsel_list
[entry_id
]
328 playerdata
.galidx
= 1
330 local formspec
= doc
.formspec_core(3)..doc
.formspec_entry(category_id
, entry_id
, playername
)
331 minetest
.show_formspec(playername
, "doc:entry", formspec
)
333 minetest
.show_formspec(playername
, "doc:error_hidden", doc
.formspec_error_hidden(category_id
, entry_id
))
337 -- Returns true if and only if:
338 -- * The specified category exists
339 -- * This category contains the specified entry
340 -- Aliases are taken into account
341 function doc
.entry_exists(category_id
, entry_id
)
342 return get_entry(category_id
, entry_id
) ~= nil
345 -- Sets the order of categories in the category list
346 function doc
.set_category_order(categories
)
347 local reverse_categories
= {}
348 for cid
=1,#categories
do
349 reverse_categories
[categories
[cid]]
= cid
351 doc
.data
.category_order
= categories
352 for cid
, cat
in pairs(doc
.data
.categories
) do
353 if reverse_categories
[cid
] == nil then
354 table.insert(doc
.data
.category_order
, cid
)
357 reverse_categories
= {}
358 for cid
=1, #doc
.data
.category_order
do
359 reverse_categories
[categories
[cid]]
= cid
362 for cid
, cat
in pairs(doc
.data
.categories
) do
363 cat
.order_position
= reverse_categories
[cid
]
365 if set_category_order_was_called
then
366 minetest
.log("warning", "[doc] doc.set_category_order was called again!")
368 set_category_order_was_called
= true
371 -- Adds an alias for an entry. Attempting to open an entry by an alias name
372 -- results in opening the entry of the original name.
373 function doc
.add_entry_alias(category_id_orig
, entry_id_orig
, category_id_alias
, entry_id_alias
)
374 if not doc
.data
.aliases
[category_id_alias
] then
375 doc
.data
.aliases
[category_id_alias
] = {}
377 doc
.data
.aliases
[category_id_alias
][entry_id_alias
] = { category_id
= category_id_orig
, entry_id
= entry_id_orig
}
380 -- Returns number of categories
381 function doc
.get_category_count()
382 return doc
.data
.category_count
385 -- Returns number of entries in category
386 function doc
.get_entry_count(category_id
)
387 return doc
.data
.categories
[category_id
].entry_count
390 -- Returns how many entries have been viewed by the player
391 function doc
.get_viewed_count(playername
, category_id
)
392 local playerdata
= doc
.data
.players
[playername
]
393 if playerdata
== nil then
396 local count
= playerdata
.stored_data
.viewed_count
[category_id
]
398 playerdata
.stored_data
.viewed
[category_id
] = {}
400 playerdata
.stored_data
.viewed_count
[category_id
] = count
407 -- Returns how many entries have been revealed by the player
408 function doc
.get_revealed_count(playername
, category_id
)
409 local playerdata
= doc
.data
.players
[playername
]
410 if playerdata
== nil then
413 local count
= playerdata
.stored_data
.revealed_count
[category_id
]
415 playerdata
.stored_data
.revealed
[category_id
] = {}
416 count
= doc
.get_entry_count(category_id
) - doc
.data
.categories
[category_id
].hidden_count
417 playerdata
.stored_data
.revealed_count
[category_id
] = count
424 -- Returns how many entries are hidden from the player
425 function doc
.get_hidden_count(playername
, category_id
)
426 local playerdata
= doc
.data
.players
[playername
]
427 if playerdata
== nil then
430 local total
= doc
.get_entry_count(category_id
)
431 local rcount
= playerdata
.stored_data
.revealed_count
[category_id
]
432 if rcount
== nil then
435 return total
- rcount
439 -- Returns the currently viewed entry and/or category of the player
440 function doc
.get_selection(playername
)
441 local playerdata
= doc
.data
.players
[playername
]
442 if playerdata
~= nil then
443 local cat
= playerdata
.category
445 local entry
= playerdata
.entry
459 -- Template function templates, to be used for build_formspec in doc.add_category
460 doc
.entry_builders
= {}
462 -- Inserts line breaks into a single paragraph and collapses all whitespace (including newlines)
464 local linebreaker_single
= function(text
, linelength
)
465 if linelength
== nil then
466 linelength
= TEXT_LINELENGTH
468 local remain
= linelength
471 local split
= function(s
)
473 for w
in string.gmatch(s
, "%S+") do
479 for _
, word
in ipairs(split(text
)) do
480 if string.len(word
) + 1 > remain
then
481 table.insert(res
, table.concat(line
, " "))
483 remain
= linelength
- string.len(word
)
485 table.insert(line
, word
)
486 remain
= remain
- (string.len(word
) + 1)
490 table.insert(res
, table.concat(line
, " "))
491 return table.concat(res
, "\n")
494 -- Inserts automatic line breaks into an entire text and preserves existing newlines
495 local linebreaker
= function(text
, linelength
)
497 for s
in string.gmatch(text
, "([^\n]*)") do
498 local l
= linebreaker_single(s
, linelength
)
500 if(string.len(l
) == 0) then
504 -- Remove last newline
505 if string.len(out
) >= 1 then
506 out
= string.sub(out
, 1, string.len(out
) - 1)
511 -- Inserts text suitable for a textlist (including automatic word-wrap)
512 local text_for_textlist
= function(text
, linelength
)
513 text
= linebreaker(text
, linelength
)
514 text
= minetest
.formspec_escape(text
)
515 text
= string.gsub(text
, "\n", ",")
519 -- Scrollable freeform text
520 doc
.entry_builders
.text
= function(data
)
521 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
)
525 -- Scrollable freeform text with an optional standard gallery (3 rows, 3:2 aspect ratio)
526 doc
.entry_builders
.text_and_gallery
= function(data
, playername
)
527 -- How much height the image gallery “steals” from the text widget
528 local stolen_height
= 0
529 local formstring
= ""
530 -- Only add the gallery if images are in the data, otherwise, the text widget gets all of the space
531 if data
.images
~= nil then
533 gallery
, stolen_height
= doc
.widgets
.gallery(data
.images
, playername
, nil, doc
.FORMSPEC
.ENTRY_END_Y
+ 0.2, nil, nil, nil, nil, false)
534 formstring
= formstring
.. gallery
536 formstring
= formstring
.. doc
.widgets
.text(data
.text
,
537 doc
.FORMSPEC
.ENTRY_START_X
,
538 doc
.FORMSPEC
.ENTRY_START_Y
,
539 doc
.FORMSPEC
.ENTRY_WIDTH
- 0.2,
540 doc
.FORMSPEC
.ENTRY_HEIGHT
- stolen_height
)
548 -- Scrollable freeform text
549 doc
.widgets
.text
= function(data
, x
, y
, width
, height
)
551 x
= doc
.FORMSPEC
.ENTRY_START_X
554 y
= doc
.FORMSPEC
.ENTRY_START_Y
557 width
= doc
.FORMSPEC
.ENTRY_WIDTH
559 if height
== nil then
560 height
= doc
.FORMSPEC
.ENTRY_HEIGHT
562 local baselength
= TEXT_LINELENGTH
563 local widget_basewidth
= doc
.FORMSPEC
.WIDTH
564 local linelength
= math
.max(20, math
.floor(baselength
* (width
/ widget_basewidth
)))
566 local widget_id
= "doc_widget_text"..text_id
567 text_id
= text_id
+ 1
568 -- TODO: Wait for Minetest to provide a native widget for scrollable read-only text with automatic line breaks.
569 -- Currently, all of this had to be hacked into this script manually by using/abusing the table widget
570 local formstring
= "tablecolumns[text]"..
571 "tableoptions[background=#000000FF;highlight=#000000FF;border=false]"..
572 "table["..tostring(x
)..","..tostring(y
)..";"..tostring(width
)..","..tostring(height
)..";"..widget_id
..";"..text_for_textlist(data
, linelength
).."]"
573 return formstring
, widget_id
577 -- Currently, only one gallery per entry is supported. TODO: Add support for multiple galleries in an entry (low priority)
578 doc
.widgets
.gallery
= function(imagedata
, playername
, x
, y
, aspect_ratio
, width
, rows
, align_left
, align_top
)
579 if playername
== nil then return nil end -- emergency exit
581 local formstring
= ""
585 if align_left
== false then
586 x
= doc
.FORMSPEC
.ENTRY_END_X
588 x
= doc
.FORMSPEC
.ENTRY_START_X
592 if align_top
== false then
593 y
= doc
.FORMSPEC
.ENTRY_END_Y
595 y
= doc
.FORMSPEC
.ENTRY_START_Y
598 if width
== nil then width
= doc
.FORMSPEC
.ENTRY_WIDTH
end
599 if rows
== nil then rows
= 3 end
601 if align_left
== false then
605 local imageindex
= doc
.data
.players
[playername
].galidx
606 doc
.data
.players
[playername
].maxgalidx
= #imagedata
607 doc
.data
.players
[playername
].galrows
= rows
609 if aspect_ratio
== nil then aspect_ratio
= (2/3) end
611 local totalimagewidth
, iw
, ih
613 local buttonoffset
= 0
614 if #imagedata
> rows
then
615 totalimagewidth
= width
- bw
*2
616 iw
= totalimagewidth
/ rows
617 ih
= iw
* aspect_ratio
618 if align_top
== false then
623 if imageindex
> 1 then
624 formstring
= formstring
.. "button["..x
..","..y
..";"..bw
..","..ih
..";doc_button_gallery_prev;"..F("<").."]"
626 tt
= F("Show previous image")
628 tt
= F("Show previous gallery page")
630 formstring
= formstring
.. "tooltip[doc_button_gallery_prev;"..tt
.."]"
632 if (imageindex
+ rows
) <= #imagedata
then
633 local rightx
= buttonoffset
+ (x
+ rows
* iw
)
634 formstring
= formstring
.. "button["..rightx
..","..y
..";"..bw
..","..ih
..";doc_button_gallery_next;"..F(">").."]"
636 tt
= F("Show next image")
638 tt
= F("Show next gallery page")
640 formstring
= formstring
.. "tooltip[doc_button_gallery_next;"..tt
.."]"
644 totalimagewidth
= width
645 iw
= totalimagewidth
/ rows
646 ih
= iw
* aspect_ratio
647 if align_top
== false then
651 for i
=imageindex
, math
.min(#imagedata
, (imageindex
-1)+rows
) do
652 local xoffset
= buttonoffset
+ (x
+ pos
* iw
)
653 local nx
= xoffset
- 0.2
655 if imagedata
[i
].imagetype
== "item" then
656 formstring
= formstring
.. "item_image["..xoffset
..","..y
..";"..iw
..","..ih
..";"..imagedata
[i
].image
.."]"
658 formstring
= formstring
.. "image["..xoffset
..","..y
..";"..iw
..","..ih
..";"..imagedata
[i
].image
.."]"
660 formstring
= formstring
.. "label["..nx
..","..ny
..";"..i
.."]"
665 return formstring
, ih
669 doc
.entry_builders
.formspec
= function(data
)
673 --[[ Internal stuff ]]
675 -- Loading and saving player data
677 local filepath
= minetest
.get_worldpath().."/doc.mt"
678 local file
= io
.open(filepath
, "r")
680 minetest
.log("action", "[doc] doc.mt opened.")
681 local string = file
:read()
683 if(string ~= nil) then
684 local savetable
= minetest
.deserialize(string)
685 for name
, players_stored_data
in pairs(savetable
.players_stored_data
) do
686 doc
.data
.players
[name
] = {}
687 doc
.data
.players
[name
].stored_data
= players_stored_data
689 minetest
.debug("[doc] doc.mt successfully read.")
694 function doc
.save_to_file()
696 savetable
.players_stored_data
= {}
697 for name
, playerdata
in pairs(doc
.data
.players
) do
698 savetable
.players_stored_data
[name
] = playerdata
.stored_data
701 local savestring
= minetest
.serialize(savetable
)
703 local filepath
= minetest
.get_worldpath().."/doc.mt"
704 local file
= io
.open(filepath
, "w")
706 file
:write(savestring
)
708 minetest
.log("action", "[doc] Wrote player data into "..filepath
..".")
710 minetest
.log("error", "[doc] Failed to write player data into "..filepath
..".")
714 minetest
.register_on_leaveplayer(function(player
)
718 minetest
.register_on_shutdown(function()
719 minetest
.log("action", "[doc] Server shuts down. Player data is about to be saved.")
723 --[[ Functions for internal use ]]
725 function doc
.formspec_core(tab
)
726 if tab
== nil then tab
= 1 else tab
= tostring(tab
) end
727 return "size["..doc
.FORMSPEC
.WIDTH
..","..doc
.FORMSPEC
.HEIGHT
.."]tabheader[0,0;doc_header;"..
728 minetest
.formspec_escape(S("Category list")) .. "," ..
729 minetest
.formspec_escape(S("Entry list")) .. "," ..
730 minetest
.formspec_escape(S("Entry")) .. ";"
731 ..tab
..";true;true]" ..
735 function doc
.formspec_main(playername
)
736 local formstring
= "label[0,0;"..minetest
.formspec_escape(DOC_INTRO
) .. "\n"
737 if doc
.get_category_count() >= 1 then
738 formstring
= formstring
.. F("Please select a category you wish to learn more about:").."]"
739 if doc
.get_category_count() <= (CATEGORYFIELDSIZE
.WIDTH
* CATEGORYFIELDSIZE
.HEIGHT
) then
742 -- Show all categories in order
743 for c
=1,#doc
.data
.category_order
do
744 local id
= doc
.data
.category_order
[c
]
745 local data
= doc
.data
.categories
[id
]
746 local bw
= doc
.FORMSPEC
.WIDTH
/ math
.floor(((doc
.data
.category_count
-1) / CATEGORYFIELDSIZE
.HEIGHT
)+1)
747 -- Skip categories which do not exist
750 local button
= "button["..((x
-1)*bw
)..","..y
..";"..bw
..",1;doc_button_category_"..id
..";"..minetest
.formspec_escape(data
.def
.name
).."]"
752 -- Optional description
753 if data
.def
.description
~= nil then
754 tooltip
= "tooltip[doc_button_category_"..id
..";"..minetest
.formspec_escape(data
.def
.description
).."]"
756 formstring
= formstring
.. button
.. tooltip
758 if y
> CATEGORYFIELDSIZE
.HEIGHT
then
765 formstring
= formstring
.. "textlist[0,1;"..(doc
.FORMSPEC
.WIDTH
-0.2)..","..(doc
.FORMSPEC
.HEIGHT
-2)..";doc_mainlist;"
766 for c
=1,#doc
.data
.category_order
do
767 local id
= doc
.data
.category_order
[c
]
768 local data
= doc
.data
.categories
[id
]
769 formstring
= formstring
.. minetest
.formspec_escape(data
.def
.name
)
770 if c
< #doc
.data
.category_order
then
771 formstring
= formstring
.. ","
774 local sel
= doc
.data
.categories
[doc
.data
.players
[playername
].category
]
776 formstring
= formstring
.. ";"
777 formstring
= formstring
.. doc
.data
.categories
[doc
.data
.players
[playername
].category
].order_position
779 formstring
= formstring
.. "]"
780 formstring
= formstring
.. "button[0,"..(doc
.FORMSPEC
.HEIGHT
-1)..";3,1;doc_button_goto_category;"..F("Show category").."]"
783 formstring
= formstring
.. "]"
788 function doc
.formspec_error_no_categories()
789 local formstring
= "size[8,6]textarea[0.25,0;8,6;;"
790 formstring
= formstring
..
791 minetest
.formspec_escape(
792 colorize(COLOR_ERROR
, S("Error: No help available.")) .. "\n\n" ..
793 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" ..
794 S("Recommended mods: doc_basics, doc_items, doc_identifier, doc_encyclopedia.")
795 formstring
= formstring
.. ";]button_exit[3,5;2,1;okay;"..F("OK").."]"
799 function doc
.formspec_error_hidden(category_id
, entry_id
)
800 local formstring
= "size[8,6]textarea[0.25,0;8,6;;"
801 formstring
= formstring
.. minetest
.formspec_escape(
802 colorize(COLOR_ERROR
, S("Error: Access denied.")) .. "\n\n" ..
803 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."))
804 formstring
= formstring
.. ";]button_exit[3,5;2,1;okay;"..F("OK").."]"
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
= 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
)