minetest_modding_book/_de/players/formspecs.md

380 lines
14 KiB
Markdown
Raw Normal View History

2022-08-30 13:10:38 +03:00
---
title: GUIs (Formspecs)
layout: default
root: ../..
idx: 4.5
description: Lerne, wie man GUIs mit formspecs anzeigt
redirect_from: /de/chapters/formspecs.html
submit_vuln:
level: warning
title: Malicious clients can submit anything at anytime
message: You should never trust a formspec submission. A malicious client
can submit anything they like at any time - even if you never showed
them the formspec. This means that you should check privileges
and make sure that they should be allowed to perform the action.
---
## Einleitung <!-- omit in toc -->
<figure class="right_image">
<img src="{{ page.root }}//static/formspec_example.png" alt="Ofen-Inventar">
<figcaption>
Screenshot eines formspec für Öfen, beschriftet.
</figcaption>
</figure>
2022-08-31 22:33:54 +03:00
In diesem Kapitel werden wir lernen, wie man einen formspec erstellt und ihn dem Benutzer anzeigt.
Ein formspec ist der Spezifikationscode für ein Form.
In Minetest sind Forms Fenster wie das Spielerinventar und können eine
eine Vielzahl von Elementen wie Beschriftungen, Schaltflächen und Felder enthalten.
Beachten Sie, dass Sie, wenn Sie keine Benutzereingaben benötigen, zum Beispiel wenn Sie nur
Informationen für den Spieler bereitstellen möchten, Sie die Verwendung von
[Heads Up Display (HUD)](hud.html)-Elemente anstelle von Formularen verwenden sollten, da
unerwartete Fenster das Spielgeschehen stören können.
- [Reale oder Legacy-Koordinaten](#reale-oder-legacy-koordinaten)
- [Anatomie eines formspecs](#anatomie-eines-a-formspecs)
- [Elemente](#elemente)
2022-08-30 13:10:38 +03:00
- [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)
2022-08-31 22:33:54 +03:00
## Reale oder Legacy-Koordinaten
2022-08-30 13:10:38 +03:00
2022-08-31 22:33:54 +03:00
In älteren Versionen von Minetest waren die formspecs inkonsistent. Die Art und Weise, wie verschiedene
Elemente auf unerwartete Art und Weise positioniert wurden; es war schwierig, die
Platzierung der Elemente vorherzusagen und auszurichten. Minetest 5.1.0 enthält eine Funktion
Koordinaten, die dieses Problem durch die Einführung eines konsistenten
Koordinatensystem. Die Verwendung von realen Koordinaten wird dringend empfohlen, und deshalb
dieses Kapitel ausschließlich diese verwenden.
2022-08-30 13:10:38 +03:00
2022-08-31 22:33:54 +03:00
Die Verwendung einer formspec_version von 2 oder höher aktiviert reale Koordinaten.
2022-08-30 13:10:38 +03:00
2022-08-31 22:33:54 +03:00
## Anatomie eines formspecs
2022-08-30 13:10:38 +03:00
2022-08-31 22:33:54 +03:00
### Elemente
2022-08-30 13:10:38 +03:00
2022-08-31 22:33:54 +03:00
Formspec ist eine domänenspezifische Sprache mit einem ungewöhnlichen Format.
Sie besteht aus einer Reihe von Elementen mit der folgenden Form:
2022-08-30 13:10:38 +03:00
type[param1;param2]
2022-08-31 22:33:54 +03:00
Der Elementtyp wird deklariert und dann werden alle Parameter
in eckigen Klammern angegeben. Mehrere Elemente können miteinander verbunden werden, oder
auf mehrere Zeilen verteilt werden, etwa so:
2022-08-30 13:10:38 +03:00
foo[param1]bar[param1]
bo[param1]
2022-08-31 22:33:54 +03:00
Elemente sind Elemente wie Textfelder oder Schaltflächen oder können Metadaten sein wie
wie Größe oder Hintergrund sein. Sie sollten nachschlagen in der
2022-08-30 13:10:38 +03:00
[lua_api.txt](https://minetest.gitlab.io/minetest/formspec/)
2022-08-31 22:33:54 +03:00
für eine Liste aller möglichen Elemente.
2022-08-30 13:10:38 +03:00
### Header
2022-08-31 22:33:54 +03:00
Der Header eines formspec enthält Informationen, die zuerst erscheinen müssen. Diese
umfasst die Größe des formspec, die Position, den Anker und ob das
spielweite Thema angewendet werden soll.
2022-08-30 13:10:38 +03:00
2022-08-31 22:33:54 +03:00
Die Elemente im Header müssen in einer bestimmten Reihenfolge definiert werden, sonst
wird ein Fehler angezeigt. Diese Reihenfolge ist im obigen Absatz angegeben und, wie immer,
in der Lua-API-Referenz dokumentiert.
2022-08-30 13:10:38 +03:00
2022-08-31 22:33:54 +03:00
Die Größe wird in formspec-Slots angegeben - eine Maßeinheit, die ungefähr
etwa 64 Pixel entspricht, jedoch abhängig von der Bildschirmdichte und den Skalierungs
Einstellungen des Clients ist. Hier ist ein formspec mit der Größe "2,2":
2022-08-30 13:10:38 +03:00
formspec_version[4]
size[2,2]
2022-08-31 22:33:54 +03:00
Beachten Sie, dass wir die formspec-Sprachversion ausdrücklich definiert haben müssen.
Ohne dies wird stattdessen das Altsystem verwendet - was
die Verwendung der konsistenten Elementpositionierung und anderer neuer Funktionen verhindert.
2022-08-30 13:10:38 +03:00
2022-08-31 22:33:54 +03:00
Die Elemente position und Anker werden verwendet, um das formspec auf dem Bildschirm zu platzieren.
Die Position legt fest, wo auf dem Bildschirm das formspec sein wird, und ist standardmäßig auf
die Mitte (`0,5,0,5`). Der Anker legt fest, wo auf dem formspec die Position ist,
so dass Sie das formspec mit dem Rand des Bildschirms ausrichten können. Das formspec
kann auf diese Weise links vom Bildschirm platziert werden:
2022-08-30 13:10:38 +03:00
formspec_version[4]
size[2,2]
position[0,0.5]
anchor[0,0.5]
2022-08-31 22:33:54 +03:00
Dadurch wird der Anker an den linken mittleren Rand des formspec-Feldes gesetzt, und die
Position dieses Ankers auf der linken Seite des Bildschirms.
2022-08-30 13:10:38 +03:00
## Guessing Game
<figure class="right_image">
<img src="{{ page.root }}/static/formspec_guessing.png" alt="Guessing Formspec">
<figcaption>
The guessing game formspec.
</figcaption>
</figure>
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.
<div style="clear: both;"></div>
```lua
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 = {
"formspec_version[4]",
"size[6,3.476]",
"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,
})
```
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
<figure class="right_image">
<img src="{{ page.root }}/static/formspec_padding_spacing.png" alt="Padding and spacing">
<figcaption>
The guessing game formspec.
</figcaption>
</figure>
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`.
<div style="clear: both;"></div>
### 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
minetest.register_on_player_receive_fields(function(player, formname, fields)
if formname ~= "guessing:game" then
return
end
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 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 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 %}
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".
### 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. In this case,
the target value that needs to be remembered.
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
local _contexts = {}
local function get_context(name)
local context = _contexts[name] or {}
_contexts[name] = context
return context
end
minetest.register_on_leaveplayer(function(player)
_contexts[player:get_player_name()] = nil
end)
```
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 = "Too high!"
else
text = "Too 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,
this is used with chests to allow for faster opening times -
you don't need to wait for the server to send the player the chest formspec.
```lua
minetest.register_node("mymod:rightclick", {
description = "Rightclick me!",
tiles = {"mymod_rightclick.png"},
groups = {cracky = 1},
after_place_node = function(pos, placer)
-- This function is run when the chest node is placed.
-- The following code sets the formspec for chest.
-- Meta is a way of storing data onto a node.
local meta = minetest.get_meta(pos)
meta:set_string("formspec",
"formspec_version[4]" ..
"size[5,5]" ..
"label[1,1;This is shown on right click]" ..
"field[1,2;2,1;x;x;]")
end,
on_receive_fields = function(pos, formname, fields, player)
if fields.quit then
return
end
print(fields.x)
end
})
```
Formspecs set this way do not trigger the same callback. In order to
receive form input for meta formspecs, you must include an
`on_receive_fields` entry when registering the node.
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.