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.
|