+
## Introduction
diff --git a/_en/players/formspecs.md b/_en/players/formspecs.md
index b670db8..cce823a 100644
--- a/_en/players/formspecs.md
+++ b/_en/players/formspecs.md
@@ -4,6 +4,13 @@ layout: default
root: ../..
idx: 4.5
redirect_from: /en/chapters/formspecs.html
+minetest510:
+ level: warning
+ title: Real coordinates will be in 5.1.0
+ classes: web-only
+ message: This chapter describes the use of a feature that hasn't been released yet.
+ You can still use this chapter and the code in Minetest 5.0, but elements will
+ be positioned differently to what is shown.
submit_vuln:
level: warning
title: Malicious clients can submit anything at anytime
@@ -24,250 +31,302 @@ submit_vuln:
In this chapter we will learn how to create a formspec and display it to the user.
A formspec is the specification code for a form.
-In Minetest, forms are windows such as the player inventory, which can contain labels,
-buttons and fields to allow you to enter information.
-
-- [Formspec Syntax](#formspec-syntax)
- - [Size[w, h]](#sizew-h)
- - [Field[x, y; w, h; name; label; default]](#fieldx-y-w-h-name-label-default)
- - [Other Elements](#other-elements)
-- [Displaying Formspecs](#displaying-formspecs)
- - [Example](#example)
-- [Callbacks](#callbacks)
- - [Fields](#fields)
-- [Contexts](#contexts)
-- [Node Meta Formspecs](#node-meta-formspecs)
+In Minetest, forms are windows such as the player inventory and can contain a
+variety of elements, such as labels, buttons and fields.
Note that if you do not need to get user input, for example when you only need
-to provide information to the player, you should consider using Heads Up Display
-(HUD) elements instead of forms, because unexpected windows tend to disrupt gameplay.
+to provide information to the player, you should consider using
+[Heads Up Display (HUD)](hud.html) elements instead of forms, because
+unexpected windows tend to disrupt gameplay.
-## Formspec Syntax
+- [Real or Legacy Coordinates](#real-or-legacy-coordinates)
+- [Anatomy of a Formspec](#anatomy-of-a-formspec)
+ - [Elements](#elements)
+ - [Header](#header)
+- [Guessing Game](#guessing-game)
+ - [Padding and Spacing](#padding-and-spacing)
+ - [Receiving Formspec Submissions](#receiving-formspec-submissions)
+ - [Contexts](#contexts)
+- [Formspec Sources](#formspec-sources)
+ - [Node Meta Formspecs](#node-meta-formspecs)
+ - [Player Inventory Formspecs](#player-inventory-formspecs)
+ - [Your Turn](#your-turn)
-Formspecs have an unusual syntax.
-They consist of a series of tags which are in the following form:
- element_type[param1;param2;...]
+## Real or Legacy Coordinates
-Firstly the element type is declared, and then the attributes are given
-in square brackets.
+In older versions of Minetest, formspecs were inconsistent. The way that different
+elements were positioned varied in unexpected ways; it was hard to predict the
+placement of elements and align them. Minetest 5.1.0 contains a feature
+called real coordinates which aims to rectify this by introducing a consistent
+coordinate system. The use of real coordinates is highly recommended, and so
+this chapter will use them exclusively.
-Elements are items such as text boxes or buttons, or can be metadata such
-as size or background.
+{% include notice.html notice=page.minetest510 %}
-Here are two elements, of types foo and bar.
+
+## Anatomy of a Formspec
+
+### Elements
+
+Formspec is a domain-specific language with an unusual format.
+It consists of a number of elements with the following form:
+
+ type[param1;param2]
+
+The element type is declared and then any parameters are given
+in square brackets. Multiple elements can be joined together, or placed
+on multiple lines, like so:
foo[param1]bar[param1]
+ bo[param1]
-### Size[w, h]
-Nearly all forms have a size tag. This declares the size of the form window. Note that
-**forms don't use pixels as co-ordinates; they use a grid based on inventories**.
-A size of (1, 1) means the form is big enough to host a 1x1 inventory.
-This means the size of the form is independent of screen resolution and it should work
-just as well on large screens as small screens.
-You can use decimals in sizes and co-ordinates.
+Elements are items such as text boxes or buttons, or can be metadata such
+as size or background. You should refer to
+[lua_api.txt](https://github.com/minetest/minetest/blob/master/doc/lua_api.txt#L1019)
+for a list of all possible elements. Search for "Formspec" to locate the correct
+part of the document.
- size[5,2]
-Co-ordinates and sizes only use one attribute.
-The x and y values are separated by a comma, as you can see above.
+### Header
-### Field[x, y; w, h; name; label; default]
+The header of a formspec contains information which must appear first. This
+includes the size of the formspec, the position, the anchor, and whether the
+game-wide theme should be applied.
-This is a textbox element. Most other elements have a similar style of attributes.
-The name attribute is used in callbacks to get the submitted information.
-The x and y attributes determine the position of the element, and
-the w and h attributes provide the size.
+The elements in the header must be defined in a specific order, otherwise you
+will see an error. This order is given in the above paragraph, and, as always,
+documented in [lua_api.txt](../../lua_api.html#sizewhfixed_size)
- field[1,1;3,1;firstname;Firstname;]
+The size is in formspec slots - a unit of measurement which is roughly
+around 64 pixels, but varies based on the screen density and scaling
+settings of the client. Here's a formspec which is `2,2` in size:
-It is perfectly valid to not define an attribute.
+ size[2,2]
+ real_coordinates[true]
-### Other Elements
+Notice how we explicitly need to enable the use of the real coordinate system.
+Without this, the legacy system will instead be used to size the formspec, which will
+result in a larger size. This element is a special case, as it is the only element
+which may appear both in the header and the body of a formspec. When in the header,
+it must appear immediately after the size.
-You should refer to [lua_api.txt](https://github.com/minetest/minetest/blob/master/doc/lua_api.txt#L1019)
-for a list of all possible elements. Search for "Formspec" to locate the correct part of the document.
-At the time of writing, formspec information begins on line 1765.
+The position and anchor elements are used to place the formspec on the screen.
+The position sets where on the screen the formspec will be, and defaults to
+the center (`0.5,0.5`). The anchor sets where on the formspec the position is,
+allowing you to line the formspec up with the edge of the screen. The formspec
+can be placed to the left of the screen like so:
-## Displaying Formspecs
+ size[2,2]
+ real_coordinates[true]
+ position[0,0.5]
+ anchor[0,0.5]
-Here is a generalised way to show a formspec:
+This sets the anchor to the left middle edge of the formspec box, and then the
+position of that anchor to the left of the screen.
- minetest.show_formspec(playername, formname, formspec)
-Formnames should be itemnames; however, this is not enforced.
-There is no need to override a formspec, because formspecs are not registered like
-nodes and items are. The formspec code is sent to the player's client for them
-to see, along with the formname.
-Formnames are used in callbacks to identify which form has been submitted,
-and to see if the callback is relevant.
-
-### Example
-
-This example shows a formspec to a player when they use the /formspec command.
+## Guessing Game
+The best way to learn is to make something, so let's make a guessing game.
+The principle is simple: the mod decides on a number, then the player makes
+guesses on the number. The mod then says if the guess is higher or lower then
+the actual number.
+
+First, let's make a function to create the formspec code. It's good practice to
+do this, as it makes it easier to reuse elsewhere.
+
+
+
```lua
--- Show form when the /formspec command is used.
-minetest.register_chatcommand("formspec", {
- func = function(name, param)
- minetest.show_formspec(name, "mymod:form",
- "size[4,3]" ..
- "label[0,0;Hello, " .. name .. "]" ..
- "field[1,1.5;3,1;name;Name;]" ..
- "button_exit[1,2;2,1;exit;Save]")
- end
+guessing = {}
+
+function guessing.get_formspec(name)
+ -- TODO: display whether the last guess was higher or lower
+ local text = "I'm thinking of a number... Make a guess!"
+
+ local formspec = {
+ "size[6,3.476]",
+ "real_coordinates[true]",
+ "label[0.375,0.5;", minetest.formspec_escape(text), "]",
+ "field[0.375,1.25;5.25,0.8;number;Number;]",
+ "button[1.5,2.3;3,0.8;guess;Guess]"
+ }
+
+ -- table.concat is faster than string concatenation - `..`
+ return table.concat(formspec, "")
+end
+```
+
+In the above code, we place a field, a label, and a button. A field allows text
+entry, and a button is used to submit the form. You'll notice that the elements
+are positioned carefully in order to add padding and spacing, this will be explained
+later.
+
+Next, we want to allow the player to show the formspec. The main way to do this
+is using `show_formspec`:
+
+```lua
+function guessing.show_to(name)
+ minetest.show_formspec(name, "guessing:game", guessing.get_formspec(name))
+end
+
+minetest.register_chatcommand("game", {
+ func = function(name)
+ guessing.show_to(name)
+ end,
})
```
-Note: the .. is used to join two strings together. The following two lines are equivalent:
+The show_formspec function accepts a player name, the formspec name, and the
+formspec itself. The formspec name should be a valid itemname, ie: in the format
+`modname:itemname`.
+
+
+### Padding and Spacing
+
+
+
+Padding is the gap between the edge of the formspec and its contents, or between unrelated
+elements, shown in red. Spacing is the gap between related elements, shown in blue.
+
+It is fairly standard to have a padding of `0.375` and a spacing of `0.25`.
+
+
+
+
+### Receiving Formspec Submissions
+
+When `show_formspec` is called, the formspec is sent to the client to be displayed.
+For formspecs to be useful, information needs to be returned from the client to server.
+The method for this is called formspec field submission, and for `show_formspec`, that
+submission is received using a global callback:
```lua
-"foobar"
-"foo" .. "bar"
-```
-
-## Callbacks
-
-It's possible to expand the previous example with a callback:
-
-```lua
--- Show form when the /formspec command is used.
-minetest.register_chatcommand("formspec", {
- func = function(name, param)
- minetest.show_formspec(name, "mymod:form",
- "size[4,3]" ..
- "label[0,0;Hello, " .. name .. "]" ..
- "field[1,1.5;3,1;name;Name;]" ..
- "button_exit[1,2;2,1;exit;Save]")
- end
-})
-
--- Register callback
-minetest.register_on_player_receive_fields(function(player,
- formname, fields)
- if formname ~= "mymod:form" then
- -- Formname is not mymod:form,
- -- exit callback.
- return false
+minetest.register_on_player_receive_fields(function(player, formname, fields)
+ if formname ~= "guessing:game" then
+ return
end
- -- Send message to player.
- minetest.chat_send_player(player:get_player_name(),
- "You said: " .. fields.name .. "!")
-
- -- Return true to stop other callbacks from
- -- receiving this submission.
- return true
+ if fields.guess then
+ local pname = player:get_player_name()
+ minetest.chat_send_all(pname .. " guessed " .. fields.number)
+ end
end)
```
The function given in minetest.register_on_player_receive_fields is called
-every time a user submits a form. Most callbacks will check the formname given
+every time a user submits a form. Most callbacks will need to check the formname given
to the function, and exit if it is not the right form; however, some callbacks
-may need to work on multiple forms, or all forms - it depends on what you
-want to do.
+may need to work on multiple forms, or on all forms.
+
+The `fields` parameter to the function is a table of the values submitted by the
+user, indexed by strings. Named elements will appear in the field under their own
+name, but only if they are relevent for the event that caused the submission.
+For example, a button element will only appear in fields if that particular button
+was pressed.
{% include notice.html notice=page.submit_vuln %}
-### Fields
+So, now the formspec is sent to the client and the client sends information back.
+The next step is to somehow generate and remember the target value, and to update
+the formspec based on guesses. The way to do this is using a concept called
+"contexts".
-The `fields` parameter to the function is a table, index by string, of the values
-submitted by the user. You can access values in the table via fields.name,
-where 'name' is the name of the element.
-As well as retrieving the values of each element, you can also get which button
-was clicked. In this case, the button called 'exit' was clicked, so fields.exit
-will be true.
-
-Some elements can submit the form without the user clicking a button,
-such as a checkbox. You can detect these cases by looking
-for a clicked button.
-
-```lua
--- An example of what fields could contain,
--- using the above code
-{
- name = "Foo Bar",
- exit = true
-}
-```
-
-## Contexts
+### Contexts
In many cases you want minetest.show_formspec to give information
to the callback which you don't want to send to the client. This might include
-what a chat command was called with, or what the dialog is about.
+what a chat command was called with, or what the dialog is about. In this case,
+the target value that needs to be remembered.
-For example, you might make a form to handle land protection information:
+A context is a per-player table to store information, and the contexts for all
+online players are stored in a file-local variable:
```lua
---
--- Step 1) set context when player requests the formspec
---
+local _contexts = {}
+local function get_context(name)
+ local context = _contexts[name] or {}
+ _contexts[name] = context
+ return context
+end
--- land_formspec_context[playername] gives the player's context.
-local land_formspec_context = {}
-
-minetest.register_chatcommand("land", {
- func = function(name, param)
- if param == "" then
- minetest.chat_send_player(name,
- "Incorrect parameters - supply a land ID")
- return
- end
-
- -- Save information
- land_formspec_context[name] = {id = param}
-
- minetest.show_formspec(name, "mylandowner:edit",
- "size[4,4]" ..
- "field[1,1;3,1;plot;Plot Name;]" ..
- "field[1,2;3,1;owner;Owner;]" ..
- "button_exit[1,3;2,1;exit;Save]")
- end
-})
-
-
-
---
--- Step 2) retrieve context when player submits the form
---
-minetest.register_on_player_receive_fields(function(player,
- formname, fields)
- if formname ~= "mylandowner:edit" then
- return false
- end
-
- -- Load information
- local context = land_formspec_context[player:get_player_name()]
-
- if context then
- minetest.chat_send_player(player:get_player_name(), "Id " ..
- context.id .. " is now called " .. fields.plot ..
- " and owned by " .. fields.owner)
-
- -- Delete context if it is no longer going to be used
- land_formspec_context[player:get_player_name()] = nil
-
- return true
- else
- -- Fail gracefully if the context does not exist.
- minetest.chat_send_player(player:get_player_name(),
- "Something went wrong, try again.")
- end
+minetest.register_on_leaveplayer(function(player)
+ _contexts[player:get_player_name()] = nil
end)
```
-## Node Meta Formspecs
+Next, we need to modify the show code to update the context
+before showing the formspec:
+
+```lua
+function guessing.show_to(name)
+ local context = get_context(name)
+ context.target = context.target or math.random(1, 10)
+
+ local fs = guessing.get_formspec(name, context)
+ minetest.show_formspec(name, "guessing:game", fs)
+end
+```
+
+We also need to modify the formspec generation code to use the context:
+
+```lua
+function guessing.get_formspec(name, context)
+ local text
+ if not context.guess then
+ text = "I'm thinking of a number... Make a guess!"
+ elseif context.guess == context.target then
+ text = "Hurray, you got it!"
+ elseif context.guess > context.target then
+ text = "To high!"
+ else
+ text = "To low!"
+ end
+```
+
+Note that it's good practice for get_formspec to only read the context, and not
+update it at all. This can make the function simpler, and also easier to test.
+
+And finally, we need to update the handler to update the context with the guess:
+
+```lua
+if fields.guess then
+ local name = player:get_player_name()
+ local context = get_context(name)
+ context.guess = tonumber(fields.number)
+ guessing.show_to(name)
+end
+```
+
+
+## Formspec Sources
+
+There are three different ways that a formspec can be delivered to the client:
+
+* [show_formspec](#guessing-game): the method used above, fields are received by register_on_player_receive_fields.
+* [Node Meta Formspecs](#node-meta-formspecs): the node contains a formspec in its meta data, and the client
+ shows it *immediately* when the player rightclicks. Fields are received by a
+ method in the node definition called `on_receive_fields`.
+* [Player Inventory Formspecs](#player-inventory-formspecs): the formspec is sent to the client at some point, and then
+ shown immediately when the player presses `i`. Fields are received by
+ register_on_player_receive_fields.
+
+### Node Meta Formspecs
minetest.show_formspec is not the only way to show a formspec; you can also
add formspecs to a [node's metadata](node_metadata.html). For example,
@@ -305,3 +364,22 @@ This style of callback triggers when you press enter
in a field, which is impossible with `minetest.show_formspec`;
however, this kind of form can only be shown by right-clicking on a
node. It cannot be triggered programmatically.
+
+### Player Inventory Formspecs
+
+The player inventory formspec is the one shown when the player presses i.
+The global callback is used to receive events from this formspec, and the
+formname is `""`.
+
+There are a number of different mods which allow multiple mods to customise
+the player inventory. The officially recommended mod is
+[Simple Fast Inventory (sfinv)](sfinv.html), and is included in Minetest Game.
+
+
+### Your Turn
+
+* Extend the Guessing Game to keep track of each player's top score, where the
+ top score is how many guesses it took.
+* Make a node called "Inbox" where users can open up a formspec and leave messages.
+ This node should store the placers' name as `owner` in the meta, and should use
+ `show_formspec` to show different formspecs to different players.
diff --git a/_includes/notice.html b/_includes/notice.html
index 009a910..aff44d5 100644
--- a/_includes/notice.html
+++ b/_includes/notice.html
@@ -4,7 +4,7 @@
{% assign notice=include %}
{% endif %}
-
+
{% if notice.level == "warning" %}
⚠
{% else if notice.level == "tip" %}
diff --git a/_sass/_main.scss b/_sass/_main.scss
index f544109..a01248b 100644
--- a/_sass/_main.scss
+++ b/_sass/_main.scss
@@ -101,16 +101,16 @@ footer a:hover {
text-decoration: underline;
}
-#header {
+header {
text-align: center;
padding: 100px 0;
}
-#header h1 {
+header h1 {
padding-bottom: 20px;
}
-#header span {
+header span {
display: block;
padding: 6px;
}
@@ -119,6 +119,10 @@ footer a:hover {
font-size: 200%;
}
+.book-only {
+ display: none;
+}
+
@media all and (max-height: 568px) {
nav {
position: absolute;
diff --git a/static/formspec_guessing.png b/static/formspec_guessing.png
new file mode 100644
index 0000000..e935d8a
Binary files /dev/null and b/static/formspec_guessing.png differ
diff --git a/static/formspec_name.png b/static/formspec_name.png
deleted file mode 100644
index 592138f..0000000
Binary files a/static/formspec_name.png and /dev/null differ
diff --git a/static/formspec_padding_spacing.png b/static/formspec_padding_spacing.png
new file mode 100644
index 0000000..2d2d7dd
Binary files /dev/null and b/static/formspec_padding_spacing.png differ