minetest_doc/init.lua
2016-08-13 13:30:38 +02:00

520 lines
18 KiB
Lua

doc = {}
doc.VERSION = {}
doc.VERSION.MAJOR = 0
doc.VERSION.MINOR = 3
doc.VERSION.PATCH = 0
doc.VERSION.STRING = doc.VERSION.MAJOR.."."..doc.VERSION.MINOR.."."..doc.VERSION.PATCH
doc.data = {}
doc.data.categories = {}
doc.data.category_order = {}
doc.data.players = {}
-- Space for additional APIs
doc.sub = {}
--[[ Core API functions ]]
-- Add a new category
function doc.new_category(id, def)
if doc.data.categories[id] == nil and id ~= nil then
doc.data.categories[id] = {}
doc.data.categories[id].entries = {}
doc.data.categories[id].entry_count = 0
doc.data.categories[id].def = def
doc.data.categories[id].entry_aliases = {}
table.insert(doc.data.category_order, id)
return true
else
return false
end
end
-- Add a new entry
function doc.new_entry(category_id, entry_id, def)
if doc.data.categories[category_id] ~= nil then
doc.data.categories[category_id].entries[entry_id] = def
doc.data.categories[category_id].entry_count = doc.data.categories[category_id].entry_count + 1
return true
else
return false
end
end
-- Marks a particular entry as viewed by a certain player
function doc.mark_entry_as_viewed(playername, category_id, entry_id)
if doc.data.players[playername].stored_data.viewed[category_id] == nil then
doc.data.players[playername].stored_data.viewed[category_id] = {}
doc.data.players[playername].stored_data.viewed_count[category_id] = 0
end
if doc.entry_exists(category_id, entry_id) and doc.data.players[playername].stored_data.viewed[category_id][entry_id] ~= true then
doc.data.players[playername].stored_data.viewed[category_id][entry_id] = true
doc.data.players[playername].stored_data.viewed_count[category_id] = doc.data.players[playername].stored_data.viewed_count[category_id] + 1
-- Needed because viewed entries get a different color
doc.data.players[playername].entry_textlist_needs_updating = true
end
end
-- Returns true if the specified entry has been viewed by the player
function doc.entry_viewed(playername, category_id, entry_id)
if doc.data.players[playername].stored_data.viewed[category_id] == nil then
return false
else
return doc.data.players[playername].stored_data.viewed[category_id][entry_id] == true
end
end
-- Opens the main documentation formspec for the player
function doc.show_doc(playername)
if doc.get_category_count() <= 0 then
minetest.show_formspec(playername, "doc:error_no_categories", doc.formspec_error_no_categories())
return
end
local formspec = doc.formspec_core()..doc.formspec_main()
minetest.show_formspec(playername, "doc:main", formspec)
end
-- Opens the documentation formspec for the player at the specified category
function doc.show_category(playername, category_id)
if doc.get_category_count() <= 0 then
minetest.show_formspec(playername, "doc:error_no_categories", doc.formspec_error_no_categories())
return
end
doc.data.players[playername].catsel = nil
doc.data.players[playername].category = category_id
doc.data.players[playername].entry = nil
local formspec = doc.formspec_core(2)..doc.formspec_category(category_id, playername)
minetest.show_formspec(playername, "doc:category", formspec)
end
-- Opens the documentation formspec for the player showing the specified entry in a category
function doc.show_entry(playername, category_id, entry_id)
if doc.get_category_count() <= 0 then
minetest.show_formspec(playername, "doc:error_no_categories", doc.formspec_error_no_categories())
return
end
-- FIXME: catsel must be set!
doc.data.players[playername].catsel = nil
doc.data.players[playername].category = category_id
doc.data.players[playername].entry = entry_id
doc.mark_entry_as_viewed(playername, category_id, entry_id)
local formspec = doc.formspec_core(3)..doc.formspec_entry(category_id, entry_id)
minetest.show_formspec(playername, "doc:entry", formspec)
end
-- Returns true if and only if:
-- * The specified category exists
-- * This category contains the specified entry
function doc.entry_exists(category_id, entry_id)
if doc.data.categories[category_id] ~= nil then
if doc.data.categories[category_id].entries[entry_id] ~= nil then
-- Entry exists
return true
else
-- Entry of this ID does not exist, so we check if there's an alis for it
return doc.data.categories[category_id].entry_aliases[entry_id] ~= nil
end
else
return false
end
end
-- Adds aliases for an entry. Attempting to open an entry by an alias name
-- results in opening the entry of the original name.
-- Aliases are true within one category only.
function doc.add_entry_aliases(category_id, entry_id, aliases)
for a=1,#aliases do
doc.data.categories[category_id].entry_aliases[aliases[a]] = entry_id
end
end
-- Same as above, but only adds one alias
function doc.add_entry_alias(category_id, entry_id, alias)
doc.data.categories[category_id].entry_aliases[alias] = entry_id
end
-- Returns number of categories
function doc.get_category_count()
return #doc.data.category_order
end
-- Returns number of entries in category
function doc.get_entry_count(category_id)
return doc.data.categories[category_id].entry_count
end
-- Returns how many entries have been viewed by the player
function doc.get_viewed_count(playername, category_id)
if doc.data.players[playername] == nil then
return nil
end
local count = doc.data.players[playername].stored_data.viewed_count[category_id]
if count == nil then
return 0
else
return count
end
end
-- Template function templates, to be used for build_formspec in doc.new_category
doc.entry_builders = {}
-- Freeform text
doc.entry_builders.text = function(data)
return "textarea[0.25,0.5;12,10;;"..minetest.formspec_escape(data)..";]"
end
-- Direct formspec
doc.entry_builders.formspec = function(data)
return data
end
--[[ Functions for internal use ]]
function doc.formspec_core(tab)
if tab == nil then tab = 1 else tab = tostring(tab) end
return "size[12,9]tabheader[0,0;doc_header;Category list,Entry list,Entry;"..tab..";true;false]"
end
function doc.formspec_main()
local formstring = "label[0,0;This is the Documentation System, Version "..doc.VERSION.STRING..".\n"
if doc.get_category_count() >= 1 then
formstring = formstring .. "Please select a category you wish to learn more about:]"
local y = 1
for c=1,#doc.data.category_order do
local id = doc.data.category_order[c]
local data = doc.data.categories[id]
-- Category buton
local button = "button[0,"..y..";3,1;doc_button_category_"..id..";"..minetest.formspec_escape(data.def.name).."]"
local tooltip = ""
-- Optional description
if data.def.description ~= nil then
tooltip = "tooltip[doc_button_category_"..id..";"..minetest.formspec_escape(data.def.description).."]"
end
formstring = formstring .. button .. tooltip
y = y + 1
end
end
return formstring
end
function doc.formspec_error_no_categories()
local formstring = "size[8,6]textarea[0.25,0;8,6;;"
formstring = formstring .. minetest.formspec_escape(
[=[This is the Documentation System, Version ]=]..doc.VERSION.STRING..[=[.
ERROR: No help available.
No categories have been registered, but the Documentation System is useless without them.
The Documentation System 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.]=])
formstring = formstring .. ";]button_exit[3,5;2,1;okay;OK]"
return formstring
end
function doc.generate_entry_list(cid, playername)
local formstring
if doc.data.players[playername].entry_textlist == nil
or doc.data.players[playername].category ~= cid
or doc.data.players[playername].entry_textlist_needs_updating == true then
local entry_textlist = "textlist[0,1;11,7;doc_catlist;"
local counter = 0
doc.data.players[playername].entry_ids = {}
local entries = doc.get_sorted_entry_names(cid)
for i=1, #entries do
local eid = entries[i].eid
table.insert(doc.data.players[playername].entry_ids, eid)
-- Colorize entries based on viewed status
-- Not viewed: Cyan
local viewedprefix = "#00FFFF"
if doc.entry_viewed(playername, cid, eid) then
-- Viewed: White
viewedprefix = "#FFFFFF"
end
entry_textlist = entry_textlist .. viewedprefix .. minetest.formspec_escape(entries[i].name) .. ","
counter = counter + 1
end
if counter >= 1 then
entry_textlist = string.sub(entry_textlist, 1, #entry_textlist-1)
end
local catsel = doc.data.players[playername].catsel
if catsel then
entry_textlist = entry_textlist .. ";"..catsel
end
entry_textlist = entry_textlist .. "]"
doc.data.players[playername].entry_textlist = entry_textlist
formstring = entry_textlist
doc.data.players[playername].entry_testlist_needs_updating = false
else
formstring = doc.data.players[playername].entry_textlist
end
return formstring
end
function doc.get_sorted_entry_names(cid)
local sort_table = {}
local entry_table = {}
local cat = doc.data.categories[cid]
local used_eids = {}
-- Predefined sorting
if cat.def.sorting == "custom" then
for i=1,#cat.def.sorting_data do
local new_entry = table.copy(cat.entries[cat.def.sorting_data[i]])
new_entry.eid = cat.def.sorting_data[i]
table.insert(entry_table, new_entry)
used_eids[cat.def.sorting_data[i]] = true
end
end
for eid,entry in pairs(cat.entries) do
local new_entry = table.copy(entry)
new_entry.eid = eid
if not used_eids[eid] then
table.insert(entry_table, new_entry)
end
table.insert(sort_table, entry.name)
end
if cat.def.sorting == "custom" then
return entry_table
else
table.sort(sort_table)
end
local reverse_sort_table = table.copy(sort_table)
for i=1, #sort_table do
reverse_sort_table[sort_table[i]] = i
end
local comp
if cat.def.sorting ~= "nosort" then
-- Sorting by user function
if cat.def.sorting == "function" then
comp = cat.def.sorting_data
-- Alphabetic sorting
elseif cat.def.sorting == "abc" or cat.def.sorting == nil then
comp = function(e1, e2)
if reverse_sort_table[e1.name] < reverse_sort_table[e2.name] then return true else return false end
end
end
table.sort(entry_table, comp)
end
return entry_table
end
function doc.formspec_category(id, playername)
local formstring
if id == nil then
formstring = "label[0,0.5;You haven't chosen a category yet. Please choose one in the category list first.]"
formstring = formstring .. "button[0,1.5;3,1;doc_button_goto_main;Go to category list]"
else
formstring = "label[0,0;Help > "..doc.data.categories[id].def.name.."]"
if doc.get_entry_count(id) >= 1 then
formstring = formstring .. "label[0,0.5;This category has the following entries:]"
formstring = formstring .. doc.generate_entry_list(id, playername)
formstring = formstring .. "button[0,8;3,1;doc_button_goto_entry;Show entry]"
formstring = formstring .. "label[8,8;Number of entries: "..doc.get_entry_count(id).."\n"
formstring = formstring .. "New entries: "..(doc.get_entry_count(id)-doc.get_viewed_count(playername, id)).."]"
else
formstring = formstring .. "label[0,0.5;This category is empty.]"
formstring = formstring .. "button[0,1.5;3,1;doc_button_goto_main;Go to category list]"
end
end
return formstring
end
function doc.formspec_entry_navigation(category_id, entry_id)
if doc.get_entry_count(category_id) < 1 then
return ""
end
local formstring = ""
formstring = formstring .. "button[10,8.5;1,1;doc_button_goto_prev;<]"
formstring = formstring .. "button[11,8.5;1,1;doc_button_goto_next;>]"
formstring = formstring .. "tooltip[doc_button_goto_prev;Show previous entry]"
formstring = formstring .. "tooltip[doc_button_goto_next;Show next entry]"
return formstring
end
function doc.formspec_entry(category_id, entry_id)
local formstring
if category_id == nil then
formstring = "label[0,0;You haven't chosen a category yet. Please choose one in the category list first.]"
formstring = formstring .. "button[0,1;3,1;doc_button_goto_main;Go to category list]"
elseif entry_id == nil then
formstring = "label[0,0;Help > "..doc.data.categories[category_id].def.name.." > (No Entry)]"
if doc.get_entry_count(category_id) >= 1 then
formstring = formstring .. "label[0,0.5;You haven't chosen an entry yet. Please choose one in the entry list first.]"
formstring = formstring .. "button[0,1.5;3,1;doc_button_goto_category;Go to entry list]"
else
formstring = formstring .. "label[0,0.5;This category does not have any entries.]"
formstring = formstring .. "button[0,1.5;3,1;doc_button_goto_main;Go to category list]"
end
else
local category = doc.data.categories[category_id]
local entry = category.entries[entry_id]
-- Check if entry has an alias
if entry == nil then
local resolved_alias = doc.data.categories[category_id].entry_aliases[entry_id]
if resolved_alias ~= nil then
entry = category.entries[resolved_alias]
end
end
formstring = "label[0,0;Help > "..category.def.name.." > "..entry.name.."]"
formstring = formstring .. category.def.build_formspec(entry.data)
formstring = formstring .. doc.formspec_entry_navigation(category_id, entry_id)
end
return formstring
end
function doc.process_form(player,formname,fields)
local playername = player:get_player_name()
--[[ process clicks on the tab header ]]
if(formname == "doc:main" or formname == "doc:category" or formname == "doc:entry") then
if fields.doc_header ~= nil then
local tab = tonumber(fields.doc_header)
local formspec, subformname, contents
local cid, eid
cid = doc.data.players[playername].category
eid = doc.data.players[playername].entry
if(tab==1) then
contents = doc.formspec_main()
subformname = "main"
elseif(tab==2) then
contents = doc.formspec_category(cid, playername)
subformname = "category"
elseif(tab==3) then
contents = doc.formspec_entry(cid, eid)
if cid ~= nil and eid ~= nil then
doc.mark_entry_as_viewed(playername, cid, eid)
end
subformname = "entry"
end
formspec = doc.formspec_core(tab)..contents
minetest.show_formspec(playername, "doc:" .. subformname, formspec)
return
end
end
if(formname == "doc:main") then
for id,_ in pairs(doc.data.categories) do
if fields["doc_button_category_"..id] then
local formspec = doc.formspec_core(2)..doc.formspec_category(id, playername)
doc.data.players[playername].catsel = nil
doc.data.players[playername].category = id
doc.data.players[playername].entry = nil
minetest.show_formspec(playername, "doc:category", formspec)
break
end
end
elseif(formname == "doc:category") then
if fields["doc_button_goto_entry"] then
local cid = doc.data.players[playername].category
if cid ~= nil then
local eid = nil
local eids, catsel = doc.data.players[playername].entry_ids, doc.data.players[playername].catsel
if eids ~= nil and catsel ~= nil then
eid = eids[catsel]
end
local formspec = doc.formspec_core(3)..doc.formspec_entry(cid, eid)
minetest.show_formspec(playername, "doc:entry", formspec)
doc.mark_entry_as_viewed(playername, cid, eid)
end
end
if fields["doc_button_goto_main"] then
local formspec = doc.formspec_core(1)..doc.formspec_main()
minetest.show_formspec(playername, "doc:main", formspec)
end
if fields["doc_catlist"] then
local event = minetest.explode_textlist_event(fields["doc_catlist"])
if event.type == "CHG" then
doc.data.players[playername].catsel = event.index
doc.data.players[playername].entry = doc.data.players[playername].entry_ids[event.index]
elseif event.type == "DCL" then
local cid = doc.data.players[playername].category
local eid = nil
local eids, catsel = doc.data.players[playername].entry_ids, event.index
if eids ~= nil and catsel ~= nil then
eid = eids[catsel]
end
local formspec = doc.formspec_core(3)..doc.formspec_entry(cid, eid)
minetest.show_formspec(playername, "doc:entry", formspec)
doc.mark_entry_as_viewed(playername, cid, eid)
end
end
elseif(formname == "doc:entry") then
if fields["doc_button_goto_main"] then
local formspec = doc.formspec_core(1)..doc.formspec_main()
minetest.show_formspec(playername, "doc:main", formspec)
elseif fields["doc_button_goto_category"] then
local formspec = doc.formspec_core(2)..doc.formspec_category(doc.data.players[playername].category, playername)
minetest.show_formspec(playername, "doc:category", formspec)
elseif fields["doc_button_goto_next"] then
if doc.data.players[playername].catsel == nil then return end -- emergency exit
local eids = doc.data.players[playername].entry_ids
local cid = doc.data.players[playername].category
local new_catsel= doc.data.players[playername].catsel + 1
local new_eid = eids[new_catsel]
if #eids > 1 and new_catsel <= #eids then
local formspec = doc.formspec_core(3)..doc.formspec_entry(cid, new_eid)
minetest.show_formspec(playername, "doc:entry", formspec)
doc.mark_entry_as_viewed(playername, cid, new_eid)
doc.data.players[playername].catsel = new_catsel
doc.data.players[playername].entry = new_eid
end
elseif fields["doc_button_goto_prev"] then
if doc.data.players[playername].catsel == nil then return end -- emergency exit
local eids = doc.data.players[playername].entry_ids
local cid = doc.data.players[playername].category
local new_catsel= doc.data.players[playername].catsel - 1
local new_eid = eids[new_catsel]
if #eids > 1 and new_catsel >= 1 then
local formspec = doc.formspec_core(3)..doc.formspec_entry(cid, new_eid)
minetest.show_formspec(playername, "doc:entry", formspec)
doc.mark_entry_as_viewed(playername, cid, new_eid)
doc.data.players[playername].catsel = new_catsel
doc.data.players[playername].entry = new_eid
end
end
end
end
minetest.register_on_player_receive_fields(doc.process_form)
minetest.register_chatcommand("doc", {
params = "",
description = "Open documentation system.",
privs = {},
func = function(playername, param)
doc.show_doc(playername)
end,
}
)
minetest.register_on_joinplayer(function(player)
local playername = player:get_player_name()
doc.data.players[playername] = {}
-- Table for persistant data
doc.data.players[playername].stored_data = {}
-- Contains viewed entries
doc.data.players[playername].stored_data.viewed = {}
-- Count viewed entries
doc.data.players[playername].stored_data.viewed_count = {}
end)
minetest.register_on_leaveplayer(function(player)
doc.data.players[player:get_player_name()] = nil
end)
---[[ Add buttons for inventory mods ]]
-- Unified Inventory
if minetest.get_modpath("unified_inventory") ~= nil then
unified_inventory.register_button("doc", {
type = "image",
image = "doc_button_icon_hires.png",
tooltip = "Documentation System",
action = function(player)
doc.show_doc(player:get_player_name())
end,
})
end