From 17473de7f456af1752362a551c25daa9f63cdfe6 Mon Sep 17 00:00:00 2001 From: rubenwardy Date: Tue, 29 Aug 2017 00:21:27 +0100 Subject: [PATCH] SFINV: Create chapter --- _data/links_en.yml | 12 +- en/chapters/sfinv.md | 236 +++++++++++++++++++++++++++++++++++ static/sfinv_admin_fs.png | Bin 0 -> 8921 bytes static/sfinv_hello_world.png | Bin 0 -> 13226 bytes 4 files changed, 244 insertions(+), 4 deletions(-) create mode 100644 en/chapters/sfinv.md create mode 100644 static/sfinv_admin_fs.png create mode 100644 static/sfinv_hello_world.png 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 0000000000000000000000000000000000000000..3932a94a1b0a89fac6cfc75a9582ec897a6aad18 GIT binary patch literal 8921 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≺>trDgN1dE<4ahOWyMpTNx4dxPSl#Me<%!kNLs*6nzbTf>3GV(YE|s`TA9{ zKKP@AL@nJePoIQ@j<^%AT6%M|$-wA>!})>(qp0y)qpv8s449P~b)N(3`!#23DyP{&*q_ zzZT{@C0k1JrY9v2l(2_dPjw|a`>0?+|`-paNoe4_%>#x>+Svl}udYo=ed7$Dh?HH7_M>IhGCLkcdUm5Vf z866!BHYA^SzI+{_DJJ_X+=n9)&t8G+Pmx#gs>xeV*_t9{cDD`m8I*@^QK^%^!5R z)?(oX(8S;;XUiyhXAGsiQZCh&bjzVbZUTbcgcwyL`*cz4xa_>_v|KqZXPGAc^rTDT&WS_Efxual z-*t{>>sGh*e~Lc7jlgD?@1$LaTEBq49PPgyW2qi8VaDBAY?#>~8F>{l9-lkevN(2A z6HfM$r~V!J*s=aq8PXcj4Z+x@K17+2@n?kML%WA`Ms!6iFP6#}3-`{-=tk%H#%KdS zzqU3aBYRW&gWrh5RHk)yS{8Qq4pe<@MknrwTj-15*3tOZ;tXp(uf5(w*-h_T_Bt9A z(C$W50{Nejy5-dN?~g~sC^Fxl>``;PE<;w*UAzdt-c|+~GKx@>p zBxLj%A=48EQ?%w}oXfgJ0kxf)%hkJ6wAht!bELmFGT&-Pr!I{Oxi?2)ot^Ezj=SdY z)=+6m&_}K$f4gWtIcBxb$^@e#bhx;~%6GR?PRQ<;a67TXxc1bZ|Hehy2A5b0KPh}W zxFhep_s>laD(5o=I*R^R7-Y4U&48MU;P<6&{aKp|>!mjj?S@lWvbyS-mCQs&%g|J# z2~k3b2a&UEoyMlZ$;TRNyz%0ra{2vBHqzq?#u;wLIzMdIxfx#jI2~P(RA<8@=ye&v Qf2sjq$i0@$lQ!`BACpcpegFUf literal 0 HcmV?d00001