diff --git a/_data/links_en.yml b/_data/links_en.yml index 579b956..56b6564 100644 --- a/_data/links_en.yml +++ b/_data/links_en.yml @@ -59,24 +59,28 @@ num: 13 link: chapters/hud.html +- title: "SFINV: Inventory Formspec" + num: 14 + link: chapters/sfinv.html + - hr: true - title: ItemStacks - num: 14 + num: 15 link: chapters/itemstacks.html - title: Inventories - num: 15 + num: 16 link: chapters/inventories.html - hr: true - title: Releasing a Mod - num: 16 + num: 17 link: chapters/releasing.html - title: Read More - num: 17 + num: 18 link: chapters/readmore.html - hr: true diff --git a/en/chapters/sfinv.md b/en/chapters/sfinv.md new file mode 100644 index 0000000..54c6a91 --- /dev/null +++ b/en/chapters/sfinv.md @@ -0,0 +1,236 @@ +--- +title: "SFINV: Inventory Formspec" +layout: default +root: ../../ +--- + +## Introduction + +Simple Fast Inventory (SFINV) is a mod found in Minetest Game that is used to +create the player's inventory [formspec](formspecs.html). 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" as +it's entirely possible that a mod or subgame decides to show them in +some other format instead. + +* [Registering a Page](#registering-a-page) + * [A more complex example](#a-more-complex-example) +* [Receiving events](#receiving-events) +* [Conditionally showing to players](#conditionally-showing-to-players) +* [on_enter and on_leave callbacks](#on_enter-and-on_leave-callbacks) + +## Registering a Page + +So, to register a page you need to call the aptly named `sfinv.register_page` +function with the page's name, and its definition. Here is a minimal example: + +{% highlight lua %} +sfinv.register_page("mymod:hello", { + title = "Hello!", + get = function(self, player, context) + -- TODO: implement this + end +}) +{% endhighlight %} + +You can also override an existing page using `sfinv.override_page`. + +If you ran the above code and clicked the page's tab, it would probably crash +as sfinv is expecting a response from the `get` method. So let's add a response +to fix that: + +{% highlight lua %} +sfinv.register_page("mymod:hello", { + title = "Hello!", + get = function(self, player, context) + return sfinv.make_formspec(player, context, + "label[0.1,0.1;Hello world!]", true) + end +}) +{% endhighlight %} + +The `make_formspec` function surrounds your formspec with sfinv's formspec code. +The fourth parameter, currently set as `true`, determines whether or not the +player's inventory is shown. + +
+ Furnace Inventory +
+ Your first sfinv page! Not exactly very exciting, though. +
+
+ +### A more complex example + +Let's make things more exciting. Here is the code for the formspec generation +part of a player admin tab. This tab will allow admins to kick or ban players by +selecting them in a list and clicking a button. + +{% highlight lua %} +sfinv.register_page("myadmin:myadmin", { + title = "Tab", + get = function(self, player, context) + local players = {} + context.myadmin_players = players + + -- Using an array to build a formspec is considerably faster + local formspec = { + "textlist[0.1,0.1;7.8,3;playerlist;" + } + + -- Add all players to the text list, and to the players list + local is_first = true + for _ , player in pairs(minetest.get_connected_players()) do + local player_name = player:get_player_name() + players[#players + 1] = player_name + if not is_first then + formspec[#formspec + 1] = "," + end + formspec[#formspec + 1] = minetest.formspec_escape(player_name) + is_first = false + end + formspec[#formspec + 1] = "]" + + -- Add buttons + formspec[#formspec + 1] = "button[0.1,3.3;2,1;kick;Kick]" + formspec[#formspec + 1] = "button[2.1,3.3;2,1;ban;Kick + Ban]" + + -- Wrap the formspec in sfinv's layout (ie: adds the tabs and background) + return sfinv.make_formspec(player, context, + table.concat(formspec, ""), false) + end, +}) +{% endhighlight %} + +There's nothing new about the above code, all the concepts are covered above and +in previous chapters. + +
+ Player Admin Page +
+ The player admin page created above. +
+
+ +## Receiving events + +You can receive formspec events by adding a `on_player_receive_fields` function +to a sfinv definition. + +{% highlight lua %} +on_player_receive_fields = function(self, player, context, fields) + -- TODO: implement this +end, +{% endhighlight %} + +Fields is the exact same as the fields given to the subscribers of +`minetest.register_on_player_receive_fields`. The return value of +`on_player_receive_fields` is the same as a normal player receive fields. +Please note that sfinv will consume events relevant to itself, such as +navigation tab events, so you won't receive them in this callback. + +Now let's implement the `on_player_receive_fields` for our admin mod: + +{% highlight lua %} +on_player_receive_fields = function(self, player, context, fields) + -- text list event, check event type and set index if selection changed + if fields.playerlist then + local event = minetest.explode_textlist_event(fields.playerlist) + if event.type == "CHG" then + context.myadmin_selected_idx = event.index + end + + -- Kick button was pressed + elseif fields.kick then + local player_name = context.myadmin_players[context.myadmin_selected_idx] + if player_name then + minetest.chat_send_player(player:get_player_name(), + "Kicked " .. player_name) + minetest.kick_player(player_name) + end + + -- Ban button was pressed + elseif fields.ban then + local player_name = context.myadmin_players[context.myadmin_selected_idx] + if player_name then + minetest.chat_send_player(player:get_player_name(), + "Banned " .. player_name) + minetest.ban_player(player_name) + minetest.kick_player(player_name, "Banned") + end + end +end, +{% endhighlight %} + +There's a rather large problem with this, however. Anyone can kick or ban players! You +need a way to only show this to players with the kick or ban privileges. +Luckily SFINV allows you to do this! + +## Conditionally showing to players + +You can add an `is_in_nav` function to your page's definition if you'd like to +control when the page is shown: + +{% highlight lua %} +is_in_nav = function(self, player, context) + local privs = player:get_privs() + return privs.kick or privs.ban +end, +{% endhighlight %} + +Note the the `is_in_nav` is only called when the player's inventory formspec is +generated. This happens when a player joins the game, switches tabs, or a mod +requests it using SFINV's API. + +This means that you need to manually request that SFINV regenerates the inventory +formspec on any events that may change `is_in_nav`'s result. In our case, +we need to do that whenever kick or ban is granted or revoked to a player: + +{% highlight lua %} +local function on_grant_revoke(grantee, granter, priv) + if priv == "kick" or priv == "ban" then + local player = mientest.get_player_by_name(grantee) + if player then + sfinv.set_player_inventory_formspec(player) + end + end +end + +-- Check that the function exists, in order to support older Minetest versions +if minetest.register_on_priv_grant then + minetest.register_on_priv_grant(on_grant_revoke) + minetest.register_on_priv_revoke(on_grant_revoke) +end +{% endhighlight %} + +## on_enter and on_leave callbacks + +You can run code when a player enters (your tab becomes selected) or +leaves (another tab is about to be selected) your tab. + +Please note that you can't cancel these, as it would be a bad user experience +if you could. + +Also note that the inventory may not be visible at the time +these callbacks are called. For example, on_enter is called for the home page +when a player joins the game even before they open their inventory! + +{% highlight lua %} +on_enter = function(self, player, context) + +end, + +on_leave = function(self, player, context) + +end, +{% endhighlight %} + +## Adding to an existing page + +
+

To Do

+ + This section will be added soon™. + This placeholder is just to let you know that it is possible! +
diff --git a/static/sfinv_admin_fs.png b/static/sfinv_admin_fs.png new file mode 100644 index 0000000..3932a94 Binary files /dev/null and b/static/sfinv_admin_fs.png differ diff --git a/static/sfinv_hello_world.png b/static/sfinv_hello_world.png new file mode 100644 index 0000000..d87b02b Binary files /dev/null and b/static/sfinv_hello_world.png differ