api.lua 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256
  1. --[[
  2. Simple Fast Inventory (SFInv) is a mod found in Minetest Game that is used to
  3. create the player's inventory formspec. SFInv comes with an API that allows you
  4. to add and otherwise manage the pages shown.
  5. Whilst SFInv by default shows pages as tabs, pages are called pages because it
  6. is entirely possible that a mod or game decides to show them in some other
  7. format instead. For example, multiple pages could be shown in one formspec.
  8. ]]
  9. ---@class SFInv
  10. ---@field pages table Table of `pages[pagename] = def`.
  11. ---@field pages_unordered table[] Array table of pages in order of addition (used to build navigation tabs).
  12. ---@field contexts table Table of `contexts[playername] = player_context`.
  13. ---@field enabled boolean Set to false to disable. Good for inventory rehaul mods like unified inventory.
  14. sfinv = {
  15. pages = {},
  16. pages_unordered = {},
  17. contexts = {},
  18. enabled = true,
  19. }
  20. ---@alias SFInvFields string[] A list of page names.
  21. ---@class SFInvContext
  22. ---@field page string Current page name.
  23. ---@field nav SFInvFields List of page names.
  24. ---@field nav_titles string[] List of page titles.
  25. ---@field nav_idx integer Current nav index (in nav and nav_titles).
  26. -- SFInv page definition.
  27. ---@class SFInvDef
  28. ---@field name string|nil Technical page name.
  29. ---@field title string Human readable page name.
  30. ---@field get fun(self:SFInv, player:mt.PlayerObjectRef, context:SFInvContext): string Returns a formspec string.
  31. ---@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).
  32. ---@field on_player_receive_fields nil|fun(self:SFInv, player:mt.PlayerObjectRef, context:SFInvContext, fields: SFInvFields) On formspec submit.
  33. ---@field on_enter nil|fun(self:SFInv, player:mt.PlayerObjectRef, contextcontext:SFInvContext) Called when the player changes pages, usually using the tabs.
  34. ---@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.
  35. -- Register a page
  36. ---@param name string
  37. ---@param def SFInvDef
  38. function sfinv.register_page(name, def)
  39. assert(name, "Invalid sfinv page. Requires a name")
  40. assert(def, "Invalid sfinv page. Requires a def[inition] table")
  41. assert(def.get, "Invalid sfinv page. Def requires a get function.")
  42. assert(
  43. not sfinv.pages[name],
  44. "Attempt to register already registered sfinv page " .. dump(name)
  45. )
  46. sfinv.pages[name] = def
  47. def.name = name
  48. table.insert(sfinv.pages_unordered, def)
  49. end
  50. -- Overrides fields of an page registered with register_page.
  51. ---@param name string
  52. ---@param def SFInvDef
  53. function sfinv.override_page(name, def)
  54. assert(name, "Invalid sfinv page override. Requires a name")
  55. assert(def, "Invalid sfinv page override. Requires a def[inition] table")
  56. local page = sfinv.pages[name]
  57. assert(
  58. page,
  59. "Attempt to override sfinv page " .. dump(name) .. " which does not exist."
  60. )
  61. for key, value in pairs(def) do
  62. page[key] = value
  63. end
  64. end
  65. -- Creates tabheader or "".
  66. ---@param player mt.PlayerObjectRef
  67. ---@param context SFInvContext
  68. ---@param nav SFInvFields A list of page names.
  69. ---@param current_idx integer Current nav index (in nav and nav_titles).
  70. ---@return string formspec
  71. ---@diagnostic disable-next-line: duplicate-set-field
  72. function sfinv.get_nav_fs(player, context, nav, current_idx)
  73. -- Only show tabs if there is more than one page.
  74. if #nav > 1 then
  75. return "tabheader[0,0;sfinv_nav_tabs;"
  76. .. table.concat(nav, ",")
  77. .. ";"
  78. .. current_idx
  79. .. ";true;false]"
  80. else
  81. return ""
  82. end
  83. end
  84. local theme_inv = [[
  85. list[current_player;main;0,5.2;8,1;]
  86. list[current_player;main;0,6.35;8,3;8]
  87. ]]
  88. -- Adds a theme to a formspec.
  89. ---@param player mt.PlayerObjectRef
  90. ---@param context SFInvContext
  91. ---@param content string Formspec.
  92. ---@param show_inv boolean|nil
  93. ---@param size string|nil
  94. ---@return string formspec
  95. ---@diagnostic disable-next-line: duplicate-set-field
  96. function sfinv.make_formspec(player, context, content, show_inv, size)
  97. local tmp = {
  98. size or "size[8,9.1]",
  99. sfinv.get_nav_fs(player, context, context.nav_titles, context.nav_idx),
  100. show_inv and theme_inv or "",
  101. content,
  102. }
  103. return table.concat(tmp, "")
  104. end
  105. -- Get the page name of the first page to show to a player.
  106. ---@param player mt.PlayerObjectRef
  107. ---@return string
  108. function sfinv.get_homepage_name(player) return "sfinv:crafting" end
  109. -- Builds current page's formspec.
  110. ---@param player mt.PlayerObjectRef
  111. ---@param context SFInvContext
  112. ---@return string
  113. function sfinv.get_formspec(player, context)
  114. -- Generate navigation tabs
  115. local nav = {}
  116. local nav_ids = {}
  117. local current_idx = 1
  118. for i, pdef in pairs(sfinv.pages_unordered) do
  119. if not pdef.is_in_nav or pdef:is_in_nav(player, context) then
  120. nav[#nav + 1] = pdef.title
  121. nav_ids[#nav_ids + 1] = pdef.name
  122. if pdef.name == context.page then current_idx = #nav_ids end
  123. end
  124. end
  125. context.nav = nav_ids
  126. context.nav_titles = nav
  127. context.nav_idx = current_idx
  128. -- Generate formspec
  129. local page = sfinv.pages[context.page] or sfinv.pages["404"]
  130. if page then
  131. return page:get(player, context)
  132. else
  133. local old_page = context.page
  134. local home_page = sfinv.get_homepage_name(player)
  135. if old_page == home_page then
  136. minetest.log(
  137. "error",
  138. "[sfinv] Couldn't find "
  139. .. dump(old_page)
  140. .. ", which is also the old page"
  141. )
  142. return ""
  143. end
  144. context.page = home_page
  145. assert(sfinv.pages[context.page], "[sfinv] Invalid homepage")
  146. minetest.log(
  147. "warning",
  148. "[sfinv] Couldn't find " .. dump(old_page) .. " so switching to homepage"
  149. )
  150. return sfinv.get_formspec(player, context)
  151. end
  152. end
  153. -- Gets the player's context.
  154. ---@param player mt.PlayerObjectRef
  155. ---@return SFInvContext
  156. function sfinv.get_or_create_context(player)
  157. local name = player:get_player_name()
  158. local context = sfinv.contexts[name]
  159. if not context then
  160. context = {
  161. page = sfinv.get_homepage_name(player),
  162. }
  163. sfinv.contexts[name] = context
  164. end
  165. return context
  166. end
  167. ---@param player mt.PlayerObjectRef
  168. ---@param context SFInvContext
  169. function sfinv.set_context(player, context)
  170. sfinv.contexts[player:get_player_name()] = context
  171. end
  172. -- (Re)builds page formspec and calls `set_inventory_formspec()`.
  173. ---@param player mt.PlayerObjectRef
  174. ---@param context SFInvContext|nil
  175. function sfinv.set_player_inventory_formspec(player, context)
  176. local fs =
  177. sfinv.get_formspec(player, context or sfinv.get_or_create_context(player))
  178. player:set_inventory_formspec(fs)
  179. end
  180. -- Changes the page.
  181. ---@param player mt.PlayerObjectRef
  182. ---@param pagename string
  183. function sfinv.set_page(player, pagename)
  184. local context = sfinv.get_or_create_context(player)
  185. local oldpage = sfinv.pages[context.page]
  186. if oldpage and oldpage.on_leave then oldpage:on_leave(player, context) end
  187. context.page = pagename
  188. local page = sfinv.pages[pagename]
  189. if page.on_enter then page:on_enter(player, context) end
  190. sfinv.set_player_inventory_formspec(player, context)
  191. end
  192. ---@param player mt.PlayerObjectRef
  193. ---@return string
  194. function sfinv.get_page(player)
  195. local context = sfinv.contexts[player:get_player_name()] ---@type SFInvContext
  196. return context and context.page or sfinv.get_homepage_name(player)
  197. end
  198. minetest.register_on_joinplayer(function(player)
  199. if sfinv.enabled then sfinv.set_player_inventory_formspec(player) end
  200. end)
  201. minetest.register_on_leaveplayer(
  202. function(player) sfinv.contexts[player:get_player_name()] = nil end
  203. )
  204. minetest.register_on_player_receive_fields(function(player, formname, fields)
  205. if formname ~= "" or not sfinv.enabled then return false end
  206. -- Get Context
  207. local name = player:get_player_name()
  208. local context = sfinv.contexts[name]
  209. if not context then
  210. sfinv.set_player_inventory_formspec(player)
  211. return false
  212. end
  213. -- Was a tab selected?
  214. if fields.sfinv_nav_tabs and context.nav then
  215. local tid = tonumber(fields.sfinv_nav_tabs)
  216. if tid and tid > 0 then
  217. local id = context.nav[tid]
  218. local page = sfinv.pages[id]
  219. if id and page then sfinv.set_page(player, id) end
  220. end
  221. else
  222. -- Pass event to page
  223. local page = sfinv.pages[context.page]
  224. if page and page.on_player_receive_fields then
  225. return page:on_player_receive_fields(player, context, fields)
  226. end
  227. end
  228. end)