123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256 |
- --[[
- Simple Fast Inventory (SFInv) is a mod found in Minetest Game that is used to
- create the player's inventory formspec. SFInv comes with an API that allows you
- to add and otherwise manage the pages shown.
- Whilst SFInv by default shows pages as tabs, pages are called pages because it
- is entirely possible that a mod or game decides to show them in some other
- format instead. For example, multiple pages could be shown in one formspec.
- ]]
- ---@class SFInv
- ---@field pages table Table of `pages[pagename] = def`.
- ---@field pages_unordered table[] Array table of pages in order of addition (used to build navigation tabs).
- ---@field contexts table Table of `contexts[playername] = player_context`.
- ---@field enabled boolean Set to false to disable. Good for inventory rehaul mods like unified inventory.
- sfinv = {
- pages = {},
- pages_unordered = {},
- contexts = {},
- enabled = true,
- }
- ---@alias SFInvFields string[] A list of page names.
- ---@class SFInvContext
- ---@field page string Current page name.
- ---@field nav SFInvFields List of page names.
- ---@field nav_titles string[] List of page titles.
- ---@field nav_idx integer Current nav index (in nav and nav_titles).
- -- SFInv page definition.
- ---@class SFInvDef
- ---@field name string|nil Technical page name.
- ---@field title string Human readable page name.
- ---@field get fun(self:SFInv, player:mt.PlayerObjectRef, context:SFInvContext): string Returns a formspec string.
- ---@field is_in_nav nil|fun(self:SFInv, player:mt.PlayerObjectRef, context:SFInvContext): boolean Returns true to show in the navigation (the tab header, by default).
- ---@field on_player_receive_fields nil|fun(self:SFInv, player:mt.PlayerObjectRef, context:SFInvContext, fields: SFInvFields) On formspec submit.
- ---@field on_enter nil|fun(self:SFInv, player:mt.PlayerObjectRef, contextcontext:SFInvContext) Called when the player changes pages, usually using the tabs.
- ---@field on_leave nil|fun(self:SFInv, player:mt.PlayerObjectRef, context:SFInvContext) When leaving this page to go to another, called before other's on_enter.
- -- Register a page
- ---@param name string
- ---@param def SFInvDef
- function sfinv.register_page(name, def)
- assert(name, "Invalid sfinv page. Requires a name")
- assert(def, "Invalid sfinv page. Requires a def[inition] table")
- assert(def.get, "Invalid sfinv page. Def requires a get function.")
- assert(
- not sfinv.pages[name],
- "Attempt to register already registered sfinv page " .. dump(name)
- )
- sfinv.pages[name] = def
- def.name = name
- table.insert(sfinv.pages_unordered, def)
- end
- -- Overrides fields of an page registered with register_page.
- ---@param name string
- ---@param def SFInvDef
- function sfinv.override_page(name, def)
- assert(name, "Invalid sfinv page override. Requires a name")
- assert(def, "Invalid sfinv page override. Requires a def[inition] table")
- local page = sfinv.pages[name]
- assert(
- page,
- "Attempt to override sfinv page " .. dump(name) .. " which does not exist."
- )
- for key, value in pairs(def) do
- page[key] = value
- end
- end
- -- Creates tabheader or "".
- ---@param player mt.PlayerObjectRef
- ---@param context SFInvContext
- ---@param nav SFInvFields A list of page names.
- ---@param current_idx integer Current nav index (in nav and nav_titles).
- ---@return string formspec
- ---@diagnostic disable-next-line: duplicate-set-field
- function sfinv.get_nav_fs(player, context, nav, current_idx)
- -- Only show tabs if there is more than one page.
- if #nav > 1 then
- return "tabheader[0,0;sfinv_nav_tabs;"
- .. table.concat(nav, ",")
- .. ";"
- .. current_idx
- .. ";true;false]"
- else
- return ""
- end
- end
- local theme_inv = [[
- list[current_player;main;0,5.2;8,1;]
- list[current_player;main;0,6.35;8,3;8]
- ]]
- -- Adds a theme to a formspec.
- ---@param player mt.PlayerObjectRef
- ---@param context SFInvContext
- ---@param content string Formspec.
- ---@param show_inv boolean|nil
- ---@param size string|nil
- ---@return string formspec
- ---@diagnostic disable-next-line: duplicate-set-field
- function sfinv.make_formspec(player, context, content, show_inv, size)
- local tmp = {
- size or "size[8,9.1]",
- sfinv.get_nav_fs(player, context, context.nav_titles, context.nav_idx),
- show_inv and theme_inv or "",
- content,
- }
- return table.concat(tmp, "")
- end
- -- Get the page name of the first page to show to a player.
- ---@param player mt.PlayerObjectRef
- ---@return string
- function sfinv.get_homepage_name(player) return "sfinv:crafting" end
- -- Builds current page's formspec.
- ---@param player mt.PlayerObjectRef
- ---@param context SFInvContext
- ---@return string
- function sfinv.get_formspec(player, context)
- -- Generate navigation tabs
- local nav = {}
- local nav_ids = {}
- local current_idx = 1
- for i, pdef in pairs(sfinv.pages_unordered) do
- if not pdef.is_in_nav or pdef:is_in_nav(player, context) then
- nav[#nav + 1] = pdef.title
- nav_ids[#nav_ids + 1] = pdef.name
- if pdef.name == context.page then current_idx = #nav_ids end
- end
- end
- context.nav = nav_ids
- context.nav_titles = nav
- context.nav_idx = current_idx
- -- Generate formspec
- local page = sfinv.pages[context.page] or sfinv.pages["404"]
- if page then
- return page:get(player, context)
- else
- local old_page = context.page
- local home_page = sfinv.get_homepage_name(player)
- if old_page == home_page then
- minetest.log(
- "error",
- "[sfinv] Couldn't find "
- .. dump(old_page)
- .. ", which is also the old page"
- )
- return ""
- end
- context.page = home_page
- assert(sfinv.pages[context.page], "[sfinv] Invalid homepage")
- minetest.log(
- "warning",
- "[sfinv] Couldn't find " .. dump(old_page) .. " so switching to homepage"
- )
- return sfinv.get_formspec(player, context)
- end
- end
- -- Gets the player's context.
- ---@param player mt.PlayerObjectRef
- ---@return SFInvContext
- function sfinv.get_or_create_context(player)
- local name = player:get_player_name()
- local context = sfinv.contexts[name]
- if not context then
- context = {
- page = sfinv.get_homepage_name(player),
- }
- sfinv.contexts[name] = context
- end
- return context
- end
- ---@param player mt.PlayerObjectRef
- ---@param context SFInvContext
- function sfinv.set_context(player, context)
- sfinv.contexts[player:get_player_name()] = context
- end
- -- (Re)builds page formspec and calls `set_inventory_formspec()`.
- ---@param player mt.PlayerObjectRef
- ---@param context SFInvContext|nil
- function sfinv.set_player_inventory_formspec(player, context)
- local fs =
- sfinv.get_formspec(player, context or sfinv.get_or_create_context(player))
- player:set_inventory_formspec(fs)
- end
- -- Changes the page.
- ---@param player mt.PlayerObjectRef
- ---@param pagename string
- function sfinv.set_page(player, pagename)
- local context = sfinv.get_or_create_context(player)
- local oldpage = sfinv.pages[context.page]
- if oldpage and oldpage.on_leave then oldpage:on_leave(player, context) end
- context.page = pagename
- local page = sfinv.pages[pagename]
- if page.on_enter then page:on_enter(player, context) end
- sfinv.set_player_inventory_formspec(player, context)
- end
- ---@param player mt.PlayerObjectRef
- ---@return string
- function sfinv.get_page(player)
- local context = sfinv.contexts[player:get_player_name()] ---@type SFInvContext
- return context and context.page or sfinv.get_homepage_name(player)
- end
- minetest.register_on_joinplayer(function(player)
- if sfinv.enabled then sfinv.set_player_inventory_formspec(player) end
- end)
- minetest.register_on_leaveplayer(
- function(player) sfinv.contexts[player:get_player_name()] = nil end
- )
- minetest.register_on_player_receive_fields(function(player, formname, fields)
- if formname ~= "" or not sfinv.enabled then return false end
- -- Get Context
- local name = player:get_player_name()
- local context = sfinv.contexts[name]
- if not context then
- sfinv.set_player_inventory_formspec(player)
- return false
- end
- -- Was a tab selected?
- if fields.sfinv_nav_tabs and context.nav then
- local tid = tonumber(fields.sfinv_nav_tabs)
- if tid and tid > 0 then
- local id = context.nav[tid]
- local page = sfinv.pages[id]
- if id and page then sfinv.set_page(player, id) end
- end
- else
- -- Pass event to page
- local page = sfinv.pages[context.page]
- if page and page.on_player_receive_fields then
- return page:on_player_receive_fields(player, context, fields)
- end
- end
- end)
|