minetest_modding_book/_en/players/sfinv.md

243 lines
7.6 KiB
Markdown
Raw Permalink Normal View History

2017-08-29 02:21:27 +03:00
---
title: "SFINV: Inventory Formspec"
layout: default
2018-07-15 21:36:35 +03:00
root: ../..
2018-07-15 17:28:10 +03:00
idx: 4.7
2018-07-15 21:13:16 +03:00
redirect_from: /en/chapters/sfinv.html
2017-08-29 02:21:27 +03:00
---
## Introduction <!-- omit in toc -->
2017-08-29 02:21:27 +03:00
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.
2018-10-27 05:10:37 +03:00
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
2017-08-29 02:21:27 +03:00
some other format instead.
2018-10-27 05:10:37 +03:00
For example, multiple pages could be shown in one formspec.
2017-08-29 02:21:27 +03:00
- [Registering a Page](#registering-a-page)
- [Receiving events](#receiving-events)
- [Conditionally showing to players](#conditionally-showing-to-players)
- [on_enter and on_leave callbacks](#onenter-and-onleave-callbacks)
- [Adding to an existing page](#adding-to-an-existing-page)
2017-08-29 02:21:27 +03:00
## Registering a Page
2018-09-27 18:31:06 +03:00
SFINV provides the aptly named `sfinv.register_page` function to create pages.
2018-10-27 05:10:37 +03:00
Simply call the function with the page's name and its definition:
2017-08-29 02:21:27 +03:00
```lua
2017-08-29 02:21:27 +03:00
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
})
```
2017-08-29 02:21:27 +03:00
2018-09-27 18:31:06 +03:00
The `make_formspec` function surrounds your formspec with SFINV's formspec code.
The fourth parameter, currently set as `true`, determines whether the
2017-08-29 02:21:27 +03:00
player's inventory is shown.
2018-10-27 05:10:37 +03:00
Let's make things more exciting; here is the code for the formspec generation
2017-08-29 02:21:27 +03:00
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.
```lua
2017-08-29 02:21:27 +03:00
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
2018-09-24 19:16:00 +03:00
formspec[#formspec + 1] =
minetest.formspec_escape(player_name)
2017-08-29 02:21:27 +03:00
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]"
2018-09-24 19:16:00 +03:00
-- Wrap the formspec in sfinv's layout
-- (ie: adds the tabs and background)
2017-08-29 02:21:27 +03:00
return sfinv.make_formspec(player, context,
table.concat(formspec, ""), false)
end,
})
```
2017-08-29 02:21:27 +03:00
2018-10-27 05:10:37 +03:00
There's nothing new about the above code; all the concepts are
covered above and in previous chapters.
2017-08-29 02:21:27 +03:00
<figure>
2018-07-15 21:36:35 +03:00
<img src="{{ page.root }}//static/sfinv_admin_fs.png" alt="Player Admin Page">
2017-08-29 02:21:27 +03:00
</figure>
## Receiving events
You can receive formspec events by adding a `on_player_receive_fields` function
to a sfinv definition.
```lua
2017-08-29 02:21:27 +03:00
on_player_receive_fields = function(self, player, context, fields)
-- TODO: implement this
end,
```
2017-08-29 02:21:27 +03:00
2018-10-27 05:10:37 +03:00
`on_player_receive_fields` works the same as
`minetest.register_on_player_receive_fields`, except that `context` is
given instead of `formname`.
Please note that SFINV will consume events relevant to itself, such as
2017-08-29 02:21:27 +03:00
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:
```lua
2017-08-29 02:21:27 +03:00
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
2018-09-24 19:16:00 +03:00
local player_name =
context.myadmin_players[context.myadmin_selected_idx]
2017-08-29 02:21:27 +03:00
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
2018-09-24 19:16:00 +03:00
local player_name =
context.myadmin_players[context.myadmin_selected_idx]
2017-08-29 02:21:27 +03:00
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,
```
2017-08-29 02:21:27 +03:00
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:
```lua
2017-08-29 02:21:27 +03:00
is_in_nav = function(self, player, context)
2017-10-12 14:23:35 +03:00
local privs = minetest.get_player_privs(player:get_player_name())
2017-08-29 02:21:27 +03:00
return privs.kick or privs.ban
end,
```
2017-08-29 02:21:27 +03:00
2018-09-27 18:31:06 +03:00
If you only need to check one priv or want to perform an 'and', you should use
2017-10-12 14:23:35 +03:00
`minetest.check_player_privs()` instead of `get_player_privs`.
Note that the `is_in_nav` is only called when the player's inventory formspec is
2017-08-29 02:21:27 +03:00
generated. This happens when a player joins the game, switches tabs, or a mod
2018-10-27 05:10:37 +03:00
requests for SFINV to regenerate.
2017-08-29 02:21:27 +03:00
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:
```lua
2017-08-29 02:21:27 +03:00
local function on_grant_revoke(grantee, granter, priv)
2018-09-27 18:31:06 +03:00
if priv ~= "kick" and priv ~= "ban" then
return
end
local player = minetest.get_player_by_name(grantee)
if not player then
return
end
local context = sfinv.get_or_create_context(player)
if context.page ~= "myadmin:myadmin" then
return
2017-08-29 02:21:27 +03:00
end
2018-09-27 18:31:06 +03:00
sfinv.set_player_inventory_formspec(player, context)
2017-08-29 02:21:27 +03:00
end
2018-09-27 18:31:06 +03:00
minetest.register_on_priv_grant(on_grant_revoke)
minetest.register_on_priv_revoke(on_grant_revoke)
```
2017-08-29 02:21:27 +03:00
## on_enter and on_leave callbacks
A player *enters* a tab when the tab is selected and *leaves* a
2018-09-27 18:31:06 +03:00
tab when another tab is about to be selected.
It's possible for multiple pages to be selected if a custom theme is
used.
2017-08-29 02:21:27 +03:00
2018-09-27 18:31:06 +03:00
Note that these events may not be triggered by the player.
The player may not even have the formspec open at that time.
For example, on_enter is called for the home page when a player
joins the game even before they open their inventory.
2017-08-29 02:21:27 +03:00
2018-09-27 18:31:06 +03:00
It's not possible to cancel a page change, as that would potentially
confuse the player.
2017-08-29 02:21:27 +03:00
```lua
2017-08-29 02:21:27 +03:00
on_enter = function(self, player, context)
end,
on_leave = function(self, player, context)
end,
```
2017-08-29 02:21:27 +03:00
## Adding to an existing page
2018-09-27 18:31:06 +03:00
To add content to an existing page, you will need to override the page
and modify the returned formspec.
```lua
local old_func = sfinv.registered_pages["sfinv:crafting"].get
sfinv.override_page("sfinv:crafting", {
get = function(self, player, context, ...)
local ret = old_func(self, player, context, ...)
if type(ret) == "table" then
ret.formspec = ret.formspec .. "label[0,0;Hello]"
else
-- Backwards compatibility
ret = ret .. "label[0,0;Hello]"
end
return ret
end
})
```