Sfinv: Remove possibility of infinite loop when homepage doesn't exist
[oweals/minetest_game.git] / mods / sfinv / api.lua
1 sfinv = {
2         pages = {},
3         pages_unordered = {},
4         contexts = {},
5         enabled = true
6 }
7
8 function sfinv.register_page(name, def)
9         assert(name, "Invalid sfinv page. Requires a name")
10         assert(def, "Invalid sfinv page. Requires a def[inition] table")
11         assert(def.get, "Invalid sfinv page. Def requires a get function.")
12         assert(not sfinv.pages[name], "Attempt to register already registered sfinv page " .. dump(name))
13
14         sfinv.pages[name] = def
15         def.name = name
16         table.insert(sfinv.pages_unordered, def)
17 end
18
19 function sfinv.override_page(name, def)
20         assert(name, "Invalid sfinv page override. Requires a name")
21         assert(def, "Invalid sfinv page override. Requires a def[inition] table")
22         local page = sfinv.pages[name]
23         assert(page, "Attempt to override sfinv page " .. dump(name) .. " which does not exist.")
24         for key, value in pairs(def) do
25                 page[key] = value
26         end
27 end
28
29 function sfinv.get_nav_fs(player, context, nav, current_idx)
30         -- Only show tabs if there is more than one page
31         if #nav > 1 then
32                 return "tabheader[0,0;sfinv_nav_tabs;" .. table.concat(nav, ",") ..
33                                 ";" .. current_idx .. ";true;false]"
34         else
35                 return ""
36         end
37 end
38
39 local theme_main = "bgcolor[#080808BB;true]" .. default.gui_bg ..
40                 default.gui_bg_img
41
42 local theme_inv = default.gui_slots .. [[
43                 list[current_player;main;0,4.7;8,1;]
44                 list[current_player;main;0,5.85;8,3;8]
45         ]]
46
47 function sfinv.make_formspec(player, context, content, show_inv, size)
48         local tmp = {
49                 size or "size[8,8.6]",
50                 theme_main,
51                 sfinv.get_nav_fs(player, context, context.nav_titles, context.nav_idx),
52                 content
53         }
54         if show_inv then
55                 tmp[#tmp + 1] = theme_inv
56         end
57         return table.concat(tmp, "")
58 end
59
60 function sfinv.get_homepage_name(player)
61         return "sfinv:crafting"
62 end
63
64 function sfinv.get_formspec(player, context)
65         -- Generate navigation tabs
66         local nav = {}
67         local nav_ids = {}
68         local current_idx = 1
69         for i, pdef in pairs(sfinv.pages_unordered) do
70                 if not pdef.is_in_nav or pdef:is_in_nav(player, context) then
71                         nav[#nav + 1] = pdef.title
72                         nav_ids[#nav_ids + 1] = pdef.name
73                         if pdef.name == context.page then
74                                 current_idx = #nav_ids
75                         end
76                 end
77         end
78         context.nav = nav_ids
79         context.nav_titles = nav
80         context.nav_idx = current_idx
81
82         -- Generate formspec
83         local page = sfinv.pages[context.page] or sfinv.pages["404"]
84         if page then
85                 return page:get(player, context)
86         else
87                 local old_page = context.page
88                 local home_page = sfinv.get_homepage_name(player)
89
90                 if old_page == home_page then
91                         minetest.log("error", "[sfinv] Couldn't find " .. dump(old_page) ..
92                                         ", which is also the old page")
93
94                         return ""
95                 end
96
97                 context.page = home_page
98                 assert(sfinv.pages[context.page], "[sfinv] Invalid homepage")
99                 minetest.log("warning", "[sfinv] Couldn't find " .. dump(old_page) ..
100                                 " so switching to homepage")
101
102                 return sfinv.get_formspec(player, context)
103         end
104 end
105
106 function sfinv.get_or_create_context(player)
107         local name = player:get_player_name()
108         local context = sfinv.contexts[name]
109         if not context then
110                 context = {
111                         page = sfinv.get_homepage_name(player)
112                 }
113                 sfinv.contexts[name] = context
114         end
115         return context
116 end
117
118 function sfinv.set_context(player, context)
119         sfinv.contexts[player:get_player_name()] = context
120 end
121
122 function sfinv.set_player_inventory_formspec(player, context)
123         local fs = sfinv.get_formspec(player,
124                         context or sfinv.get_or_create_context(player))
125         player:set_inventory_formspec(fs)
126 end
127
128 function sfinv.set_page(player, pagename)
129         local context = sfinv.get_or_create_context(player)
130         local oldpage = sfinv.pages[context.page]
131         if oldpage and oldpage.on_leave then
132                 oldpage:on_leave(player, context)
133         end
134         context.page = pagename
135         local page = sfinv.pages[pagename]
136         if page.on_enter then
137                 page:on_enter(player, context)
138         end
139         sfinv.set_player_inventory_formspec(player, context)
140 end
141
142 minetest.register_on_joinplayer(function(player)
143         if sfinv.enabled then
144                 sfinv.set_player_inventory_formspec(player)
145         end
146 end)
147
148 minetest.register_on_leaveplayer(function(player)
149         sfinv.contexts[player:get_player_name()] = nil
150 end)
151
152 minetest.register_on_player_receive_fields(function(player, formname, fields)
153         if formname ~= "" or not sfinv.enabled then
154                 return false
155         end
156
157         -- Get Context
158         local name = player:get_player_name()
159         local context = sfinv.contexts[name]
160         if not context then
161                 sfinv.set_player_inventory_formspec(player)
162                 return false
163         end
164
165         -- Was a tab selected?
166         if fields.sfinv_nav_tabs and context.nav then
167                 local tid = tonumber(fields.sfinv_nav_tabs)
168                 if tid and tid > 0 then
169                         local id = context.nav[tid]
170                         local page = sfinv.pages[id]
171                         if id and page then
172                                 sfinv.set_page(player, id)
173                         end
174                 end
175         else
176                 -- Pass event to page
177                 local page = sfinv.pages[context.page]
178                 if page and page.on_player_receive_fields then
179                         return page:on_player_receive_fields(player, context, fields)
180                 end
181         end
182 end)