WIP: Перевод на русский #1

Draft
Koldun wants to merge 5 commits from ru_dev into ru
32 changed files with 5633 additions and 0 deletions

View File

@ -14,3 +14,5 @@ collections:
output: true
it:
output: true
ru:
output: true

View File

@ -7,3 +7,7 @@
- code: it
name: Italiano
cta: Questo libro è disponibile in italiano
- code: ru
name: Russian
cta: Эта кника доступна на русском

View File

@ -9,6 +9,9 @@ layout: base
{% if language == "_it" %}
{% assign language = "it" %}
{% assign links = site.it %}
{% elif language == "_ru" %}
{% assign language = "ru" %}
{% assign links = site.ru %}
{% else %}
{% assign language = "en" %}
{% assign links = site.en %}

243
_ru/advmap/biomesdeco.md Normal file
View File

@ -0,0 +1,243 @@
---
title: Biomes and Decorations
author: Shara
layout: default
root: ../..
idx: 6.1
description: Create biomes and decorations to customise the map
---
## Introduction <!-- omit in toc -->
The ability to register biomes and decorations is vital when aiming to create an
interesting and varied in-game environment. This chapter teaches you how to
register biomes, how to control biome distribution, and how to place decorations in biomes.
- [What are Biomes?](#what-are-biomes)
- [Biome Placement](#biome-placement)
- [Heat and Humidity](#heat-and-humidity)
- [Visualising Boundaries using Voronoi Diagrams](#visualising-boundaries-using-voronoi-diagrams)
- [Creating a Voronoi Diagram using Geogebra](#creating-a-voronoi-diagram-using-geogebra)
- [Registering a Biome](#registering-a-biome)
- [What are Decorations?](#what-are-decorations)
- [Registering a Simple Decoration](#registering-a-simple-decoration)
- [Registering a Schematic Decoration](#registering-a-schematic-decoration)
- [Mapgen Aliases](#mapgen-aliases)
## What are Biomes?
A Minetest biome is a specific in-game environment. When registering biomes, you
can determine the types of nodes that appear in them during map generation.
Some of the most common types of node that may vary between biomes include:
* Top node: This is the node most commonly found on the surface. A well-known
example would be "Dirt with Grass" from Minetest Game.
* Filler node: This is the layer immediately beneath the top node.
In biomes with grass, it will often be dirt.
* Stone node: This is the node you most commonly see underground.
* Water node: This is usually a liquid and will be the node that appears
where you would expect bodies of water.
Other types of node can also vary between biomes, providing an opportunity
to create vastly different environments within the same game.
## Biome Placement
### Heat and Humidity
It is not enough to simply register a biome; you must also decide where it can
occur in game. This is done by assigning a heat and a humidity value to each biome.
You should think carefully about these values; they determine which biomes can
be neighbours to each other. Poor decisions could result in what is meant to
be a hot desert sharing a border with a glacier, and other improbable
combinations which you may prefer to avoid.
In game, heat and humidity values at any point of the map will usually be between
0 and 100. The values gradually change, increasing or decreasing as you move
around the map. The biome at any given point will be determined by which of the
registered biomes has heat and humidity values closest to those at that position on the map.
Because the changes in heat and humidity are gradual, it is good practice to assign
heat and humidity values to biomes based on reasonable expectations about that
biomes environment. For example:
* A desert might have high heat and low humidity.
* A snowy forest might have low heat and a medium humidity value.
* A swamp biome would generally have high humidity.
*
In practice, this means that, as long as you have a diverse range of biomes, you
are likely to find that the biomes which border each other form a logical progression.
### Visualising Boundaries using Voronoi Diagrams
<figure class="right_image">
<img src="{{ page.root }}/static/biomes_voronoi.png" alt="Vernoi">
<figcaption>
Voronoi diagram, showing the closest point.
<span class="credit">By <a href="https://en.wikipedia.org/wiki/Voronoi_diagram#/media/File:Euclidean_Voronoi_diagram.svg">Balu Ertl</a>, CC BY-SA 4.0.</span>
</figcaption>
</figure>
Fine-tuning heat and humidity values for biomes is
easier if you can visualise the relationship between the biomes you are using.
This is most important if you are creating a full set of your own biomes, but
can also be helpful if you are adding a biome to an existing set.
The simplest way to visualise which biomes may share borders is to create a
Voronoi diagram, which can be used to show which point on a 2-dimensional
diagram any given position is closest to.
A Voronoi diagram can reveal where biomes that should border each other do not,
and where biomes that should not border each other do. It can also give a
general insight into how common biomes will be in-game, with larger and more
central biomes being more common than smaller biomes or biomes that are located
on the outer edge of the diagram.
This is done by marking a point for each biome based on heat and humidity values,
where the x-axis is heat and the y-axis is humidity. The diagram is then
divided into areas, such that every position in a given area is closer to the
point inside that area than it is to any other point on the diagram.
Each area represents a biome. If two areas share a border, the biomes they
represent in-game can be located next to each other. The length of the border
shared between two areas, compared to the length shared with other areas, will
tell you how frequently two biomes are likely to be found next to each other.
### Creating a Voronoi Diagram using Geogebra
As well as drawing them by hand, you can also create Voronoi diagrams using
programs such as [Geogebra](https://www.geogebra.org).
1. Create points by selecting the point tool in the toolbar (icon is a point with 'A'),
and then clicking the chart. You can drag points around or explicitly set their
position in the left sidebar. You should also give each point a label, to make things clearer.
1. Next, create the voronoi by entering the following function into the
input box in the left sidebar:
```cpp
Voronoi({ A, B, C, D, E })
```
Where the each point is inside the curly brackets, separated by commas. You should now
3. Profit! You should now have a voronoi diagram with all draggable points.
## Registering a Biome
The following code registers a simple biome named grasslands biome:
```lua
core.register_biome({
name = "grasslands",
node_top = "default:dirt_with_grass",
depth_top = 1,
node_filler = "default:dirt",
depth_filler = 3,
y_max = 1000,
y_min = -3,
heat_point = 50,
humidity_point = 50,
})
```
This biome has one layer of Dirt with Grass nodes on the surface, and three layers
of Dirt nodes beneath this. It does not specify a stone node, so the node defined
in the mapgen alias registration for `mapgen_stone` will be present underneath the dirt.
There are many options when registering a biome, and these are documented
in the [Minetest Lua API Reference](https://minetest.gitlab.io/minetest/definition-tables/#biome-definition),
as always.
You dont need to define every option for every biome you create, but in some cases failure
to define either a specific option, or a suitable mapgen alias, can result in map generation errors.
## What are Decorations?
Decorations are either nodes or schematics that can be placed on the map at mapgen.
Some common examples include flowers, bushes, and trees. Other more creative uses
may include hanging icicles or stalagmites in caves, underground crystal formations,
or even the placement of small buildings.
Decorations can be restricted to specific biomes, by height, or by which nodes
they can be placed on. They are often used to develop the environment of a biome
by ensuring it has specific plants, trees or other features.
## Registering a Simple Decoration
Simple decorations are used to place single node decorations on the map during
map generation. You must specify the node that is to be placed as a decoration,
details for where it can be placed, and how frequently it occurs.
For example:
```lua
core.register_decoration({
deco_type = "simple",
place_on = {"base:dirt_with_grass"},
sidelen = 16,
fill_ratio = 0.1,
biomes = {"grassy_plains"},
y_max = 200,
y_min = 1,
decoration = "plants:grass",
})
```
In this example, the node named `plants:grass` will be placed in the biome named
grassy_plains on top of `base:dirt_with_grass` nodes, between the heights of `y = 1` and `y = 200`.
The fill_ratio value determines how frequently the decoration appears, with higher
values up to 1 resulting in a great number of decorations being placed. It is possible
to instead use noise parameters to determine placement.
## Registering a Schematic Decoration
Schematic decorations are very similar to simple decoration, but involve the placement
of a schematic instead of the placement of a single node. For example:
```lua
core.register_decoration({
deco_type = "schematic",
place_on = {"base:desert_sand"},
sidelen = 16,
fill_ratio = 0.0001,
biomes = {"desert"},
y_max = 200,
y_min = 1,
schematic = core.get_modpath("plants") .. "/schematics/cactus.mts",
flags = "place_center_x, place_center_z",
rotation = "random",
})
```
In this example the cactus.mts schematic is placed in desert biomes. You need to provide
a path to a schematic, which in this case is stored in a dedicated schematic directory within the mod.
This example also sets flags to center the placement of the schematic, and the rotation
is set to random. The random rotation of schematics when they are placed as decorations
helps introduce more variation when asymmetrical schematics are used.
## Mapgen Aliases
Existing games should already include suitable mapgen aliases, so you only need
to consider registering mapgen aliases of your own if you are making your own game.
Mapgen aliases provide information to the core mapgen, and can be registered in the form:
```lua
core.register_alias("mapgen_stone", "base:smoke_stone")
```
At a minimum you should register:
* mapgen_stone
* mapgen_water_source
* mapgen_river_water_source
If you are not defining cave liquid nodes for all biomes, you should also register:
* mapgen_lava_source

211
_ru/advmap/lvm.md Normal file
View File

@ -0,0 +1,211 @@
---
title: Lua Voxel Manipulators
layout: default
root: ../..
idx: 6.2
description: Learn how to use LVMs to speed up map operations.
redirect_from:
- /en/chapters/lvm.html
- /en/map/lvm.html
mapgen_object:
level: warning
title: LVMs and Mapgen
message: Don't use `core.get_voxel_manip()` with mapgen, as it can cause glitches.
Use `core.get_mapgen_object("voxelmanip")` instead.
---
## Introduction <!-- omit in toc -->
The functions outlined in the [Basic Map Operations](../map/environment.html) chapter
are convenient and easy to use, but for large areas they are inefficient.
Every time you call `set_node` or `get_node`, your mod needs to communicate with
the engine. This results in constant individual copying operations between the
engine and your mod, which is slow and will quickly decrease the performance of
your game. Using a Lua Voxel Manipulator (LVM) can be a better alternative.
- [Concepts](#concepts)
- [Reading into the LVM](#reading-into-the-lvm)
- [Reading Nodes](#reading-nodes)
- [Writing Nodes](#writing-nodes)
- [Example](#example)
- [Your Turn](#your-turn)
## Concepts
An LVM allows you to load large areas of the map into your mod's memory.
You can then read and write this data without further interaction with the
engine and without running any callbacks, which means that these
operations are very fast. Once done, you can then write the area back into
the engine and run any lighting calculations.
## Reading into the LVM
You can only load a cubic area into an LVM, so you need to work out the minimum
and maximum positions that you need to modify. Then you can create and read into
an LVM. For example:
```lua
local vm = core.get_voxel_manip()
local emin, emax = vm:read_from_map(pos1, pos2)
```
For performance reasons, an LVM will almost never read the exact area you tell it to.
Instead, it will likely read a larger area. The larger area is given by `emin` and `emax`,
which stand for *emerged min pos* and *emerged max pos*. An LVM will load the area
it contains for you - whether that involves loading from memory, from disk, or
calling the map generator.
{% include notice.html notice=page.mapgen_object %}
## Reading Nodes
To read the types of nodes at particular positions, you'll need to use `get_data()`.
This returns a flat array where each entry represents the type of a
particular node.
```lua
local data = vm:get_data()
```
You can get param2 and lighting data using the methods `get_light_data()` and `get_param2_data()`.
You'll need to use `emin` and `emax` to work out where a node is in the flat arrays
given by the above methods. There's a helper class called `VoxelArea` which handles
the calculation for you.
```lua
local a = VoxelArea:new{
MinEdge = emin,
MaxEdge = emax
}
-- Get node's index
local idx = a:index(x, y, z)
-- Read node
print(data[idx])
```
When you run this, you'll notice that `data[vi]` is an integer. This is because
the engine doesn't store nodes using strings, for performance reasons.
Instead, the engine uses an integer called a content ID.
You can find out the content ID for a particular type of node with
`get_content_id()`. For example:
```lua
local c_stone = core.get_content_id("default:stone")
```
You can then check whether the node is stone:
```lua
local idx = a:index(x, y, z)
if data[idx] == c_stone then
print("is stone!")
end
```
Content IDs of a node type may change during load time, so it is recommended that
you don't try getting them during this time.
Nodes in an LVM data array are stored in reverse co-ordinate order, so you should
always iterate in the order `z, y, x`. For example:
```lua
for z = min.z, max.z do
for y = min.y, max.y do
for x = min.x, max.x do
-- vi, voxel index, is a common variable name here
local vi = a:index(x, y, z)
if data[vi] == c_stone then
print("is stone!")
end
end
end
end
```
The reason for this touches on the topic of computer architecture. Reading from RAM is rather
costly, so CPUs have multiple levels of caching. If the data that a process requests
is in the cache, it can very quickly retrieve it. If the data is not in the cache,
then a cache miss occurs and it will fetch the data it needs from RAM. Any data
surrounding the requested data is also fetched and then replaces the data in the cache. This is
because it's quite likely that the process will ask for data near that location again. This means
a good rule of optimisation is to iterate in a way that you read data one after
another, and avoid *cache thrashing*.
## Writing Nodes
First, you need to set the new content ID in the data array:
```lua
for z = min.z, max.z do
for y = min.y, max.y do
for x = min.x, max.x do
local vi = a:index(x, y, z)
if data[vi] == c_stone then
data[vi] = c_air
end
end
end
end
```
When you finish setting nodes in the LVM, you then need to upload the data
array to the engine:
```lua
vm:set_data(data)
vm:write_to_map(true)
```
For setting lighting and param2 data, use the appropriately named
`set_light_data()` and `set_param2_data()` methods.
`write_to_map()` takes a Boolean which is true if you want lighting to be
calculated. If you pass false, you need to recalculate lighting at a future
time using `core.fix_light`.
## Example
```lua
local function grass_to_dirt(pos1, pos2)
local c_dirt = core.get_content_id("default:dirt")
local c_grass = core.get_content_id("default:dirt_with_grass")
-- Read data into LVM
local vm = core.get_voxel_manip()
local emin, emax = vm:read_from_map(pos1, pos2)
local a = VoxelArea:new{
MinEdge = emin,
MaxEdge = emax
}
local data = vm:get_data()
-- Modify data
for z = pos1.z, pos2.z do
for y = pos1.y, pos2.y do
for x = pos1.x, pos2.x do
local vi = a:index(x, y, z)
if data[vi] == c_grass then
data[vi] = c_dirt
end
end
end
end
-- Write data
vm:set_data(data)
vm:write_to_map(true)
end
```
## Your Turn
* Create `replace_in_area(from, to, pos1, pos2)`, which replaces all instances of
`from` with `to` in the area given, where `from` and `to` are node names.
* Make a function which rotates all chest nodes by 90&deg;.
* Make a function which uses an LVM to cause mossy cobble to spread to nearby
stone and cobble nodes.
Does your implementation cause mossy cobble to spread more than a distance of one node each
time? If so, how could you stop this?

View File

@ -0,0 +1,182 @@
---
title: Основы
layout: default
root: ../..
idx: 1.1
description: Как создать проект мода, включая init.lua, mod.conf и прочее.
redirect_from:
- /ru/chapters/folders.html
- /ru/basics/folders.html
---
## Введение <!-- omit in toc -->
При создании модов очень важно понимать структуру проекта мода. В этой главе
вы узнаете как работает моддинг в Minetest и создадите свой первый мод.
- [Что такое Игры и что такое Моды?](#what-are-games-and-mods)
- [Где хранятся моды?](#where-are-mods-stored)
- [Создание первого мода](#creating-your-first-mod)
- [Директория](#mod-directory)
- [mod.conf](#modconf)
- [init.lua](#initlua)
- [Итого](#summary)
- [Зависимости](#dependencies)
- [Модпаки](#mod-packs)
## Что такое Игры и что такое Моды?
Сила Minetest — в возможности легко создавать игры без необходимости создания
собственной воксельной графики, воксельных алгоритмов или крутого сетевого
кода.
В Minetest Игра — это набор модулей, работающих вместе и создающих контент и
игровую механику.
Модуль (или мод) — это набор скриптов и материалов.
Конечно, можно создать игру только из одного мода, но такое встречается редко,
ведь так труднее настраивать и заменять отдельные элементы игры, не затрагивая
всё остальное.
Кроме того, вы можете распространять свой мод без привязки к конкретной игре.
В этом случае он также будет называться *модом* в традиционном смысле —
модификация. Такие моды изменяют или добавляют различные элементы игры.
Как моды в составе игры, так и просто сторонние моды используют один и тот же
API.
В этой книге описаны основы Minetest API, и она подходит как для разработчиков
игр, так и для моддеров.
## Где хранятся моды?
<a name="mod-locations"></a>
У каждого мода есть своя директория, в которой лежат Lua-код, текстуры,
модели и звуки. Minetest ищет моды в нескольких определённых папках.
В документации эти папки обозначаются как *mod load paths*.
Для каждой созданной карты или сохранённой игры Minetest ищет моды в трёх
местах в следующем порядке:
1. Моды Игры: Эти моды являются неотъемлимой частью Игры.
Папки: `minetest/games/minetest_game/mods/`, `/usr/share/minetest/games/minetest/`
2. Глобальные моды: место куда почти всегда устанавливаются моды.
Если не уверены, кладите их сюда.
Папка: `minetest/mods/`
3. Моды Мира: эти моды относятся к конкретной созданной карте.
Папка: `minetest/worlds/world/worldmods/`
`minetest` — это директория для пользовательских фалов. Чтобы её найти,
запустите Minetest и нажмите "Папка данных пользователя" на вкладке
"Подробней".
Во время загрузки модов Minetest проверяет по порядку каждое из перечисленных
расположений. Если он обнаружит несколько модов с одинаковым именем, то мод,
найденный позже, заменит собой предыдущий. Таким образом можно подменять моды
игры, помещая мод с таким же названием в папку Глобальных модов.
## Создание первого мода
### Директория
Перейдите в глобальную папку модов (Подробней > Папка данных пользователя > mods)
и создайте новую папку под названием "mymod". `mymod` — это имя мода.
У каждого мода должно быть уникальное *имя*, технический идентификатор (id),
по которому можно ссылаться на этот мод. Имя мода может состоять из букв, цифр
и подчёркиваний. Хорошее имя даёт представление о том, что делает мод.
Директория, в которой лежат компоненты мода, должна иметь такое же имя.
Моды можно искать по их имени на сайте [content.minetest.net](https://content.minetest.net).
mymod
├── textures
│   └── mymod_node.png
├── init.lua
└── mod.conf
Обязательным является только файл init.lua. Тем не менее, рекомендуется
добавлять файл mod.conf и другие компоненты в зависимости от функционала мода.
### mod.conf
Создайте файл mod.conf следующего содержания:
```
name = mymod
description = Adds foo, bar, and bo.
depends = default
```
Этот файл содержит мета-данные мода, такие как имя, описание и прочее.
### init.lua
Создайте файл init.lua следующего содержания:
```lua
print("This file will be run at load time!")
core.register_node("mymod:node", {
description = "This is a node",
tiles = {"mymod_node.png"},
groups = {cracky = 1}
})
core.register_craft({
type = "shapeless",
output = "mymod:node 3",
recipe = { "default:dirt", "default:stone" },
})
```
Файл init.lua запускается при загрузке мода.
### Итого
Этот мод носит имя "mymod". Он содержит два текстовых файла: init.lua
и mod.conf. Скрипт выводит сообщение, а затем регистрирует новый рецепт крафта
(об этом будет написано дальше). У этого мода указана единственная зависимость
от [мода default](https://content.minetest.net/metapackages/default/), который,
обычно, является частью Minetest Game. Также есть текстура для блока в папке
textures/.
## Зависимости
Зависимости указываются, если перед загрузкой нода необходимо загрузить другой
мод. Моду могут потребоваться код, предметы или ещё что-нибудь из другого мода.
Существует два типа зависимостей: жёсткие и необязательные.
В обоих случаях требуемый мод загружается первым. Если требуемый мод
недоступен, жёсткая зависимость не позволит загрузить мод, в то время как
при необязательной зависимости мод всего лишь потеряет часть функций.
Необязательные зависимости полезны в случаях, когда вы хотите добавить
поддержку другого мода: например, вы можете добавлять дополнительный контент,
если пользователь решит задействовать оба мода одновременно.
Несколько зависимостей перечисляются через запятую в файле mod.conf.
depends = modone, modtwo
optional_depends = modthree
## Модпаки
Несколько модов могут быть собраны в модпак, который позволяет распространять
эти моды вместе. Модпак позволяет загрузить и установить несколько модов
за раз, без необходимости скачивать их по одному.
modpack1
├── modpack.conf (обязательный) — обозначает, что это модпак
├── mod1
│   └── ... файлы мода
└── mymod (optional)
   └── ... файлы мода
Не нужно путать модпак и *Игру*.
У Игры есть собственная структура, которая будет описана в главе Игры.

200
_ru/basics/lua.md Normal file
View File

@ -0,0 +1,200 @@
---
title: Пишем на Lua
layout: default
root: ../..
idx: 1.2
description: Введение в Lua
redirect_from: /ru/chapters/lua.html
---
## Введение <!-- omit in toc -->
В этой главе вы узнаете, как писать на Lua и какие для этого нужны инструменты.
Также вы познакомитесь с некоторыми полезными техниками.
- [Программирование](#programming)
- [Пишем на Lua](#coding-in-lua)
- [Редактор](#code-editors)
- [Области видимости](#local-and-global-scope)
- [Используйте локальные переменные везде](#locals-should-be-used-as-much-as-possible)
- [Подключение других скриптов](#including-other-lua-scripts)
## Программирование
Программирование — это когда вы берёте задачу (например, сортировка списка)
и превращаете её в простые шаги, который компьютер сможет выполнить.
Обучение вас программированию не является целью этой книги. Тем не менее,
эти сайты могут оказаться для вас полезными:
* [Codecademy](http://www.codecademy.com/) один из лучших ресурсов для обучения
программированию. Здесь есть интерактивные обучающие примеры.
* [Scratch](https://scratch.mit.edu) — хороший ресурс для того, чтобы начать
с самых основ и научиться технике решения задач в программировании.
Он отлично подойдёт детям и подросткам.
* [Programming with Mosh](https://www.youtube.com/user/programmingwithmosh) —
прекрасный YouTube-канал по обучению программированию.
### Пишем на Lua
Обучение вас программированию на Lua также не является целью этой книги.
Рекомендую книгу [Programming in Lua (PiL)](https://www.lua.org/pil/contents.html) —
она прекрасно подойдёт для входа в Lua.
## Редактор
Текстовый редактор с подсветкой синтаксиса очень полезен при написании на Lua.
Слова и символы выделяются разными цветами в зависимости от их назначения. Это
облегчает поиск ошибок и несоответствий.
Например:
```lua
function ctf.post(team,msg)
if not ctf.team(team) then
return false
end
if not ctf.team(team).log then
ctf.team(team).log = {}
end
table.insert(ctf.team(team).log,1,msg)
ctf.save()
return true
end
```
В этом примере подсвечены ключевые слова, такие как: `if`, `then`, `end`
и `return`. Стандартные фукнции Lua, такие как `table.insert`, также будут
подсвечены.
Широко используемые редакторы для Lua:
* [VSCode](https://code.visualstudio.com/):
открытй (как Code-OSS или VSCodium), популярный и к нему есть
[плагин для Minetest](https://marketplace.visualstudio.com/items?itemName=GreenXenith.minetest-tools).
* [Notepad++](http://notepad-plus-plus.org/): простой, только для Windows.
Есть и множество других подходящих редакторов.
## Области видимости
Переменные могут быть локальными и глобальными. К глобальным переменным можно
обращаться из любого места в скрипте и даже из другого мода.
```lua
function one()
foo = "bar"
end
function two()
print(dump(foo)) -- Выведет сообщение: "bar"
end
one()
two()
```
Локальные же переменные доступны только там, где они объявлены.
По умолчанию, все переменные в Lua глобальные, и вам необходимо явно помечать
локальные переменные словом `local`.
```lua
-- Доступна в любом месте этого файла
local one = 1
function myfunc()
-- Доступна только внутри этой функции
local two = one + one
if two == one then
-- Доступна только внутри условного оператора
local three = one + two
end
end
```
### Используйте локальные переменные везде
Старайтесь использовать локальные переменные везде, где только можно.
Мод должен создавать только одну глобальную переменную с таким же именем, как
и у мода. Создание любых других глобальных переменных считается плохим тоном,
и Minetest будет предупреждать об этом сообщением:
Assignment to undeclared global 'foo' inside function at init.lua:2
Чтобы исправить это, используйте ключевое слово "local":
```lua
function one()
local foo = "bar"
end
function two()
print(dump(foo)) -- Выведет сообщение: nil
end
one()
two()
```
Запомните, что "nil" означает **не инициализировано**: переменной ещё не было
присвоено значение, перменная не существует или была удалена (было присвоено
значение nil).
Функции — это особый тип переменных, но они также должны быть объявлены
локально, ведь в других модах могут оказаться свои функции с такими же именами.
```lua
local function foo(bar)
return bar * 2
end
```
Чтобы позволить другим модам вызывать ваши функции, необходимо создать таблицу
с таким же именем, как у вашего мода, и добавить в неё нужные функции.
Такая таблица обычно называется "API мода" или namespace.
```lua
mymod = {}
function mymod.foo(bar)
return "foo" .. bar
end
-- В другом моде или скрипте:
mymod.foo("foobar")
```
`function mymod.foo()` то же самое что и `mymod.foo = function()`, только
выглядит лучше.
## Подключение других скриптов
Лучше всего подключать другие Lua-скрипты в мод с помощью функции *dofile*.
```lua
dofile(core.get_modpath("modname") .. "/script.lua")
```
Скрипт может веруть значение, через которое предоставит доступ к локальным
переменным:
```lua
-- script.lua
local module = {}
module.message = "Hello World!"
return module
-- init.lua
local ret = dofile(core.get_modpath("modname") .. "/script.lua")
print(ret.message) -- Hello world!
```
В [следующей главе](../quality/clean_arch.html) вы узнаете, как лучше всего
разбивать код мода.

93
_ru/games/games.md Normal file
View File

@ -0,0 +1,93 @@
---
title: Creating Games
layout: default
root: ../..
idx: 7.1
---
## Introduction <!-- omit in toc -->
The power of Minetest is the ability to easily develop games without the need
to create your own voxel graphics, voxel algorithms, or fancy networking code.
- [What is a Game?](#what-is-a-game)
- [Game Directory](#game-directory)
- [Inter-game Compatibility](#inter-game-compatibility)
- [API Compatibility](#api-compatibility)
- [Groups and Aliases](#groups-and-aliases)
- [Your Turn](#your-turn)
## What is a Game?
Games are a collection of mods which work together to make a cohesive game.
A good game has a consistent underlying theme and a direction, for example,
it could be a classic crafter miner with hard survival elements, or
it could be a space simulation game with a steampunk automation aesthetic.
Game design is a complex topic and is actually a whole field of expertise.
It's beyond the scope of the book to more than briefly touch on it.
## Game Directory
The structure and location of a game will seem rather familiar after working
with mods.
Games are found in a game location, such as `minetest/games/foo_game`.
foo_game
├── game.conf
├── menu
│   ├── header.png
│   ├── background.png
│   └── icon.png
├── minetest.conf
├── mods
│   └── ... mods
├── README.txt
└── settingtypes.txt
The only thing that is required is a mods folder, but `game.conf` and `menu/icon.png`
are recommended.
## Inter-game Compatibility
### API Compatibility
It's a good idea to try to keep as much API compatibility with Minetest Game as
convenient, as it'll make porting mods to another game much easier.
The best way to keep compatibility with another game is to keep API compatibility
with any mods which have the same name.
That is, if a mod uses the same name as another mod, even if third-party,
it should have a compatible API.
For example, if a game includes a mod called `doors`, then it should have the
same API as `doors` in Minetest Game.
API compatibility for a mod is the sum of the following things:
* Lua API table - All documented/advertised functions in the global table which shares the same name.
For example, `mobs.register_mob`.
* Registered Nodes/Items - The presence of items.
Small breakages aren't that bad, such as not having a random utility
function that was only actually used internally, but bigger breakages
related to core features are very bad.
It's difficult to maintain API compatibility with a disgusting mega God-mod like
*default* in Minetest Game, in which case the game shouldn't include a mod named
default.
API compatibility also applies to other third-party mods and games,
so try to make sure that any new mods have a unique mod name.
To check whether a mod name has been taken, search for it on
[content.minetest.net](https://content.minetest.net/).
### Groups and Aliases
Groups and Aliases are both useful tools in keeping compatibility between games,
as it allows item names to be different between different games. Common nodes
like stone and wood should have groups to indicate the material. It's also a
good idea to provide aliases from default nodes to any direct replacements.
## Your Turn
* Create a simple game where the player gains points from digging special blocks.

35
_ru/index.md Normal file
View File

@ -0,0 +1,35 @@
---
title: Обложка
layout: default
description: Простой гайд как создавать моды в Minetest
homepage: true
no_header: true
root: ..
idx: 0.1
---
<header>
<h1>Luanti Modding Book (бывший Minetest)</h1>
<span>автор <a href="https://rubenwardy.com" rel="author">rubenwardy</a></span>
<span>редактор <a href="http://rc.minetest.tv/">Shara</a></span>
</header>
## Предисловие
Minetest поддерживает создание модов с помощью Lua-скриптов.
Цель этой книги — научить вас создавать свои собственные моды, начиная с основ.
Каждая глава охватывает определённую часть API.
Вы можете не только [читать книгу online](https://rubenwardy.com/minetest_modding_book),
но также [скачать её в формате HTML](https://gitlab.com/rubenwardy/minetest_modding_book/-/releases).
### Обратная связь
Нашли ошибку или хотите дать обратную связь? Сообщите мне об этом.
* Создайте тему в [GitLab Issue](https://gitlab.com/rubenwardy/minetest_modding_book/-/issues).
* Напишите на [форуме](https://forum.minetest.net/viewtopic.php?f=14&t=10729).
* [Свяжитесь со мной](https://rubenwardy.com/contact/).
* Желаете внести свой вклад?
[Прочтите README](https://gitlab.com/rubenwardy/minetest_modding_book/-/blob/master/README.md).

206
_ru/items/callbacks.md Normal file
View File

@ -0,0 +1,206 @@
---
title: Node and Item Callbacks
layout: default
root: ../..
idx: 2.15
description: Learn about callbacks, actions, and events, including on_use, on_punch, on_place, on_rightclick
---
## Introduction <!-- omit in toc -->
Minetest heavily uses a callback-based modding design. A callback is a function
that you give to an API and is called when an event happens. For example, you
can provide an `on_punch` function in a node definition to be called when a player
punches a node. There are also global callbacks like
`core.register_on_punchnode` to receive events for all nodes.
- [Item Callbacks](#item-callbacks)
- [on_use](#on_use)
- [on_place and on_secondary_use](#on_place-and-on_secondary_use)
- [on_drop](#on_drop)
- [after_use](#after_use)
- [item_place vs place_item](#item_place-vs-place_item)
- [Node Callbacks](#node-callbacks)
- [Right-clicking and placing a node](#right-clicking-and-placing-a-node)
- [Punching and digging](#punching-and-digging)
- [...and more!](#and-more)
## Item Callbacks
When a player has a node, craftitem, or tool in their inventory, they may trigger
certain events:
| Callback | Default binding | Default value |
|------------------|---------------------------|----------------------------------------------|
| on_use | left-click | nil |
| on_place | right-click on a node | `core.item_place` |
| on_secondary_use | right-click not on a node | `core.item_secondary_use` (does nothing) |
| on_drop | Q | `core.item_drop` |
| after_use | digging a node | nil |
### on_use
Having a use callback prevents the item from being used to dig nodes. One common
use of the use callback is for food:
```lua
core.register_craftitem("mymod:mudpie", {
description = "Alien Mud Pie",
inventory_image = "myfood_mudpie.png",
on_use = core.item_eat(20),
})
```
The number supplied to the core.item_eat function is the number of hit
points healed when this food is consumed. Each heart icon the player has is
worth two hitpoints. A player can usually have up to 10 hearts, which is equal
to 20 hitpoints.
core.item_eat() is a function that returns a function, setting it as the
on_use callback. This means the code above is equivalent to this:
```lua
core.register_craftitem("mymod:mudpie", {
description = "Alien Mud Pie",
inventory_image = "myfood_mudpie.png",
on_use = function(...)
return core.do_item_eat(20, nil, ...)
end,
})
```
By understanding how item_eat works by simply returning a function, it's
possible to modify it to do more complex behaviour like playing a custom sound.
### on_place and on_secondary_use
The difference between `on_place` and `on_secondary_use` is that `on_place` is
called when the player is pointing at a node and `on_secondary_use` when the
player isn't.
Both callbacks are called for all types of items. `on_place` defaults to the
`core.item_place` function, which handles calling the `on_rightclick`
callback of the pointed node or placing the wielded item if it is a node.
### on_drop
on_drop is called when the player requests to drop an item, for example using
the drop key (Q) or dragging it outside of the inventory. It defaults to the
`core.item_drop` function, which will handle dropping the item.
### after_use
`after_use` is called when digging a node and allows you to customise how wear
is applied to a tool. If after_use doesn't exist, then it is the same as:
```lua
after_use = function(itemstack, user, node, digparams)
itemstack:add_wear(digparams.wear)
return itemstack
end
```
## item_place vs place_item
Minetest's API includes many different built-in callback implementations for you
to use. These callbacks are named with the item type first, for example,
`core.item_place` and `core.node_dig`. Some callback implementations are
used directly whereas some are functions that return the callback:
```lua
core.register_item("mymod:example", {
on_place = core.item_place,
on_use = core.item_eat(10),
})
```
Minetest's API also includes built-in functions that _do_ something. These are
often named in a confusingly similar way to built-in callback implementations
but have the verb first. Examples include `core.place_item` and
`core.dig_node` - these functions allow you to dig and place nodes with a
similar effect to players.
## Node Callbacks
When a node is in an inventory, it uses Item Callbacks, as discussed above. When
a node is placed in the world, it uses Node Callbacks. There are quite a lot of
node callbacks, too many to discuss in this book. However, quite a few of them
will be talked about later in the book.
Several of the callbacks are related to node operations such as placing and
removing from the world. It's important to note that node operation callbacks
like these aren't called from bulk changes - those that set a large number of
nodes at once - for performance reasons. Therefore, you can't rely on these
callbacks to always be called.
### Right-clicking and placing a node
When the user right-clicks with an item whilst pointing at a node, the item's
`on_place` callback is called. By default, this is set to `core.item_place`.
If the pointed node has an `on_rightclick` callback and sneak (shift) is held,
then the `on_rightclick` callback is called. Otherwise, `core.item_place`
will place the node.
Placing a node will call both `on_construct` and `after_place_node`.
`on_construct` is called by any node set event that wasn't in bulk and is just
given the node's position and value .`after_place_node` is only called by node
place, and so has more information - such as the placer and itemstack.
It's important to note that players aren't the only objects that can place
nodes; it's common for mobs and mods to place nodes. To account for this,
`placer` could be a player, entity, or nil.
```lua
core.register_node("mymod:mynode", {
on_rightclick = function(pos, node, clicker, itemstack, pointed_thing)
if clicker:is_player() then
core.chat_send_player(clicker:get_player_name(), "Hello world!")
end
end,
on_construct = function(pos, node)
local meta = core.get_meta(pos)
meta:set_string("infotext", "My node!")
end,
after_place_node = function(pos, placer, itemstack, pointed_thing)
-- Make sure to check placer
if placer and placer:is_player() then
local meta = core.get_meta(pos)
meta:set_string("owner", placer:get_player_name())
end
end,
})
```
### Punching and digging
Punching is when the player left-clicks for a short period. If the wielded item
has an `on_use` callback, this will be called. Otherwise, the `on_punch`
callback on the pointed node will be called.
When the player attempts to dig a node, the `on_dig` callback on the node will be called.
This defaults to `core.node_dig`, which will check for area protection, wear
out the tool, remove the node, and run the `after_dig_node` callback.
```lua
core.register_node("mymod:mynode", {
on_punch = function(pos, node, puncher, pointed_thing)
if puncher:is_player() then
core.chat_send_player(puncher:get_player_name(), "Ow!")
end
end,
})
```
### ...and more!
Check out Minetest's Lua API reference for a list of all node callbacks, and
more information on the callbacks above.

View File

@ -0,0 +1,98 @@
---
title: Creating Textures
layout: default
root: ../..
idx: 2.2
description: An introduction to making textures in your editor of choice, and a guide on GIMP.
redirect_from: /en/chapters/creating_textures.html
---
## Introduction <!-- omit in toc -->
Being able to create and optimise textures is a very useful skill when
developing for Minetest.
There are many techniques relevant to working on pixel art textures,
and understanding these techniques will greatly improve
the quality of the textures you create.
Detailed approaches to creating good pixel art are outside the scope
of this book, and instead only the most relevant basic techniques
will be covered.
There are many [good online tutorials](http://www.photonstorm.com/art/tutorials-art/16x16-pixel-art-tutorial)
available, which cover pixel art in much more detail.
- [Techniques](#techniques)
- [Using the Pencil](#using-the-pencil)
- [Tiling](#tiling)
- [Transparency](#transparency)
- [Color Palettes](#color-palettes)
- [Editors](#editors)
- [MS Paint](#ms-paint)
- [Aseprite / LibreSprite](#aseprite--libresprite)
- [GIMP](#gimp)
## Techniques
### Using the Pencil
The pencil tool is available in most editors. When set to its lowest size,
it allows you to edit one pixel at a time without changing any other parts
of the image. By manipulating the pixels one at a time, you create clear
and sharp textures without unintended blurring. It also gives you a high
level of precision and control.
### Tiling
Textures used for nodes should generally be designed to tile. This means
when you place multiple nodes with the same texture together, the edges line
up correctly.
<!-- IMAGE NEEDED - cobblestone that tiles correctly -->
If you fail to match the edges correctly, the result is far less pleasing
to look at.
<!-- IMAGE NEEDED - node that doesn't tile correctly -->
### Transparency
Transparency is important when creating textures for nearly all craftitems
and some nodes, such as glass.
Not all editors support transparency, so make sure you choose an
editor which is suitable for the textures you wish to create.
### Color Palettes
Using a consistent color palette is an easy way to make your art look a lot
better. It's a good idea to use one with a limited number of colors, perhaps 32
at most. Premade palettes can be found at
[lospec.com](https://lospec.com/palette-list).
## Editors
### MS Paint
MS Paint is a simple editor which can be useful for basic texture
design; however, it does not support transparency.
This usually won't matter when making textures for the sides of nodes,
but if you need transparency in your textures you should choose a
different editor.
### Aseprite / LibreSprite
[Aseprite](https://www.aseprite.org/) is a proprietary pixel art editor.
It contains a lot of useful features by default such as color palettes and
animation tools.
[LibreSprite](https://libresprite.github.io/) is an open-source fork of Aseprite
from before it went proprietary.
### GIMP
GIMP is commonly used in the Minetest community. It has quite a high
learning curve because many of its features are not immediately
obvious.
When using GIMP, make sure to use the Pencil tool with the Pixel brush and a
size of 1. It's also advisable to select the "Hard edge" checkbox for the Eraser
tool.

356
_ru/items/inventories.md Normal file
View File

@ -0,0 +1,356 @@
---
title: ItemStacks and Inventories
layout: default
root: ../..
idx: 2.4
description: Manipulate InvRefs and ItemStacks
redirect_from:
- /en/chapters/inventories.html
- /en/chapters/itemstacks.html
- /en/inventories/inventories.html
- /en/inventories/itemstacks.html
---
## Introduction <!-- omit in toc -->
In this chapter, you will learn how to use and manipulate inventories, whether
that be a player inventory, a node inventory, or a detached inventory.
- [What are ItemStacks and Inventories?](#what-are-itemstacks-and-inventories)
- [ItemStacks](#itemstacks)
- [Inventory Locations](#inventory-locations)
- [Node Inventories](#node-inventories)
- [Player Inventories](#player-inventories)
- [Detached Inventories](#detached-inventories)
- [Lists](#lists)
- [Size and Width](#size-and-width)
- [Checking Contents](#checking-contents)
- [Modifying Inventories and ItemStacks](#modifying-inventories-and-itemstacks)
- [Adding to a List](#adding-to-a-list)
- [Taking Items](#taking-items)
- [Manipulating Stacks](#manipulating-stacks)
- [Wear](#wear)
- [Lua Tables](#lua-tables)
## What are ItemStacks and Inventories?
An ItemStack is the data behind a single cell in an inventory.
An *inventory* is a collection of *inventory lists*, each of which is a 2D grid
of ItemStacks. Inventory lists are referred to as *lists* in the context of
inventories.
Players and nodes only have a single inventory; lists enable you to have
multiple grids within that inventory. By default, the player has the "main" list
for the bulk of its inventory and a few lists for the crafting system.
## ItemStacks
ItemStacks have four components to them: `name`, `count`, `wear`, and metadata.
The item name may be the item name of a registered item, an alias, or an unknown
item name. Unknown items are common when users uninstall mods, or when mods
remove items without precautions, such as registering aliases.
```lua
print(stack:get_name())
stack:set_name("default:dirt")
if not stack:is_known() then
print("Is an unknown item!")
end
```
The count will always be 0 or greater. Through normal gameplay, the count should
be no more than the maximum stack size of the item - `stack_max`. However, admin
commands and buggy mods may result in stacks exceeding the maximum size.
```lua
print(stack:get_stack_max())
```
An ItemStack can be empty, in which case the count will be 0.
```lua
print(stack:get_count())
stack:set_count(10)
```
ItemStacks can be constructed in multiple ways using the ItemStack function:
```lua
ItemStack() -- name="", count=0
ItemStack("default:pick_stone") -- count=1
ItemStack("default:stone 30")
ItemStack({ name = "default:wood", count = 10 })
```
Item metadata is an unlimited key-value store for data about the item. Key-value
means that you use a name (called the key) to access the data (called the
value). Some keys have special meaning, such as `description` which is used to
have a per-stack item description. This will be covered in more detail in the
[Storage and Metadata](../map/storage.html) chapter.
## Inventory Locations
An Inventory Location is where and how the inventory is stored. There are three
types of inventory location: player, node, and detached. An inventory is
directly tied to one and only one location - updating the inventory will cause
it to update immediately.
### Node Inventories
Node inventories are related to the position of a specific node, such as a
chest. The node must be loaded because it is stored in
[node metadata](../map/storage.html#metadata).
```lua
on_punch = function(pos, node)
local inv = core.get_inventory({ type="node", pos=pos })
-- now use the inventory
end,
```
The above obtains an *inventory reference*, commonly referred to as *InvRef*.
Inventory references are used to manipulate an inventory.
*Reference* means that the data isn't actually stored inside that object,
but the object instead directly updates the data in-place.
The location of an inventory reference can be found like so:
```lua
local location = inv:get_location()
```
### Player Inventories
Player inventories can be obtained similarly or using a player reference.
The player must be online to access their inventory.
```lua
local inv = core.get_inventory({ type="player", name="player1" })
-- or
local inv = player:get_inventory()
```
### Detached Inventories
A detached inventory is one that is independent of players or nodes. Detached
inventories also don't save over a restart.
```lua
local inv = core.get_inventory({
type="detached", name="inventory_name" })
```
Unlike the other types of inventory, you must first create a detached inventory
before accessing it:
```lua
core.create_detached_inventory("inventory_name")
```
The `create_detached_inventory` function accepts 3 arguments, where only the
first - the inventory name - is required. The second argument takes a table of
callbacks, which can be used to control how players interact with the inventory:
```lua
-- Input only detached inventory
core.create_detached_inventory("inventory_name", {
allow_move = function(inv, from_list, from_index, to_list, to_index, count, player)
return count -- allow moving
end,
allow_put = function(inv, listname, index, stack, player)
return stack:get_count() -- allow putting
end,
allow_take = function(inv, listname, index, stack, player)
return 0 -- don't allow taking
end,
on_put = function(inv, listname, index, stack, player)
core.chat_send_all(player:get_player_name() ..
" gave " .. stack:to_string() ..
" to the donation chest from " .. core.pos_to_string(player:get_pos()))
end,
})
```
Permission callbacks - ie: those starting with `allow_` - return the number
of items to transfer, with 0 being used to prevent transfer completely.
On the contrary, action callbacks - starting with `on_` - don't have a return value.
## Lists
Inventory Lists are a concept used to allow multiple grids to be stored inside a
single location. This is especially useful for the player as there are several
common lists that all games have, such as the *main* inventory and *craft*
slots.
### Size and Width
Lists have a size, which is the total number of cells in the grid, and a width,
which is only used within the engine.
The width of the list is not used when drawing the inventory in a window,
because the code behind the window determines the width to use.
```lua
if inv:set_size("main", 32) then
inv:set_width("main", 8)
print("size: " .. inv:get_size("main"))
print("width: " .. inv:get_width("main"))
else
print("Error! Invalid itemname or size to set_size()")
end
```
`set_size` will fail and return false if the listname or size is invalid.
For example, the new size may be too small to fit all the current items
in the inventory.
### Checking Contents
`is_empty` can be used to see if a list contains any items:
```lua
if inv:is_empty("main") then
print("The list is empty!")
end
```
`contains_item` can be used to see if a list contains a specific item:
```lua
if inv:contains_item("main", "default:stone") then
print("I've found some stone!")
end
```
## Modifying Inventories and ItemStacks
### Adding to a List
`add_item` adds items to a list (in this case `"main"`). In the example below,
the maximum stack size is also respected:
```lua
local stack = ItemStack("default:stone 99")
local leftover = inv:add_item("main", stack)
if leftover:get_count() > 0 then
print("Inventory is full! " ..
leftover:get_count() .. " items weren't added")
end
```
### Taking Items
To remove items from a list:
```lua
local taken = inv:remove_item("main", stack)
print("Took " .. taken:get_count())
```
### Manipulating Stacks
You can modify individual stacks by first getting them:
```lua
local stack = inv:get_stack(listname, 0)
```
Then modifying them by setting properties or by using the methods which
respect `stack_size`:
```lua
local stack = ItemStack("default:stone 50")
local to_add = ItemStack("default:stone 100")
local leftover = stack:add_item(to_add)
local taken = stack:take_item(19)
print("Could not add" .. leftover:get_count() .. " of the items.")
-- ^ will be 51
print("Have " .. stack:get_count() .. " items")
-- ^ will be 80
-- min(50+100, stack_max) - 19 = 80
-- where stack_max = 99
```
`add_item` will add items to an ItemStack and return any that could not be added.
`take_item` will take up to the number of items but may take less, and returns the stack taken.
Finally, set the item stack:
```lua
inv:set_stack(listname, 0, stack)
```
## Wear
Tools can have wear; wear shows a progress bar and makes the tool break when completely worn.
Wear is a number out of 65535; the higher it is, the more worn the tool is.
Wear can be manipulated using `add_wear()`, `get_wear()`, and `set_wear(wear)`.
```lua
local stack = ItemStack("default:pick_mese")
local max_uses = 10
-- This is done automatically when you use a tool that digs things
-- It increases the wear of an item by one use.
stack:add_wear(65535 / (max_uses - 1))
```
When digging a node, the amount of wear a tool gets may depend on the node
being dug. So max_uses varies depending on what is being dug.
## Lua Tables
ItemStacks and Inventories can be converted to and from tables.
This is useful for copying and bulk operations.
```lua
-- Entire inventory
local data = inv1:get_lists()
inv2:set_lists(data)
-- One list
local listdata = inv1:get_list("main")
inv2:set_list("main", listdata)
```
The table of lists returned by `get_lists()` will be in this form:
```lua
{
list_one = {
ItemStack,
ItemStack,
ItemStack,
ItemStack,
-- inv:get_size("list_one") elements
},
list_two = {
ItemStack,
ItemStack,
ItemStack,
ItemStack,
-- inv:get_size("list_two") elements
}
}
```
`get_list()` will return a single list as just a list of ItemStacks.
One important thing to note is that the set methods above don't change the size
of the lists.
This means that you can clear a list by setting it to an empty table and it won't
decrease in size:
```lua
inv:set_list("main", {})
```

446
_ru/items/node_drawtypes.md Normal file
View File

@ -0,0 +1,446 @@
---
title: Node Drawtypes
layout: default
root: ../..
idx: 2.3
description: Guide to all drawtypes, including node boxes/nodeboxes and mesh nodes.
redirect_from: /en/chapters/node_drawtypes.html
---
## Introduction <!-- omit in toc -->
The method by which a node is drawn is called a *drawtype*. There are many
available drawtypes. The behaviour of a drawtype can be controlled
by providing properties in the node type definition. These properties
are fixed for all instances of this node. It is possible to control some properties
per-node using something called `param2`.
In the previous chapter, the concept of nodes and items was introduced, but a
full definition of a node wasn't given. The Minetest world is a 3D grid of
positions. Each position is called a node, and consists of the node type
(name) and two parameters (param1 and param2). The function
`core.register_node` is a bit misleading in that it doesn't actually
register a node - it registers a new *type* of node.
The node params are used to control how a node is individually rendered.
`param1` is used to store the lighting of a node, and the meaning of
`param2` depends on the `paramtype2` property of the node type definition.
- [Cubic Nodes: Normal and Allfaces](#cubic-nodes-normal-and-allfaces)
- [Glasslike Nodes](#glasslike-nodes)
- [Glasslike_Framed](#glasslike_framed)
- [Airlike Nodes](#airlike-nodes)
- [Lighting and Sunlight Propagation](#lighting-and-sunlight-propagation)
- [Liquid Nodes](#liquid-nodes)
- [Node Boxes](#node-boxes)
- [Wallmounted Node Boxes](#wallmounted-node-boxes)
- [Mesh Nodes](#mesh-nodes)
- [Signlike Nodes](#signlike-nodes)
- [Plantlike Nodes](#plantlike-nodes)
- [Firelike Nodes](#firelike-nodes)
- [More Drawtypes](#more-drawtypes)
## Cubic Nodes: Normal and Allfaces
<figure class="right_image">
<img src="{{ page.root }}//static/drawtype_normal.png" alt="Normal Drawtype">
<figcaption>
Normal Drawtype
</figcaption>
</figure>
The normal drawtype is typically used to render a cubic node.
If the side of a normal node is against a solid side, then that side won't be rendered,
resulting in a large performance gain.
In contrast, the allfaces drawtype will still render the inner side when up against
a solid node. This is good for nodes with partially transparent sides, such as
leaf nodes. You can use the allfaces_optional drawtype to allow users to opt-out
of the slower drawing, in which case it'll act like a normal node.
```lua
core.register_node("mymod:diamond", {
description = "Alien Diamond",
tiles = {"mymod_diamond.png"},
groups = {cracky = 3},
})
core.register_node("default:leaves", {
description = "Leaves",
drawtype = "allfaces_optional",
tiles = {"default_leaves.png"}
})
```
Note: the normal drawtype is the default drawtype, so you don't need to explicitly
specify it.
## Glasslike Nodes
The difference between glasslike and normal nodes is that placing a glasslike node
next to a normal node won't cause the side of the normal node to be hidden.
This is useful because glasslike nodes tend to be transparent, and so using a normal
drawtype would result in the ability to see through the world.
<figure>
<img src="{{ page.root }}//static/drawtype_glasslike_edges.png" alt="Glasslike's Edges">
<figcaption>
Glasslike's Edges
</figcaption>
</figure>
```lua
core.register_node("default:obsidian_glass", {
description = "Obsidian Glass",
drawtype = "glasslike",
tiles = {"default_obsidian_glass.png"},
paramtype = "light",
is_ground_content = false,
sunlight_propagates = true,
sounds = default.node_sound_glass_defaults(),
groups = {cracky=3,oddly_breakable_by_hand=3},
})
```
### Glasslike_Framed
This makes the node's edge go around the whole thing with a 3D effect, rather
than individual nodes, like the following:
<figure>
<img src="{{ page.root }}//static/drawtype_glasslike_framed.png" alt="Glasslike_framed's Edges">
<figcaption>
Glasslike_Framed's Edges
</figcaption>
</figure>
You can use the glasslike_framed_optional drawtype to allow the user to *opt-in*
to the framed appearance.
```lua
core.register_node("default:glass", {
description = "Glass",
drawtype = "glasslike_framed",
tiles = {"default_glass.png", "default_glass_detail.png"},
inventory_image = core.inventorycube("default_glass.png"),
paramtype = "light",
sunlight_propagates = true, -- Sunlight can shine through block
groups = {cracky = 3, oddly_breakable_by_hand = 3},
sounds = default.node_sound_glass_defaults()
})
```
## Airlike Nodes
These nodes are not rendered and thus have no textures.
```lua
core.register_node("myair:air", {
description = "MyAir (you hacker you!)",
drawtype = "airlike",
paramtype = "light",
sunlight_propagates = true,
walkable = false, -- Would make the player collide with the air node
pointable = false, -- You can't select the node
diggable = false, -- You can't dig the node
buildable_to = true, -- Nodes can replace this node.
-- (you can place a node and remove the air node
-- that used to be there)
air_equivalent = true,
drop = "",
groups = {not_in_creative_inventory=1}
})
```
## Lighting and Sunlight Propagation
The lighting of a node is stored in param1. In order to work out how to shade
a node's side, the light value of the neighbouring node is used.
Because of this, solid nodes don't have light values because they block light.
By default, a node type won't allow light to be stored in any node instances.
It's usually desirable for some nodes such as glass and air to be able to
let light through. To do this, there are two properties which need to be defined:
```lua
paramtype = "light",
sunlight_propagates = true,
```
The first line means that param1 does, in fact, store the light level.
The second line means that sunlight should go through this node without decreasing in value.
## Liquid Nodes
<figure class="right_image">
<img src="{{ page.root }}//static/drawtype_liquid.png" alt="Liquid Drawtype">
<figcaption>
Liquid Drawtype
</figcaption>
</figure>
Each type of liquid requires two node definitions - one for the liquid source, and
another for flowing liquid.
```lua
-- Some properties have been removed as they are beyond
-- the scope of this chapter.
core.register_node("default:water_source", {
drawtype = "liquid",
paramtype = "light",
inventory_image = core.inventorycube("default_water.png"),
-- ^ this is required to stop the inventory image from being animated
tiles = {
{
name = "default_water_source_animated.png",
animation = {
type = "vertical_frames",
aspect_w = 16,
aspect_h = 16,
length = 2.0
}
}
},
special_tiles = {
-- New-style water source material (mostly unused)
{
name = "default_water_source_animated.png",
animation = {type = "vertical_frames", aspect_w = 16,
aspect_h = 16, length = 2.0},
backface_culling = false,
}
},
--
-- Behavior
--
walkable = false, -- The player falls through
pointable = false, -- The player can't highlight it
diggable = false, -- The player can't dig it
buildable_to = true, -- Nodes can be replace this node
alpha = 160,
--
-- Liquid Properties
--
drowning = 1,
liquidtype = "source",
liquid_alternative_flowing = "default:water_flowing",
-- ^ when the liquid is flowing
liquid_alternative_source = "default:water_source",
-- ^ when the liquid is a source
liquid_viscosity = WATER_VISC,
-- ^ how fast
liquid_range = 8,
-- ^ how far
post_effect_color = {a=64, r=100, g=100, b=200},
-- ^ colour of screen when the player is submerged
})
```
Flowing nodes have a similar definition, but with a different name and animation.
See default:water_flowing in the default mod in minetest_game for a full example.
## Node Boxes
<figure class="right_image">
<img src="{{ page.root }}//static/drawtype_nodebox.gif" alt="Nodebox drawtype">
<figcaption>
Nodebox drawtype
</figcaption>
</figure>
Node boxes allow you to create a node which is not cubic, but is instead made out
of as many cuboids as you like.
```lua
core.register_node("stairs:stair_stone", {
drawtype = "nodebox",
paramtype = "light",
node_box = {
type = "fixed",
fixed = {
{-0.5, -0.5, -0.5, 0.5, 0, 0.5},
{-0.5, 0, 0, 0.5, 0.5, 0.5},
},
}
})
```
The most important part is the node box table:
```lua
{-0.5, -0.5, -0.5, 0.5, 0, 0.5},
{-0.5, 0, 0, 0.5, 0.5, 0.5}
```
Each row is a cuboid which are joined to make a single node.
The first three numbers are the co-ordinates, from -0.5 to 0.5 inclusive, of
the bottom front left most corner, the last three numbers are the opposite corner.
They are in the form X, Y, Z, where Y is up.
You can use the [NodeBoxEditor](https://forum.minetest.net/viewtopic.php?f=14&t=2840) to
create node boxes by dragging the edges, it is more visual than doing it by hand.
### Wallmounted Node Boxes
Sometimes you want different nodeboxes for when it is placed on the floor, wall, or ceiling like with torches.
```lua
core.register_node("default:sign_wall", {
drawtype = "nodebox",
node_box = {
type = "wallmounted",
-- Ceiling
wall_top = {
{-0.4375, 0.4375, -0.3125, 0.4375, 0.5, 0.3125}
},
-- Floor
wall_bottom = {
{-0.4375, -0.5, -0.3125, 0.4375, -0.4375, 0.3125}
},
-- Wall
wall_side = {
{-0.5, -0.3125, -0.4375, -0.4375, 0.3125, 0.4375}
}
},
})
```
## Mesh Nodes
Whilst node boxes are generally easier to make, they are limited in that
they can only consist of cuboids. Node boxes are also unoptimised;
Inner faces will still be rendered even when they're completely hidden.
A face is a flat surface on a mesh. An inner face occurs when the faces of two
different node boxes overlap, causing parts of the node box model to be
invisible but still rendered.
You can register a mesh node as so:
```lua
core.register_node("mymod:meshy", {
drawtype = "mesh",
-- Holds the texture for each "material"
tiles = {
"mymod_meshy.png"
},
-- Path to the mesh
mesh = "mymod_meshy.b3d",
})
```
Make sure that the mesh is available in a `models` directory.
Most of the time the mesh should be in your mod's folder, however, it's okay to
share a mesh provided by another mod you depend on. For example, a mod that
adds more types of furniture may want to share the model provided by a basic
furniture mod.
## Signlike Nodes
Signlike nodes are flat nodes with can be mounted on the sides of other nodes.
Despite the name of this drawtype, signs don't actually tend to use signlike but
instead use the `nodebox` drawtype to provide a 3D effect. The `signlike` drawtype
is, however, commonly used by ladders.
```lua
core.register_node("default:ladder_wood", {
drawtype = "signlike",
tiles = {"default_ladder_wood.png"},
-- Required: store the rotation in param2
paramtype2 = "wallmounted",
selection_box = {
type = "wallmounted",
},
})
```
## Plantlike Nodes
<figure class="right_image">
<img src="{{ page.root }}//static/drawtype_plantlike.png" alt="Plantlike Drawtype">
<figcaption>
Plantlike Drawtype
</figcaption>
</figure>
Plantlike nodes draw their tiles in an X like pattern.
```lua
core.register_node("default:papyrus", {
drawtype = "plantlike",
-- Only one texture used
tiles = {"default_papyrus.png"},
selection_box = {
type = "fixed",
fixed = {-6 / 16, -0.5, -6 / 16, 6 / 16, 0.5, 6 / 16},
},
})
```
## Firelike Nodes
Firelike is similar to plantlike, except that it is designed to "cling" to walls
and ceilings.
<figure>
<img src="{{ page.root }}//static/drawtype_firelike.png" alt="Firelike nodes">
<figcaption>
Firelike nodes
</figcaption>
</figure>
```lua
core.register_node("mymod:clingere", {
drawtype = "firelike",
-- Only one texture used
tiles = { "mymod:clinger" },
})
```
## More Drawtypes
This is not a comprehensive list, there are more types including:
* Fencelike
* Plantlike rooted - for underwater plants
* Raillike - for cart tracks
* Torchlike - for 2D wall/floor/ceiling nodes.
The torches in Minetest Game actually use two different node definitions of
mesh nodes (default:torch and default:torch_wall).
As always, read the [Lua API documentation](https://minetest.gitlab.io/minetest/nodes/#node-drawtypes)
for the complete list.

View File

@ -0,0 +1,340 @@
---
title: Блоки, предметы и крафт
layout: default
root: ../..
idx: 2.1
description: Вы узнаете как зарегистрировать новый блок, предмет или рецепт с помощью register_node, register_item и register_craft.
redirect_from: /ru/chapters/nodes_items_crafting.html
---
## Введение <!-- omit in toc -->
Регистрация новых блоков, ингредиентов и рецептов встречается практически
во всех модах.
- [Что такое Блоки и Предметы?](#what-are-nodes-and-items)
- [Регистрация предметов](#registering-items)
- [Названия предметов](#item-names)
- [Псевдонимы](#item-aliases)
- [Текстуры](#textures)
- [Регистрация простого блока](#registering-a-basic-node)
- [Рецепты](#crafting)
- [Shaped](#shaped)
- [Shapeless](#shapeless)
- [Cooking и Fuel](#cooking-and-fuel)
- [Группы](#groups)
- [Инструменты, Возможности, and Способы ломания](#tools-capabilities-and-dig-types)
## Что такое Блоки и Предметы?
Все блоки и инструменты также являются Предметами. Предмет — это то,
что может лежать в инвентаре (даже если правила игры это запрещают).
Блок — это Предмет, который может быть поставлен или найден в мире. В каждом
вокселе мира всегда находится один и только один блок (пустое место — это,
обычно, блок воздуха).
Обычные предметы (не блоки) не могут быть поставлены в мире, а могут только
находиться в инвентаре или выпадать в мир.
Инструмент похож на обычный предмет, но его можно использовать. Когда вы
используете инструмент, шкала его прочности уменьшается до тех пор, пока он
не сломается. Инструменты не могут быть сгруппированы в стак. В будущем,
возможно, Инструменты не будут выделяться в отдельный тип, поскольку различие
между ними и обычными предметами довольно условное.
## Регистрация предметов
Объявление предмета состоит из *названия* и *аттрибутов*.
В аттрибутах записаны различные параметры, влияющие на поведение предмета.
```lua
core.register_craftitem("modname:itemname", {
description = "My Special Item",
inventory_image = "modname_itemname.png"
})
```
### Названия предметов
У каждого предмета есть название для обращения к нему, которое должно иметь
следующий формат:
modname:itemname
`modname` — это имя мода, в котором предмет зарегистрирован, а `itemname` — это
название самого предмета. Название предмета должно быть уникальным и должно
отражать его суть.
Для написания `modname` и `itemname` используются только строчные буквы
английского алфавита, цифры и подчёркивания.
### Псевдонимы
У предметов могут быть *псевдонимы*. Игровой движок работает с псевдонимами
предметов как будто это сами предметы. Псевдонимы используются в двух случаях:
* Чтобы переименовать удалённые предметы на что нибудь другое. На карте или
в инвентаре могут появится *неизвестные блоки* (unknown nodes), если
просто удалить предмет из мода без исправления кода.
* Для сокращения названий. Команда `/giveme dirt` получается короче,
чем `/giveme default:dirt`.
Зарегистрировать псевдоним очень просто. Нужно запомнить правильный порядок
аргументов `from → to`, гле *from* — это псевдоним, а *to* — это сам предмет.
```lua
core.register_alias("dirt", "default:dirt")
```
Моды должны всегд разименовывать псевдонимы перед тем как работать с именем
предмета, потому что движок сам этого не делает. Но это довольно просто:
```lua
itemname = core.registered_aliases[itemname] or itemname
```
### Текстуры
Файлы текстур нужно класть в папку textures/ и давать имена в следующем
формате: `modname_itemname.png`.\\
Можно использовать текстуры в формате JPEG, но они не поддерживают прозрачность
и, обычно, плохо выглядят при низком разрешении. Лучше использовать формат PNG.
Обычно, текстуры в Minetest имеют размер 16 на 16 пикселей. Вообще, разрешение
может быть любым, но рекомендуется использовать значения степени двойки,
например 16, 32, 64 или 128. В противном случае они могут вызвать проблемы
с производительностью на старых компьютерах и особенно телефонах.
## Регистрация простого блока
Блоки регистрируются так же, как и предметы, только другой функцией:
```lua
core.register_node("mymod:diamond", {
description = "Alien Diamond",
tiles = {"mymod_diamond.png"},
is_ground_content = true,
groups = {cracky=3, stone=1}
})
```
При объявлении блока в список аттрибутов добавляются дополнительные свойства,
характерные для блоков.
Свойство `tiles` — это таблица с именами текстур этого блока.
Если указана только одна текстура, она используется для всех сторон блока.
Если нужно указать разные текстуры для сторон, то они должны быть перечислены
в следующем порядке:
сверху (+Y), снизу (-Y), справа (+X), слева (-X), сзади (+Z), спереди (-Z).
(+Y, -Y, +X, -X, +Z, -Z)
Запомните, что +Y это верх в Minetest, как и в большинстве других игр.
```lua
core.register_node("mymod:diamond", {
description = "Alien Diamond",
tiles = {
"mymod_diamond_up.png", -- y+
"mymod_diamond_down.png", -- y-
"mymod_diamond_right.png", -- x+
"mymod_diamond_left.png", -- x-
"mymod_diamond_back.png", -- z+
"mymod_diamond_front.png", -- z-
},
is_ground_content = true,
groups = {cracky = 3},
drop = "mymod:diamond_fragments"
-- ^ Выпадает не сам алмаз, а mymod:diamond_framgents
})
```
Свойство `is_ground_content` позволяет генерировать пещеры в каменной толще.
Это важно для всех блоков, которые должны появляться под землёй в процессе
генерации карты. Пещеры прорезаются после того, как будут сгенерированы все
остальные блоки в данной области.
## Рецепты
Существует несколько типов рецептов, которые указываются через свойство `type`.
* shaped — Ингредиенты должны быть сложены в правильном порядке.
* shapeless — Порядок ингредиентов не важен, важно лишь их наличие
и количество.
* cooking — Можно приготовить в печи.
* fuel — Может быть топливом для печи.
* tool_repair — Можно отремонтировать инструментом.
Рецепты крфта — это не предметы, и поэтому им не нужно указывать имя.
### Shaped
Для рецептов `shaped` важно расположение ингредиентов в сетке крафта.
В примере ниже фрагменты нужно расположить в форме стула, чтобы рецепт
сработал:
```lua
core.register_craft({
type = "shaped",
output = "mymod:diamond_chair 99",
recipe = {
{"mymod:diamond_fragments", "", ""},
{"mymod:diamond_fragments", "mymod:diamond_fragments", ""},
{"mymod:diamond_fragments", "mymod:diamond_fragments", ""}
}
})
```
Обратите внимание на пустую колонку в правой части.
Так обозначается, что *должна* быть пустая колонка справа, иначе рецепт
не будет работать.
Если пустая колонка не требуется, постые строки можно не указывать:
```lua
core.register_craft({
output = "mymod:diamond_chair 99",
recipe = {
{"mymod:diamond_fragments", "" },
{"mymod:diamond_fragments", "mymod:diamond_fragments"},
{"mymod:diamond_fragments", "mymod:diamond_fragments"}
}
})
```
Свойство type можно не указывать, потому что все рецепты по умолчанию
считаются shaped.
### Shapeless
В рецептах `shapeless` не важно расположение ингредиентов, они просто должны
быть.
```lua
core.register_craft({
type = "shapeless",
output = "mymod:diamond 3",
recipe = {
"mymod:diamond_fragments",
"mymod:diamond_fragments",
"mymod:diamond_fragments",
},
})
```
### Cooking и Fuel
Рецепты типа `cooking` предназначены не для сетки крафта, а для приготовления
в различных типах печей.
```lua
core.register_craft({
type = "cooking",
output = "mymod:diamond_fragments",
recipe = "default:coalblock",
cooktime = 10,
})
```
Единственное отличие в коде такого рецепта заключается в том, что поле recipe —
это просто одно свойство, а не таблица. Кроме того, в таких рецептах можно
указывать свойство "cooktime", в котором задаётся время приготовления.
По умоланию, оно равно 3.
Указанный выше рецепт работает, когда во входной слот печи кладут блок угля,
а в слот топлива — какое-нибудь топливо. Через 10 секунд на выходе получается
кусочек алмаза.
Рецепты типа `fuel` дополняют рецепты типа `cooking`. Они указывают, что
можно использовать в качестве топлива в печах.
```lua
core.register_craft({
type = "fuel",
recipe = "mymod:diamond",
burntime = 300,
})
```
У таких рецептов нет свойства output, но есть свойство burntime, определяющее
время горения предмета в печи (в секнудах). В данном примере алмаз получается
хорошим топливом, потому что он горит целых 300 секунд!
## Группы
Предметы можно объединять в группы, и в группах может быть множество предметов.
Принадлежность к группе указывается с помощью аттрибута `groups` в таблице
аттрибутов.
```lua
groups = {cracky = 3, wood = 1}
```
Есть несколько причин указывать группы.
Во первых, группы позволяют указать такие свойства, как способ ломания блока
и его горючесть.
Во вторых, в рецептах крафта в качестве ингредиентов можно указывать группы
вместо конкретных предметов.
```lua
core.register_craft({
type = "shapeless",
output = "mymod:diamond_thing 3",
recipe = {"group:wood", "mymod:diamond"}
})
```
## Инструменты, Возможности, и Способы ломания
Способы ломания — это группы, которые обозначают, насколько блок устойчив
к разным типам инструментов.
Чем большее значение задано группе, тем проще сломать блок данным видом
инструмента.
Можно комбинировать несколько способов ломания, чтобы позволить игрокам
эффективнее использовать разные виды инструментов.
Если не указать никакой из способов ломания, блок нельзя будет сломать.
| Группа | Инструмент | Описание |
|-------------------------|------------|--------------------------------------------------------------------------------------|
| crumbly | лопата | Земля, песок |
| cracky | кирка | Твердый (но хрупкий) материал. Например, камень. |
| snappy | *любой* | Можно сломать чем угодно. Например, листья, неболшие растения, проволока. |
| choppy | топор | Можно разрубить. Например, деревья, доски. |
| fleshy | меч | Живые объекты, такие как животные или игроки.<br>Могут быть эффекты крови при ударе. |
| explody | ? | Может взрываться |
| oddly_breakable_by_hand | *любой* | Факелы и подобные быстро ломаемые вещи |
У каждого инструмента есть Возможности — это список типов ломания
с соответствующими свойствами, такими как время ломания и степень износа.
Кроме того, можно указывать максимально допустимую прочность блоков, которые
можно ломать данным инструментом. Таким образом можно запретить ломать твёрдые
блоки слишком простыми инструментами. Обычно, всем инструментам указывают все
возможные способы ломания, делая неподходящие способы сильно неэффективными.
Если игрок пытается сломать блок инструментом, не поддерживающим такой тип
блоков, то будет применяться свойство рук, а не инструмента.
```lua
core.register_tool("mymod:tool", {
description = "My Tool",
inventory_image = "mymod_tool.png",
tool_capabilities = {
full_punch_interval = 1.5,
max_drop_level = 1,
groupcaps = {
crumbly = {
maxlevel = 2,
uses = 20,
times = { [1]=1.60, [2]=1.20, [3]=0.80 }
},
},
damage_groups = {fleshy=2},
},
})
```
`groupcaps` — это список поддерживаемых Способов ломания.
`damage_groups` позволяет указать, каким образом инструмент повреждает блок,
это будет описано позже в главе "Объекты, Игроки и Сущности".

234
_ru/map/environment.md Normal file
View File

@ -0,0 +1,234 @@
---
title: Basic Map Operations
layout: default
root: ../..
idx: 3.1
description: Basic operations like set_node and get_node
redirect_from: /en/chapters/environment.html
---
## Introduction <!-- omit in toc -->
In this chapter, you will learn how to perform basic actions on the map, such as
adding, removing, and finding nodes.
- [Map Structure](#map-structure)
- [Reading](#reading)
- [Reading Nodes](#reading-nodes)
- [Finding Nodes](#finding-nodes)
- [Writing](#writing)
- [Writing Nodes](#writing-nodes)
- [Removing Nodes](#removing-nodes)
- [Loading Blocks](#loading-blocks)
- [Deleting Blocks](#deleting-blocks)
## Map Structure
The Minetest map is split into MapBlocks, each MapBlocks being a cube of
size 16. As players travel around the map, MapBlocks are created, loaded,
activated, and unloaded. Areas of the map which are not yet loaded are full of
*ignore* nodes, an impassable unselectable placeholder node. Empty space is
full of *air* nodes, an invisible node you can walk through.
An active MapBlock is one which is loaded and has updates running on it.
Loaded map blocks are often referred to as *active blocks*. Active Blocks can be
read from or written to by mods or players, and have active entities. The Engine
also performs operations on the map, such as performing liquid physics.
MapBlocks can either be loaded from the world database or generated. MapBlocks
will be generated up to the map generation limit (`mapgen_limit`) which is set
to its maximum value, 31000, by default. Existing MapBlocks can, however, be
loaded from the world database outside of the generation limit.
## Reading
### Reading Nodes
You can read from the map once you have a position:
```lua
local node = core.get_node({ x = 1, y = 3, z = 4 })
print(dump(node)) --> { name=.., param1=.., param2=.. }
```
If the position is a decimal, it will be rounded to the containing node.
The function will always return a table containing the node information:
* `name` - The node name, which will be *ignore* when the area is unloaded.
* `param1` - See the node definition. This will commonly be light.
* `param2` - See the node definition.
It's worth noting that the function won't load the containing block if the block
is inactive, but will instead return a table with `name` being `ignore`.
You can use `core.get_node_or_nil` instead, which will return `nil` rather
than a table with a name of `ignore`. It still won't load the block, however.
This may still return `ignore` if a block actually contains ignore.
This will happen near the edge of the map as defined by the map generation
limit (`mapgen_limit`).
### Finding Nodes
Minetest offers a number of helper functions to speed up common map actions.
The most commonly used of these are for finding nodes.
For example, say we wanted to make a certain type of plant that grows
better near mese; you would need to search for any nearby mese nodes,
and adapt the growth rate accordingly.
`core.find_node_near` will return the first found node in a certain radius
which matches the node names or groups given. In the following example,
we look for a mese node within 5 nodes of the position:
```lua
local grow_speed = 1
local node_pos = core.find_node_near(pos, 5, { "default:mese" })
if node_pos then
core.chat_send_all("Node found at: " .. dump(node_pos))
grow_speed = 2
end
```
Let's say, for example, that the growth rate increases the more mese there is
nearby. You should then use a function that can find multiple nodes in the area:
```lua
local pos1 = vector.subtract(pos, { x = 5, y = 5, z = 5 })
local pos2 = vector.add(pos, { x = 5, y = 5, z = 5 })
local pos_list =
core.find_nodes_in_area(pos1, pos2, { "default:mese" })
local grow_speed = 1 + #pos_list
```
The above code finds the number of nodes in a *cuboid volume*. This is different
to `find_node_near`, which uses the distance to the position (ie: a *sphere*). In
order to fix this, we will need to manually check the range ourselves:
```lua
local pos1 = vector.subtract(pos, { x = 5, y = 5, z = 5 })
local pos2 = vector.add(pos, { x = 5, y = 5, z = 5 })
local pos_list =
core.find_nodes_in_area(pos1, pos2, { "default:mese" })
local grow_speed = 1
for i=1, #pos_list do
local delta = vector.subtract(pos_list[i], pos)
if delta.x*delta.x + delta.y*delta.y + delta.z*delta.z <= 5*5 then
grow_speed = grow_speed + 1
end
end
```
Now the code will correctly increase `grow_speed` based on mese nodes in range.
Note how we compared the squared distance from the position, rather than square
rooting it to obtain the actual distance. This is because computers find square
roots computationally expensive, so they should avoided as much as possible.
There are more variations of the above two functions, such as
`find_nodes_with_meta` and `find_nodes_in_area_under_air`, which work similarly
and are useful in other circumstances.
## Writing
### Writing Nodes
You can use `set_node` to write to the map. Each call to set_node will cause
lighting to be recalculated and node callbacks to run, which means that set_node
is fairly slow for large numbers of nodes.
```lua
core.set_node({ x = 1, y = 3, z = 4 }, { name = "default:mese" })
local node = core.get_node({ x = 1, y = 3, z = 4 })
print(node.name) --> default:mese
```
set_node will remove any associated metadata or inventory from that position.
This isn't desirable in all circumstances, especially if you're using multiple
node definitions to represent one conceptual node. An example of this is the
furnace node - whilst you conceptually think of it as one node, it's actually
two.
You can set a node without deleting metadata or the inventory like so:
```lua
core.swap_node({ x = 1, y = 3, z = 4 }, { name = "default:mese" })
```
### Removing Nodes
A node must always be present. To remove a node, you set the position to `air`.
The following two lines will both remove a node, and are both identical:
```lua
core.remove_node(pos)
core.set_node(pos, { name = "air" })
```
In fact, remove_node is just a helper function that calls set_node with `"air"`.
## Loading Blocks
You can use `core.emerge_area` to load map blocks. Emerge area is asynchronous,
meaning the blocks won't be loaded instantly. Instead, they will be loaded
soon in the future, and the callback will be called each time.
```lua
-- Load a 20x20x20 area
local halfsize = { x = 10, y = 10, z = 10 }
local pos1 = vector.subtract(pos, halfsize)
local pos2 = vector.add (pos, halfsize)
local context = {} -- persist data between callback calls
core.emerge_area(pos1, pos2, emerge_callback, context)
```
Minetest will call `emerge_callback` whenever it loads a block, with some
progress information.
```lua
local function emerge_callback(pos, action,
num_calls_remaining, context)
-- On first call, record number of blocks
if not context.total_blocks then
context.total_blocks = num_calls_remaining + 1
context.loaded_blocks = 0
end
-- Increment number of blocks loaded
context.loaded_blocks = context.loaded_blocks + 1
-- Send progress message
if context.total_blocks == context.loaded_blocks then
core.chat_send_all("Finished loading blocks!")
else
local perc = 100 * context.loaded_blocks / context.total_blocks
local msg = string.format("Loading blocks %d/%d (%.2f%%)",
context.loaded_blocks, context.total_blocks, perc)
core.chat_send_all(msg)
end
end
```
This is not the only way of loading blocks; using an
[Lua Voxel Manipulator (LVM)](../advmap/lvm.html) will also cause the
encompassed blocks to be loaded synchronously.
## Deleting Blocks
You can use delete_blocks to delete a range of map blocks:
```lua
-- Delete a 20x20x20 area
local halfsize = { x = 10, y = 10, z = 10 }
local pos1 = vector.subtract(pos, halfsize)
local pos2 = vector.add (pos, halfsize)
core.delete_area(pos1, pos2)
```
This will delete all map blocks in that area, *inclusive*. This means that some
nodes will be deleted outside the area as they will be on a mapblock which overlaps
the area bounds.

361
_ru/map/objects.md Normal file
View File

@ -0,0 +1,361 @@
---
title: Objects, Players, and Entities
layout: default
root: ../..
idx: 3.4
description: Using an ObjectRef
degrad:
level: warning
title: Degrees and Radians
message: Attachment rotation is set in degrees, whereas object rotation is in radians.
Make sure to convert to the correct angle measurement.
---
## Introduction <!-- omit in toc -->
In this chapter, you will learn how to manipulate objects and how to define your
own.
- [What are Objects, Players, and Entities?](#what-are-objects-players-and-entities)
- [Position and Velocity](#position-and-velocity)
- [Object Properties](#object-properties)
- [Entities](#entities)
- [Health and Damage](#health-and-damage)
- [Health Points (HP)](#health-points-hp)
- [Punch, Damage Groups, and Armor Groups](#punch-damage-groups-and-armor-groups)
- [Example Damage Calculation](#example-damage-calculation)
- [Attachments](#attachments)
- [Your Turn](#your-turn)
## What are Objects, Players, and Entities?
Players and Entities are both types of Objects. An object is something that can move
independently of the node grid and has properties such as velocity and scale.
Objects aren't items, and they have their own separate registration system.
There are a few differences between Players and Entities.
The biggest one is that Players are player-controlled, whereas Entities are mod-controlled.
This means that the velocity of a player cannot be set by mods - players are client-side,
and entities are server-side.
Another difference is that Players will cause map blocks to be loaded, whereas Entities
will just be saved and become inactive.
This distinction is muddied by the fact that Entities are controlled using a table
which is referred to as a Lua entity, as discussed later.
## Position and Velocity
`get_pos` and `set_pos` exist to allow you to get and set an entity's position.
```lua
local object = core.get_player_by_name("bob")
local pos = object:get_pos()
object:set_pos({ x = pos.x, y = pos.y + 1, z = pos.z })
```
`set_pos` immediately sets the position, with no animation. If you'd like to
smoothly animate an object to the new position, you should use `move_to`.
This, unfortunately, only works for entities.
```lua
object:move_to({ x = pos.x, y = pos.y + 1, z = pos.z })
```
An important thing to think about when dealing with entities is network latency.
In an ideal world, messages about entity movements would arrive immediately,
in the correct order, and with a similar interval as to how you sent them.
However, unless you're in singleplayer, this isn't an ideal world.
Messages will take a while to arrive. Position messages may arrive out of order,
resulting in some `set_pos` calls being skipped as there's no point going to
a position older than the current known position.
Moves may not be similarly spaced, which makes it difficult to use them for animation.
All this results in the client seeing different things to the server, which is something
you need to be aware of.
## Object Properties
Object properties are used to tell the client how to render and deal with an
object. It's not possible to define custom properties, because the properties are
for the engine to use, by definition.
Unlike nodes, objects have a dynamic rather than set appearance.
You can change how an object looks, among other things, at any time by updating
its properties.
```lua
object:set_properties({
visual = "mesh",
mesh = "character.b3d",
textures = {"character_texture.png"},
visual_size = {x=1, y=1},
})
```
The updated properties will be sent to all players in range.
This is very useful to get a large amount of variety very cheaply, such as having
different skins per-player.
As shown in the next section, entities can have initial properties
provided in their definition.
The default Player properties are defined in the engine, however, so you'll
need to use `set_properties()` in `on_joinplayer` to set the properties for newly
joined players.
## Entities
An Entity has a definition table that resembles an item definition table.
This table can contain callback methods, initial object properties, and custom
members.
```lua
local MyEntity = {
initial_properties = {
hp_max = 1,
physical = true,
collide_with_objects = false,
collisionbox = {-0.3, -0.3, -0.3, 0.3, 0.3, 0.3},
visual = "wielditem",
visual_size = {x = 0.4, y = 0.4},
textures = {""},
spritediv = {x = 1, y = 1},
initial_sprite_basepos = {x = 0, y = 0},
},
message = "Default message",
}
function MyEntity:set_message(msg)
self.message = msg
end
```
Entity definitions differ in one very important way from Item definitions.
When an entity is emerged (ie: loaded or created), a new table is created for
that entity that *inherits* from the definition table.
<!--
This inheritance is done using a metatables.
Metatables are an important Lua feature that you will need to be aware of, as it
is an essential part of the Lua language. In layman's terms, a metatable allows
you to control how the table behaves when using certain Lua syntax. The most
common use of metatables is the ability to use another table as a prototype,
defaulting to the other table's properties and methods when they do not exist in
the current table.
Say you want to access `a.x`. If the table `a` has that member, then it will be
returned as normal. However, if the table doesn't have that member and the
metatable lists a table `b` as a prototype, then table `b` will be checked to
see if it has that member.
-->
Both an ObjectRef and an entity table provide ways to get the counterpart:
```lua
local entity = object:get_luaentity()
local object = entity.object
print("entity is at " .. core.pos_to_string(object:get_pos()))
```
There are a number of available callbacks for use with entities.
A complete list can be found in [lua_api.md](https://minetest.gitlab.io/minetest/minetest-namespace-reference/#registered-definition-tables).
```lua
function MyEntity:on_step(dtime)
local pos = self.object:get_pos()
local pos_down = vector.subtract(pos, vector.new(0, 1, 0))
local delta
if core.get_node(pos_down).name == "air" then
delta = vector.new(0, -1, 0)
elseif core.get_node(pos).name == "air" then
delta = vector.new(0, 0, 1)
else
delta = vector.new(0, 1, 0)
end
delta = vector.multiply(delta, dtime)
self.object:move_to(vector.add(pos, delta))
end
function MyEntity:on_punch(hitter)
core.chat_send_player(hitter:get_player_name(), self.message)
end
```
Now, if you were to spawn and use this entity, you'd notice that the message
would be forgotten when the entity becomes inactive then active again.
This is because the message isn't saved.
Rather than saving everything in the entity table, Minetest gives you control over
how to save things.
Staticdata is a string which contains all the custom information that
needs to stored.
```lua
function MyEntity:get_staticdata()
return core.write_json({
message = self.message,
})
end
function MyEntity:on_activate(staticdata, dtime_s)
if staticdata ~= "" and staticdata ~= nil then
local data = core.parse_json(staticdata) or {}
self:set_message(data.message)
end
end
```
Minetest may call `get_staticdata()` as many times as it wants and at any time.
This is because Minetest doesn't wait for a MapBlock to become inactive to save
it, as this would result in data loss. MapBlocks are saved roughly every 18
seconds, so you should notice a similar interval for `get_staticdata()` being called.
`on_activate()`, on the other hand, will only be called when an entity becomes
active either from the MapBlock becoming active or from the entity spawning.
This means that staticdata could be empty.
Finally, you need to register the type table using the aptly named `register_entity`.
```lua
core.register_entity("mymod:entity", MyEntity)
```
The entity can be spawned by a mod like so:
```lua
local pos = { x = 1, y = 2, z = 3 }
local obj = core.add_entity(pos, "mymod:entity", nil)
```
The third parameter is the initial staticdata.
To set the message, you can use the entity table method:
```lua
obj:get_luaentity():set_message("hello!")
```
Players with the *give* [privilege](../players/privileges.html) can
use a [chat command](../players/chat.html) to spawn entities:
/spawnentity mymod:entity
## Health and Damage
### Health Points (HP)
Each object has a Health Points (HP) number, which represents the current health.
Players have a maximum hp set using the `hp_max` object property.
An object will die if its hp reaches 0.
```lua
local hp = object:get_hp()
object:set_hp(hp + 3)
```
### Punch, Damage Groups, and Armor Groups
Damage is the reduction of an object's HP. An object can *punch* another object to
inflict damage. A punch isn't necessarily an actual punch - it can be an
explosion, a sword slash, or something else.
The total damage is calculated by multiplying the punch's damage groups with the
target's vulnerabilities. This is then limited depending on how recent the last
punch was. We will go over an example of this calculation in a bit.
Just like [node dig groups](../items/nodes_items_crafting.html#tools-capabilities-and-dig-types),
these groups can take any name and do not need to be registered. However, it's
common to use the same group names as with node digging.
How vulnerable an object is to particular types of damage depends on its
`armor_groups`. Despite its misleading name, `armor_groups` specify the
percentage damage taken from particular damage groups, not the resistance. If a
damage group is not listed in an object's armor groups, that object is
completely invulnerable to it.
```lua
target:set_armor_groups({
fleshy = 90,
crumbly = 50,
})
```
In the above example, the object will take 90% of `fleshy` damage and 50% of
`crumbly` damage.
When a player punches an object, the damage groups come from the item they are
currently wielding. In other cases, mods decide which damage groups are used.
### Example Damage Calculation
Let's punch the `target` object:
```lua
local tool_capabilities = {
full_punch_interval = 0.8,
damage_groups = { fleshy = 5, choppy = 10 },
-- This is only used for digging nodes, but is still required
max_drop_level=1,
groupcaps={
fleshy={times={[1]=2.5, [2]=1.20, [3]=0.35}, uses=30, maxlevel=2},
},
}
local time_since_last_punch = tool_capabilities.full_punch_interval
target:punch(object, time_since_last_punch, tool_capabilities)
```
Now, let's work out what the damage will be. The punch's damage groups are
`fleshy=5` and `choppy=10`, and `target` will take 90% damage from fleshy and 0%
from choppy.
First, we multiply the damage groups by the vulnerability and sum the result.
We then multiply by a number between 0 or 1 depending on the `time_since_last_punch`.
```lua
= (5*90/100 + 10*0/100) * limit(time_since_last_punch / full_punch_interval, 0, 1)
= (5*90/100 + 10*0/100) * 1
= 4.5
```
As HP is an integer, the damage is rounded to 5 points.
## Attachments
Attached objects will move when the parent - the object they are attached to -
is moved. An attached object is said to be a child of the parent.
An object can have an unlimited number of children, but at most one parent.
```lua
child:set_attach(parent, bone, position, rotation)
```
An object's `get_pos()` will always return the global position of the object, no
matter whether it is attached or not.
`set_attach` takes a relative position, but not as you'd expect.
The attachment position is relative to the parent's origin as scaled up by 10 times.
So, `0,5,0` would be half a node above the parent's origin.
{% include notice.html notice=page.degrad %}
For 3D models with animations, the bone argument is used to attach the entity
to a bone.
3D animations are based on skeletons - a network of bones in the model where
each bone can be given a position and rotation to change the model, for example,
to move the arm.
Attaching to a bone is useful if you want to make a character hold something:
```lua
obj:set_attach(player,
"Arm_Right", -- default bone
{x=0.2, y=6.5, z=3}, -- default position
{x=-100, y=225, z=90}) -- default rotation
```
## Your Turn
* Make a windmill by combining nodes and an entity.
* Make a mob of your choice (using just the entity API, and without using any other mods).

247
_ru/map/storage.md Normal file
View File

@ -0,0 +1,247 @@
---
title: Storage and Metadata
layout: default
root: ../..
idx: 3.3
description: Mod Storage, NodeMetaRef (get_meta).
redirect_from:
- /en/chapters/node_metadata.html
- /en/map/node_metadata.html
---
## Introduction <!-- omit in toc -->
In this chapter, you will learn how you can store data.
- [Metadata](#metadata)
- [What is Metadata?](#what-is-metadata)
- [Obtaining a Metadata Object](#obtaining-a-metadata-object)
- [Reading and Writing](#reading-and-writing)
- [Special Keys](#special-keys)
- [Storing Tables](#storing-tables)
- [Private Metadata](#private-metadata)
- [Lua Tables](#lua-tables)
- [Mod Storage](#mod-storage)
- [Databases](#databases)
- [Deciding Which to Use](#deciding-which-to-use)
- [Your Turn](#your-turn)
## Metadata
### What is Metadata?
In Minetest, Metadata is a key-value store used to attach custom data to something.
You can use metadata to store information against a Node, Player, or ItemStack.
Each type of metadata uses the exact same API.
Metadata stores values as strings, but there are a number of methods to
convert and store other primitive types.
Some keys in metadata may have special meaning.
For example, `infotext` in node metadata is used to store the tooltip which shows
when hovering over the node using the crosshair.
To avoid conflicts with other mods, you should use the standard namespace
convention for keys: `modname:keyname`.
The exception is for conventional data such as the owner name which is stored as
`owner`.
Metadata is data about data.
The data itself, such as a node's type or an stack's count, is not metadata.
### Obtaining a Metadata Object
If you know the position of a node, you can retrieve its metadata:
```lua
local meta = core.get_meta({ x = 1, y = 2, z = 3 })
```
Player and ItemStack metadata are obtained using `get_meta()`:
```lua
local pmeta = player:get_meta()
local imeta = stack:get_meta()
```
### Reading and Writing
In most cases, `get_<type>()` and `set_<type>()` methods will be used to read
and write to meta.
Metadata stores strings, so the string methods will directly set and get the value.
```lua
print(meta:get_string("foo")) --> ""
meta:set_string("foo", "bar")
print(meta:get_string("foo")) --> "bar"
```
All of the typed getters will return a neutral default value if the key doesn't
exist, such as `""` or `0`.
You can use `get()` to return a string or nil.
As Metadata is a reference, any changes will be updated to the source automatically.
ItemStacks aren't references however, so you'll need to update the itemstack in the
inventory.
The non-typed getters and setters will convert to and from strings:
```lua
print(meta:get_int("count")) --> 0
meta:set_int("count", 3)
print(meta:get_int("count")) --> 3
print(meta:get_string("count")) --> "3"
```
### Special Keys
`infotext` is used in Node Metadata to show a tooltip when hovering the crosshair over a node.
This is useful when showing the ownership or status of a node.
`description` is used in ItemStack Metadata to override the description when
hovering over the stack in an inventory.
You can use colours by encoding them with `core.colorize()`.
`owner` is a common key used to store the username of the player that owns the
item or node.
### Storing Tables
Tables must be converted to strings before they can be stored.
Minetest offers two formats for doing this: Lua and JSON.
The Lua method tends to be a lot faster and matches the format Lua
uses for tables, while JSON is a more standard format, is better
structured, and is well suited for when you need to exchange information
with another program.
```lua
local data = { username = "player1", score = 1234 }
meta:set_string("foo", core.serialize(data))
data = core.deserialize(meta:get_string("foo"))
```
### Private Metadata
By default, all node metadata is sent to the client.
You can mark keys as private to prevent this.
```lua
meta:set_string("secret", "asd34dn")
meta:mark_as_private("secret")
```
### Lua Tables
You can convert to and from Lua tables using `to_table` and `from_table`:
```lua
local tmp = meta:to_table()
tmp.foo = "bar"
meta:from_table(tmp)
```
## Mod Storage
Mod storage uses the exact same API as Metadata, although it's not technically
Metadata.
Mod storage is per-mod, and can only be obtained during load time in order to
know which mod is requesting it.
```lua
local storage = core.get_mod_storage()
```
You can now manipulate the storage just like metadata:
```lua
storage:set_string("foo", "bar")
```
## Databases
If the mod is likely to be used on a server and will store lots of data,
it's a good idea to offer a database storage method.
You should make this optional by separating how the data is stored and where
it is used.
```lua
local backend
if use_database then
backend =
dofile(core.get_modpath("mymod") .. "/backend_sqlite.lua")
else
backend =
dofile(core.get_modpath("mymod") .. "/backend_storage.lua")
end
backend.get_foo("a")
backend.set_foo("a", { score = 3 })
```
The backend_storage.lua file should include a mod storage implementation:
```lua
local storage = core.get_mod_storage()
local backend = {}
function backend.set_foo(key, value)
storage:set_string(key, core.serialize(value))
end
function backend.get_foo(key)
return core.deserialize(storage:get_string(key))
end
return backend
```
The backend_sqlite would do a similar thing, but use the Lua *lsqlite3* library
instead of mod storage.
Using a database such as SQLite requires using an insecure environment.
An insecure environment is a table that is only available to mods
explicitly whitelisted by the user, and it contains a less restricted
copy of the Lua API which could be abused if available to malicious mods.
Insecure environments will be covered in more detail in the
[Security](../quality/security.html) chapter.
```lua
local ie = core.request_insecure_environment()
assert(ie, "Please add mymod to secure.trusted_mods in the settings")
local _sql = ie.require("lsqlite3")
-- Prevent other mods from using the global sqlite3 library
if sqlite3 then
sqlite3 = nil
end
```
Teaching about SQL or how to use the lsqlite3 library is out of scope for this book.
## Deciding Which to Use
The type of method you use depends on what the data is about,
how it is formatted, and how large it is.
As a guideline, small data is up to 10K, medium data is up to 10MB, and large
data is any size above that.
Node metadata is a good choice when you need to store node-related data.
Storing medium data is fairly efficient if you make it private.
Item metadata should not be used to store anything but small amounts of data as it is not
possible to avoid sending it to the client.
The data will also be copied every time the stack is moved, or accessed from Lua.
Mod storage is good for medium data but writing large data may be inefficient.
It's better to use a database for large data to avoid having to write all the
data out on every save.
Databases are only viable for servers due to the
need to whitelist the mod to access an insecure environment.
They're well suited for large data sets.
## Your Turn
* Make a node which disappears after it has been punched five times.
(Use `on_punch` in the node definition and `core.set_node`.)

110
_ru/map/timers.md Normal file
View File

@ -0,0 +1,110 @@
---
title: Node Timers and ABMs
layout: default
root: ../..
idx: 3.2
description: Learn how to make ABMs to change blocks.
redirect_from:
- /en/chapters/abms.html
- /en/map/abms.html
---
## Introduction <!-- omit in toc -->
Periodically running a function on certain nodes is a common task.
Minetest provides two methods of doing this: Active Block Modifiers (ABMs) and node timers.
ABMs scan all loaded MapBlocks looking for nodes that match a criteria.
They are best suited for nodes which are frequently found in the world,
such as grass.
They have a high CPU overhead, but a low memory and storage overhead.
For nodes that are uncommon or already use metadata, such as furnaces
and machines, node timers should be used instead.
Node timers work by keeping track of pending timers in each MapBlock, and then
running them when they expire.
This means that timers don't need to search all loaded nodes to find matches,
but instead require slightly more memory and storage for the tracking
of pending timers.
- [Node Timers](#node-timers)
- [Active Block Modifiers](#active-block-modifiers)
- [Your Turn](#your-turn)
## Node Timers
Node timers are directly tied to a single node.
You can manage node timers by obtaining a NodeTimerRef object.
```lua
local timer = core.get_node_timer(pos)
timer:start(10.5) -- in seconds
```
When a node timer is up, the `on_timer` method in the node's definition table will
be called. The method only takes a single parameter, the position of the node:
```lua
core.register_node("autodoors:door_open", {
on_timer = function(pos)
core.set_node(pos, { name = "autodoors:door" })
return false
end
})
```
Returning true in `on_timer` will cause the timer to run again for the same interval.
It's also possible to use `get_node_timer(pos)` inside of `on_timer`, just make
sure you return false to avoid conflict.
You may have noticed a limitation with timers: for optimisation reasons, it's
only possible to have one type of timer per node type, and only one timer running per node.
## Active Block Modifiers
Alien grass, for the purposes of this chapter, is a type of grass which
has a chance to appear near water.
```lua
core.register_node("aliens:grass", {
description = "Alien Grass",
light_source = 3, -- The node radiates light. Min 0, max 14
tiles = {"aliens_grass.png"},
groups = {choppy=1},
on_use = core.item_eat(20)
})
core.register_abm({
nodenames = {"default:dirt_with_grass"},
neighbors = {"default:water_source", "default:water_flowing"},
interval = 10.0, -- Run every 10 seconds
chance = 50, -- One node has a chance of 1 in 50 to get selected
action = function(pos, node, active_object_count,
active_object_count_wider)
local pos = {x = pos.x, y = pos.y + 1, z = pos.z}
core.set_node(pos, {name = "aliens:grass"})
end
})
```
This ABM runs every ten seconds, and for each matching node, there is
a 1 in 50 chance of it running.
If the ABM runs on a node, an alien grass node is placed above it.
Please be warned, this will delete any node previously located in that position.
To prevent this you should include a check using core.get_node to make sure there is space for the grass.
Specifying a neighbour is optional.
If you specify multiple neighbours, only one of them needs to be
present to meet the requirements.
Specifying chance is also optional.
If you don't specify the chance, the ABM will always run when the other conditions are met.
## Your Turn
* Midas touch: Make water turn to gold blocks with a 1 in 100 chance, every 5 seconds.
* Decay: Make wood turn into dirt when water is a neighbour.
* Burnin': Make every air node catch on fire. (Tip: "air" and "fire:basic_flame").
Warning: expect the game to crash.

197
_ru/players/chat.md Normal file
View File

@ -0,0 +1,197 @@
---
title: Chat and Commands
layout: default
root: ../..
idx: 4.2
description: Registering a chatcommand and handling chat messages with register_on_chat_message
redirect_from: /en/chapters/chat.html
cmd_online:
level: warning
title: Offline players can run commands
message: |
A player name is passed instead of a player object because mods
can run commands on behalf of offline players. For example, the IRC
bridge allows players to run commands without joining the game.
So make sure that you don't assume that the player is online.
You can check by seeing if `core.get_player_by_name` returns a player.
cb_cmdsprivs:
level: warning
title: Privileges and Chat Commands
message: |
The shout privilege isn't needed for a player to trigger this callback.
This is because chat commands are implemented in Lua, and are just
chat messages that begin with a /.
---
## Introduction <!-- omit in toc -->
Mods can interact with player chat, including
sending messages, intercepting messages, and registering chat commands.
- [Sending Messages](#sending-messages)
- [To All Players](#to-all-players)
- [To Specific Players](#to-specific-players)
- [Chat Commands](#chat-commands)
- [Accepting Multiple Arguments](#accepting-multiple-arguments)
- [Using string.split](#using-stringsplit)
- [Using Lua patterns](#using-lua-patterns)
- [Intercepting Messages](#intercepting-messages)
## Sending Messages
### To All Players
To send a message to every player in the game, call the `chat_send_all` function.
```lua
core.chat_send_all("This is a chat message to all players")
```
Here is an example of how this appears in-game:
<player1> Look at this entrance
This is a chat message to all players
<player2> What about it?
The message appears on a separate line to distinguish it from in-game player chat.
### To Specific Players
To send a message to a specific player, call the `chat_send_player` function:
```lua
core.chat_send_player("player1", "This is a chat message for player1")
```
This message displays in the same manner as messages to all players, but is
only visible to the named player, in this case, player1.
## Chat Commands
To register a chat command, for example `/foo`, use `register_chatcommand`:
```lua
core.register_chatcommand("foo", {
privs = {
interact = true,
},
func = function(name, param)
return true, "You said " .. param .. "!"
end,
})
```
In the above snippet, `interact` is listed as a required
[privilege](privileges.html) meaning that only players with the `interact` privilege can run the command.
`param` is a string containing everything a player writes after the chatcommand
name. For example, if a user types `/grantme one,two,three` then `param` will be
`one,two,three`.
Chat commands can return up to two values,
the first being a Boolean indicating success, and the second being a
message to send to the user.
{% include notice.html notice=page.cmd_online %}
### Accepting Multiple Arguments
<a name="complex-subcommands"></a>
`param` gives you all the arguments to a chat command in a single string. It's
common for chat commands to need to extract multiple arguments. There are two
ways of doing this, either using Minetest's string split or Lua patterns.
#### Using string.split
A string can be split up into words using `string.split(" ")`:
```lua
local parts = param:split(" ")
local cmd = parts[1]
if cmd == "join" then
local team_name = parts[2]
team.join(name, team_name)
return true, "Joined team!"
elseif cmd == "max_users" then
local team_name = parts[2]
local max_users = tonumber(parts[3])
if team_name and max_users then
return true, "Set max users of team " .. team_name .. " to " .. max_users
else
return false, "Usage: /team max_users <team_name> <number>"
end
else
return false, "Command needed"
end
```
#### Using Lua patterns
[Lua patterns](https://www.lua.org/pil/20.2.html) are a way of extracting stuff
from text using rules. They're best suited for when there are arguments that can
contain spaces or more control is needed on how parameters are captured.
```lua
local to, msg = param:match("^([%a%d_-]+) (.+)$")
```
The above code implements `/msg <to> <message>`. Let's go through left to right:
* `^` means match the start of the string.
* `()` is a matching group - anything that matches stuff in here will be
returned from string.match.
* `[]` means accept characters in this list.
* `%a` means accept any letter and `%d` means accept any digit.
* `[%a%d_-]` means accept any letter or digit or `_` or `-`.
* `+` means match the thing before one or more times.
* `.` means match any character in this context.
* `$` means match the end of the string.
Put simply, the pattern matches the name (a word with only letters/numbers/-/_),
then a space, then the message (one or more of any character). The name and
message are returned, because they're surrounded by parentheses.
That's how most mods implement complex chat commands. A better guide to Lua
Patterns would probably be the
[lua-users.org tutorial](http://lua-users.org/wiki/PatternsTutorial)
or the [PIL documentation](https://www.lua.org/pil/20.2.html).
## Intercepting Messages
To intercept a message, use register_on_chat_message:
```lua
core.register_on_chat_message(function(name, message)
print(name .. " said " .. message)
return false
end)
```
By returning false, you allow the chat message to be sent by the default
handler. You can actually remove the line `return false` and it would still
work the same, because `nil` is returned implicitly and is treated like false.
{% include notice.html notice=page.cb_cmdsprivs %}
You should make sure you take into account that it may be a chat command,
or the user may not have `shout`.
```lua
core.register_on_chat_message(function(name, message)
if message:sub(1, 1) == "/" then
print(name .. " ran chat command")
elseif core.check_player_privs(name, { shout = true }) then
print(name .. " said " .. message)
else
print(name .. " tried to say " .. message ..
" but doesn't have shout")
end
return false
end)
```

379
_ru/players/formspecs.md Normal file
View File

@ -0,0 +1,379 @@
---
title: GUIs (Formspecs)
layout: default
root: ../..
idx: 4.5
description: Learn how to display GUIs using formspecs
redirect_from: /en/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.
---
## Introduction <!-- omit in toc -->
<figure class="right_image">
<img src="{{ page.root }}//static/formspec_example.png" alt="Furnace Inventory">
<figcaption>
Screenshot of furnace formspec, labelled.
</figcaption>
</figure>
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 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)](hud.html) elements instead of forms, because
unexpected windows tend to disrupt gameplay.
- [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)
## Real or Legacy Coordinates
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.
Using a formspec_version of 2 or above will enable real coordinates.
## 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]
Elements are items such as text boxes or buttons, or can be metadata such
as size or background. You should refer to
[lua_api.md](https://minetest.gitlab.io/minetest/formspec/)
for a list of all possible elements.
### Header
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.
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 the Lua API reference.
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:
formspec_version[4]
size[2,2]
Notice how we explicitly defined the formspec language version.
Without this, the legacy system will instead be used instead - which will
prevent the use of consistent element positioning and other new features.
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:
formspec_version[4]
size[2,2]
position[0,0.5]
anchor[0,0.5]
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.
## 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;", core.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)
core.show_formspec(name, "guessing:game", guessing.get_formspec(name))
end
core.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
core.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()
core.chat_send_all(pname .. " guessed " .. fields.number)
end
end)
```
The function given in `core.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, depending on the event. Some elements will only be submitted if they caused
the event, such as buttons, and some elements will always appear in submissions,
such as fields.
{% 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 core.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
core.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)
core.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
`core.show_formspec` is not the only way to show a formspec; you can also
add formspecs to a [node's metadata](../map/storage.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
core.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 = core.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 `core.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. Minetest Game uses
[SFINV](https://github.com/rubenwardy/sfinv/blob/master/Tutorial.md).
### 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.

294
_ru/players/hud.md Normal file
View File

@ -0,0 +1,294 @@
---
title: HUD
layout: default
root: ../..
idx: 4.6
description: Learn how to display HUD elements
redirect_from: /en/chapters/hud.html
---
## Introduction <!-- omit in toc -->
Heads Up Display (HUD) elements allow you to show text, images, and other graphical elements.
The HUD doesn't accept user input; for that, you should use a [formspec](formspecs.html).
- [Positioning](#positioning)
- [Position and Offset](#position-and-offset)
- [Alignment](#alignment)
- [Scoreboard](#scoreboard)
- [Text Elements](#text-elements)
- [Parameters](#parameters)
- [Our Example](#our-example)
- [Image Elements](#image-elements)
- [Parameters](#parameters-1)
- [Scale](#scale)
- [Changing an Element](#changing-an-element)
- [Storing IDs](#storing-ids)
- [Other Elements](#other-elements)
## Positioning
### Position and Offset
<figure class="right_image">
<img
width="300"
src="{{ page.root }}//static/hud_diagram_center.svg"
alt="Diagram showing a centered text element">
</figure>
Screens come in a variety of different physical sizes and resolutions, and
the HUD needs to work well on all screen types.
Minetest's solution to this is to specify the location of an element using both
a percentage position and an offset.
The percentage position is relative to the screen size, so to place an element
in the centre of the screen, you would need to provide a percentage position of half
the screen, e.g. (50%, 50%), and an offset of (0, 0).
The offset is then used to move an element relative to the percentage position.
<div style="clear:both;"></div>
### Alignment
Alignment is where the result of position and offset is on the element -
for example, `{x = -1.0, y = 0.0}` will make the result of position and offset point
to the left of the element's bounds. This is particularly useful when you want to
make a text element aligned to the left, centre, or right.
<figure>
<img
width="500"
src="{{ page.root }}//static/hud_diagram_alignment.svg"
alt="Diagram showing alignment">
</figure>
The above diagram shows 3 windows (blue), each with a single HUD element (yellow)
and a different alignment each time. The arrow is the result of the position
and offset calculation.
### Scoreboard
For the purposes of this chapter, you will learn how to position and update a
score panel like so:
<figure>
<img
src="{{ page.root }}//static/hud_final.png"
alt="screenshot of the HUD we're aiming for">
</figure>
In the above screenshot, all the elements have the same percentage position
(100%, 50%) - but different offsets. This allows the whole thing to be anchored
to the right of the window, but to resize without breaking.
## Text Elements
You can create a HUD element once you have a copy of the player object:
```lua
local player = core.get_player_by_name("username")
local idx = player:hud_add({
hud_elem_type = "text",
position = {x = 0.5, y = 0.5},
offset = {x = 0, y = 0},
text = "Hello world!",
alignment = {x = 0, y = 0}, -- center aligned
scale = {x = 100, y = 100}, -- covered later
})
```
The `hud_add` function returns an element ID - this can be used later to modify
or remove a HUD element.
### Parameters
The element's type is given using the `hud_elem_type` property in the definition
table. The meaning of other properties varies based on this type.
`scale` is the maximum bounds of text; text outside these bounds is cropped, e.g.: `{x=100, y=100}`.
`number` is the text's colour, and is in [hexadecimal form](http://www.colorpicker.com/), e.g.: `0xFF0000`.
### Our Example
Let's go ahead and place all the text in our score panel:
```lua
-- Get the dig and place count from storage, or default to 0
local meta = player:get_meta()
local digs_text = "Digs: " .. meta:get_int("score:digs")
local places_text = "Places: " .. meta:get_int("score:places")
player:hud_add({
hud_elem_type = "text",
position = {x = 1, y = 0.5},
offset = {x = -120, y = -25},
text = "Stats",
alignment = 0,
scale = { x = 100, y = 30},
number = 0xFFFFFF,
})
player:hud_add({
hud_elem_type = "text",
position = {x = 1, y = 0.5},
offset = {x = -180, y = 0},
text = digs_text,
alignment = -1,
scale = { x = 50, y = 10},
number = 0xFFFFFF,
})
player:hud_add({
hud_elem_type = "text",
position = {x = 1, y = 0.5},
offset = {x = -70, y = 0},
text = places_text,
alignment = -1,
scale = { x = 50, y = 10},
number = 0xFFFFFF,
})
```
This results in the following:
<figure>
<img
src="{{ page.root }}//static/hud_text.png"
alt="screenshot of the HUD we're aiming for">
</figure>
## Image Elements
Image elements are created in a very similar way to text elements:
```lua
player:hud_add({
hud_elem_type = "image",
position = {x = 1, y = 0.5},
offset = {x = -220, y = 0},
text = "score_background.png",
scale = { x = 1, y = 1},
alignment = { x = 1, y = 0 },
})
```
You will now have this:
<figure>
<img
src="{{ page.root }}//static/hud_background_img.png"
alt="screenshot of the HUD so far">
</figure>
### Parameters
The `text` field is used to provide the image name.
If a co-ordinate is positive, then it is a scale factor with 1 being the
original image size, 2 being double the size, and so on.
However, if a co-ordinate is negative, it is a percentage of the screen size.
For example, `x=-100` is 100% of the width.
### Scale
Let's make the progress bar for our score panel as an example of scale:
```lua
local percent = tonumber(meta:get("score:score") or 0.2)
player:hud_add({
hud_elem_type = "image",
position = {x = 1, y = 0.5},
offset = {x = -215, y = 23},
text = "score_bar_empty.png",
scale = { x = 1, y = 1},
alignment = { x = 1, y = 0 },
})
player:hud_add({
hud_elem_type = "image",
position = {x = 1, y = 0.5},
offset = {x = -215, y = 23},
text = "score_bar_full.png",
scale = { x = percent, y = 1},
alignment = { x = 1, y = 0 },
})
```
We now have a HUD that looks like the one in the first post!
There is one problem however, it won't update when the stats change.
## Changing an Element
You can use the ID returned by the `hud_add` method to update it or remove it later.
```lua
local idx = player:hud_add({
hud_elem_type = "text",
text = "Hello world!",
-- parameters removed for brevity
})
player:hud_change(idx, "text", "New Text")
player:hud_remove(idx)
```
The `hud_change` method takes the element ID, the property to change, and the new
value. The above call changes the `text` property from "Hello World" to "New text".
This means that doing the `hud_change` immediately after the `hud_add` is
functionally equivalent to the following, in a rather inefficient way:
```lua
local idx = player:hud_add({
hud_elem_type = "text",
text = "New Text",
})
```
## Storing IDs
```lua
score = {}
local saved_huds = {}
function score.update_hud(player)
local player_name = player:get_player_name()
-- Get the dig and place count from storage, or default to 0
local meta = player:get_meta()
local digs_text = "Digs: " .. meta:get_int("score:digs")
local places_text = "Places: " .. meta:get_int("score:places")
local percent = tonumber(meta:get("score:score") or 0.2)
local ids = saved_huds[player_name]
if ids then
player:hud_change(ids["places"], "text", places_text)
player:hud_change(ids["digs"], "text", digs_text)
player:hud_change(ids["bar_foreground"],
"scale", { x = percent, y = 1 })
else
ids = {}
saved_huds[player_name] = ids
-- create HUD elements and set ids into `ids`
end
end
core.register_on_joinplayer(score.update_hud)
core.register_on_leaveplayer(function(player)
saved_huds[player:get_player_name()] = nil
end)
```
## Other Elements
Read [lua_api.md](https://minetest.gitlab.io/minetest/hud/) for a complete list of HUD elements.

View File

@ -0,0 +1,77 @@
---
title: Player Physics
layout: default
root: ../..
idx: 4.4
description: Learn how to make a player run faster, jump higher or simply float
redirect_from: /en/chapters/player_physics.html
---
## Introduction <!-- omit in toc -->
Player physics can be modified using physics overrides.
Physics overrides can set the walking speed, jump speed,
and gravity constants.
Physics overrides are set on a player-by-player basis
and are multipliers.
For example, a value of 2 for gravity would make gravity twice as strong.
- [Basic Example](#basic-example)
- [Available Overrides](#available-overrides)
- [Old Movement Behaviour](#old-movement-behaviour)
- [Mod Incompatibility](#mod-incompatibility)
- [Your Turn](#your-turn)
## Basic Example
Here is an example of how to add an antigravity command, which
puts the caller in low G:
```lua
core.register_chatcommand("antigravity", {
func = function(name, param)
local player = core.get_player_by_name(name)
player:set_physics_override({
gravity = 0.1, -- set gravity to 10% of its original value
-- (0.1 * 9.81)
})
end,
})
```
## Available Overrides
`player:set_physics_override()` is given a table of overrides.\\
According to [lua_api.md](https://minetest.gitlab.io/minetest/class-reference/#player-only-no-op-for-other-objects),
these can be:
* speed: multiplier to default walking speed value (default: 1)
* jump: multiplier to default jump value (default: 1)
* gravity: multiplier to default gravity value (default: 1)
* sneak: whether the player can sneak (default: true)
### Old Movement Behaviour
Player movement prior to the 0.4.16 release included the sneak glitch, which
allows various movement glitches, including the ability
to climb an 'elevator' made from a certain placement of nodes by sneaking
(pressing shift) and pressing space to ascend. Though the behaviour was
unintended, it has been preserved in overrides due to its use on many servers.
Two overrides are needed to fully restore old movement behaviour:
* new_move: whether the player uses new movement (default: true)
* sneak_glitch: whether the player can use 'sneak elevators' (default: false)
## Mod Incompatibility
Please be warned that mods which override the same physics value of a player tend
to be incompatible with each other. When setting an override, it overwrites
any overrides that have been set before. This means that if multiple overrides set a
player's speed, only the last one to run will be in effect.
## Your Turn
* **Sonic**: Set the speed multiplier to a high value (at least 6) when a player joins the game.
* **Super bounce**: Increase the jump value so that the player can jump 20 metres (1 metre is 1 node).
* **Space**: Make gravity decrease as the player gets higher.

138
_ru/players/privileges.md Normal file
View File

@ -0,0 +1,138 @@
---
title: Privileges
layout: default
root: ../..
idx: 4.1
description: Registering privs.
redirect_from: /en/chapters/privileges.html
---
## Introduction <!-- omit in toc -->
Privileges, often called privs for short, give players the ability to perform
certain actions. Server owners can grant and revoke privileges to control
which abilities each player has.
- [When to use Privileges](#when-to-use-privileges)
- [Declaring Privileges](#declaring-privileges)
- [Checking for Privileges](#checking-for-privileges)
- [Getting and Setting Privileges](#getting-and-setting-privileges)
- [Adding Privileges to basic_privs](#adding-privileges-to-basicprivs)
## When to use Privileges
A privilege should give a player the ability to do something.
Privileges are **not** for indicating class or status.
**Good Privileges:**
* interact
* shout
* noclip
* fly
* kick
* ban
* vote
* worldedit
* area_admin - admin functions of one mod is ok
**Bad Privileges:**
* moderator
* admin
* elf
* dwarf
## Declaring Privileges
Use `register_privilege` to declare a new privilege:
```lua
core.register_privilege("vote", {
description = "Can vote on issues",
give_to_singleplayer = true
})
```
`give_to_singleplayer` defaults to true when not specified, so it isn't
actually needed in the above definition.
## Checking for Privileges
To quickly check whether a player has all the required privileges:
```lua
local has, missing = core.check_player_privs(player_or_name, {
interact = true,
vote = true })
```
In this example, `has` is true if the player has all the privileges needed.
If `has` is false, then `missing` will contain a key-value table
of the missing privileges.
```lua
local has, missing = core.check_player_privs(name, {
interact = true,
vote = true })
if has then
print("Player has all privs!")
else
print("Player is missing privs: " .. dump(missing))
end
```
If you don't need to check the missing privileges, you can put
`check_player_privs` directly into the if statement.
```lua
if not core.check_player_privs(name, { interact=true }) then
return false, "You need interact for this!"
end
```
## Getting and Setting Privileges
Player privileges can be accessed or modified regardless of the player
being online.
```lua
local privs = core.get_player_privs(name)
print(dump(privs))
privs.vote = true
core.set_player_privs(name, privs)
```
Privileges are always specified as a key-value table with the key being
the privilege name and the value being a boolean.
```lua
{
fly = true,
interact = true,
shout = true
}
```
## Adding Privileges to basic_privs
Players with the `basic_privs` privilege are able to grant and revoke a limited
set of privileges. It's common to give this privilege to moderators so that
they can grant and revoke `interact` and `shout`, but can't grant themselves or other
players privileges with greater potential for abuse such as `give` and `server`.
To add a privilege to `basic_privs`, and adjust which privileges your moderators can
grant and revoke from other players, you must change the `basic_privs` setting.
By default, `basic_privs` has the following value:
basic_privs = interact, shout
To add `vote`, update this to:
basic_privs = interact, shout, vote
This will allow players with `basic_privs` to grant and revoke the `vote` privilege.

5
_ru/players/sfinv.md Normal file
View File

@ -0,0 +1,5 @@
---
sitemap: false
redirect_from: /en/chapters/sfinv.html
redirect_to: "https://github.com/rubenwardy/sfinv/blob/master/Tutorial.md"
---

253
_ru/quality/clean_arch.md Normal file
View File

@ -0,0 +1,253 @@
---
title: Intro to Clean Architectures
layout: default
root: ../..
idx: 8.4
---
## Introduction <!-- omit in toc -->
Once your mod reaches a respectable size, you'll find it harder and harder to
keep the code clean and free of bugs. This is an especially big problem when using
a dynamically typed language like Lua, given that the compiler gives you very little
compiler-time help when it comes to things like making sure that types are used correctly.
This chapter covers important concepts needed to keep your code clean,
and common design patterns to achieve that. Please note that this chapter isn't
meant to be prescriptive, but to instead give you an idea of the possibilities.
There is no one good way of designing a mod, and good mod design is very subjective.
- [Cohesion, Coupling, and Separation of Concerns](#cohesion-coupling-and-separation-of-concerns)
- [Observer](#observer)
- [Model-View-Controller](#model-view-controller)
- [API-View](#api-view)
- [Conclusion](#conclusion)
## Cohesion, Coupling, and Separation of Concerns
Without any planning, a programming project will tend to gradually descend into
spaghetti code. Spaghetti code is characterised by a lack of structure - all the
code is thrown in together with no clear boundaries. This ultimately makes a
project completely unmaintainable, ending in its abandonment.
The opposite of this is to design your project as a collection of interacting
smaller programs or areas of code. <!-- Weird wording? -->
> Inside every large program, there is a small program trying to get out.
>
> --C.A.R. Hoare
This should be done in such a way that you achieve Separation of Concerns -
each area should be distinct and address a separate need or concern.
These programs/areas should have the following two properties:
* **High Cohesion** - the area should be closely/tightly related.
* **Low Coupling** - keep dependencies between areas as low as possible, and avoid
relying on internal implementations. It's a very good idea to make sure you have
a low amount of coupling, as this means that changing the APIs of certain areas
will be more feasible.
Note that these apply both when thinking about the relationship between mods,
and the relationship between areas inside a mod.
## Observer
A simple way to separate different areas of code is to use the Observer pattern.
Let's take the example of unlocking an achievement when a player first kills a
rare animal. The naïve approach would be to have achievement code in the mob
kill function, checking the mob name and unlocking the award if it matches.
This is a bad idea, however, as it makes the mobs mod coupled to the achievements
code. If you kept on doing this - for example, adding XP to the mob death code -
you could end up with a lot of messy dependencies.
Enter the Observer pattern. Instead of the mymobs mod caring about awards,
the mymobs mod exposes a way for other areas of code to register their
interest in an event and receive data about the event.
```lua
mymobs.registered_on_death = {}
function mymobs.register_on_death(func)
table.insert(mymobs.registered_on_death, func)
end
-- in mob death code
for i=1, #mymobs.registered_on_death do
mymobs.registered_on_death[i](entity, reason)
end
```
Then the other code registers its interest:
```lua
mymobs.register_on_death(function(mob, reason)
if reason.type == "punch" and reason.object and
reason.object:is_player() then
awards.notify_mob_kill(reason.object, mob.name)
end
end)
```
You may be thinking - wait a second, this looks awfully familiar. And you're right!
The Minetest API is heavily Observer-based to stop the engine having to care about
what is listening to something.
## Model-View-Controller
In the next chapter, we will discuss how to automatically test your
code and one of the problems we will have is how to separate your logic
(calculations, what should be done) from API calls (`core.*`, other mods)
as much as possible.
One way to do this is to think about:
* What **data** you have.
* What **actions** you can take with this data.
* How **events** (ie: formspec, punches, etc) trigger these actions, and how
these actions cause things to happen in the engine.
Let's take an example of a land protection mod. The data you have is the areas
and any associated metadata. Actions you can take are `create`, `edit`, or
`delete`. The events that trigger these actions are chat commands and formspec
receive fields. These are 3 areas that can usually be separated pretty well.
In your tests, you will be able to make sure that an action when triggered does
the right thing to the data. You won't need to test that an event calls an
action (as this would require using the Minetest API, and this area of code
should be made as small as possible anyway.)
You should write your data representation using Pure Lua. "Pure" in this context
means that the functions could run outside of Minetest - none of the engine's
functions are called.
```lua
-- Data
function land.create(name, area_name)
land.lands[area_name] = {
name = area_name,
owner = name,
-- more stuff
}
end
function land.get_by_name(area_name)
return land.lands[area_name]
end
```
Your actions should also be pure, but calling other functions is more
acceptable than in the above.
```lua
-- Controller
function land.handle_create_submit(name, area_name)
-- process stuff
-- (ie: check for overlaps, check quotas, check permissions)
land.create(name, area_name)
end
function land.handle_creation_request(name)
-- This is a bad example, as explained later
land.show_create_formspec(name)
end
```
Your event handlers will have to interact with the Minetest API. You should keep
the number of calculations to a minimum, as you won't be able to test this area
very easily.
```lua
-- View
function land.show_create_formspec(name)
-- Note how there's no complex calculations here!
return [[
size[4,3]
label[1,0;This is an example]
field[0,1;3,1;area_name;]
button_exit[0,2;1,1;exit;Exit]
]]
end
core.register_chatcommand("/land", {
privs = { land = true },
func = function(name)
land.handle_creation_request(name)
end,
})
core.register_on_player_receive_fields(function(player,
formname, fields)
land.handle_create_submit(player:get_player_name(),
fields.area_name)
end)
```
The above is the Model-View-Controller pattern. The model is a collection of data
with minimal functions. The view is a collection of functions which listen to
events and pass it to the controller, and also receives calls from the controller to
do something with the Minetest API. The controller is where the decisions and
most of the calculations are made.
The controller should have no knowledge about the Minetest API - notice how
there are no Minetest calls or any view functions that resemble them.
You should *NOT* have a function like `view.hud_add(player, def)`.
Instead, the view defines some actions that the controller can tell the view to do,
like `view.add_hud(info)` where info is a value or table which doesn't relate
to the Minetest API at all.
<figure class="right_image">
<img
width="100%"
src="{{ page.root }}/static/mvc_diagram.svg"
alt="Diagram showing a centered text element">
</figure>
It is important that each area only communicates with its direct neighbours,
as shown above, in order to reduce how much you need to change if you modify
an area's internals or externals. For example, to change the formspec you
would only need to edit the view. To change the view API, you would only need to
change the view and the controller, but not the model at all.
In practice, this design is rarely used because of the increased complexity
and because it doesn't give many benefits for most types of mods. Instead,
you will commonly see a less formal and strict kind of design -
variants of the API-View.
### API-View
In an ideal world, you'd have the above 3 areas perfectly separated with all
events going into the controller before going back to the normal view. But
this isn't the real world. A good compromise is to reduce the mod into two
parts:
* **API** - This was the model and controller above. There should be no uses of
`core.` here.
* **View** - This was also the view above. It's a good idea to structure this into separate
files for each type of event.
rubenwardy's [crafting mod](https://github.com/rubenwardy/crafting) roughly
follows this design. `api.lua` is almost all pure Lua functions handling the data
storage and controller-style calculations. `gui.lua` is the view for formspecs
and formspec submission, and `async_crafter.lua` is the view and controller for
a node formspec and node timers.
Separating the mod like this means that you can very easily test the API part,
as it doesn't use any Minetest APIs - as shown in the
[next chapter](unit_testing.html) and seen in the crafting mod.
## Conclusion
Good code design is subjective, and highly depends on the project you're making. As a
general rule, try to keep cohesion high and coupling low. Phrased differently,
keep related code together and unrelated code apart, and keep dependencies simple.
I highly recommend reading the [Game Programming Patterns](http://gameprogrammingpatterns.com/)
book. It's freely available to [read online](http://gameprogrammingpatterns.com/contents.html)
and goes into much more detail on common programming patterns relevant to games.

View File

@ -0,0 +1,140 @@
---
title: Common Mistakes
layout: default
root: ../..
idx: 8.1
redirect_from: /en/chapters/common_mistakes.html
---
## Introduction <!-- omit in toc -->
This chapter details common mistakes, and how to avoid them.
- [Be Careful When Storing ObjectRefs (ie: players or entities)](#be-careful-when-storing-objectrefs-ie-players-or-entities)
- [Don't Trust Formspec Submissions](#dont-trust-formspec-submissions)
- [Set ItemStacks After Changing Them](#set-itemstacks-after-changing-them)
## Be Careful When Storing ObjectRefs (ie: players or entities)
An ObjectRef is invalidated when the player or entity it represents leaves
the game. This may happen when the player goes offline, or the entity is unloaded
or removed.
The methods of ObjectRefs will always return nil when invalid, since Minetest 5.2.
Any call will essentially be ignored.
You should avoid storing ObjectRefs where possible. If you do to store an
ObjectRef, you should make sure you check it before use, like so:
```lua
-- This only works in Minetest 5.2+
if obj:get_pos() then
-- is valid!
end
```
## Don't Trust Formspec Submissions
Malicious clients can submit formspecs whenever they like, with
whatever content they like.
For example, the following code has a vulnerability which allows players to
give themselves moderator privileges:
```lua
local function show_formspec(name)
if not core.check_player_privs(name, { privs = true }) then
return false
end
core.show_formspec(name, "modman:modman", [[
size[3,2]
field[0,0;3,1;target;Name;]
button_exit[0,1;3,1;sub;Promote]
]])
return true
})
core.register_on_player_receive_fields(function(player,
formname, fields)
-- BAD! Missing privilege check here!
local privs = core.get_player_privs(fields.target)
privs.kick = true
privs.ban = true
core.set_player_privs(fields.target, privs)
return true
end)
```
Add a privilege check to solve this:
```lua
core.register_on_player_receive_fields(function(player,
formname, fields)
if not core.check_player_privs(name, { privs = true }) then
return false
end
-- code
end)
```
## Set ItemStacks After Changing Them
Have you noticed that it's simply called an `ItemStack` in the API, not an `ItemStackRef`,
similar to `InvRef`? This is because an `ItemStack` isn't a reference - it's a
copy. Stacks work on a copy of the data rather than the stack in the inventory.
This means that modifying a stack won't actually modify that stack in the inventory.
For example, don't do this:
```lua
local inv = player:get_inventory()
local stack = inv:get_stack("main", 1)
stack:get_meta():set_string("description", "Partially eaten")
-- BAD! Modification will be lost
```
Do this instead:
```lua
local inv = player:get_inventory()
local stack = inv:get_stack("main", 1)
stack:get_meta():set_string("description", "Partially eaten")
inv:set_stack("main", 1, stack)
-- Correct! Item stack is set
```
The behaviour of callbacks is slightly more complicated. Modifying an `ItemStack` you
are given will change it for the caller too, and any subsequent callbacks. However,
it will only be saved in the engine if the callback caller sets it.
```lua
core.register_on_item_eat(function(hp_change, replace_with_item,
itemstack, user, pointed_thing)
itemstack:get_meta():set_string("description", "Partially eaten")
-- Almost correct! Data will be lost if another
-- callback cancels the behaviour
end)
```
If no callbacks cancel this, the stack will be set and the description will be updated,
but if a callback does cancel this, then the update may be lost.
It's better to do this instead:
```lua
core.register_on_item_eat(function(hp_change, replace_with_item,
itemstack, user, pointed_thing)
itemstack:get_meta():set_string("description", "Partially eaten")
user:get_inventory():set_stack("main", user:get_wield_index(),
itemstack)
-- Correct, description will always be set!
end)
```
If the callbacks cancel or the callback runner doesn't set the stack,
then the update will still be set.
If the callbacks or the callback runner set the stack, then the use of
set_stack doesn't matter.

107
_ru/quality/luacheck.md Normal file
View File

@ -0,0 +1,107 @@
---
title: Automatic Error Checking
layout: default
root: ../..
idx: 8.2
description: Use LuaCheck to find errors
redirect_from: /en/chapters/luacheck.html
---
## Introduction <!-- omit in toc -->
In this chapter, you will learn how to use a tool called LuaCheck to automatically
scan your mod for any mistakes. This tool can be used in combination with your
editor to provide alerts to any mistakes.
- [Installing LuaCheck](#installing-luacheck)
- [Windows](#windows)
- [Linux](#linux)
- [Running LuaCheck](#running-luacheck)
- [Configuring LuaCheck](#configuring-luacheck)
- [Troubleshooting](#troubleshooting)
- [Using with editor](#using-with-editor)
## Installing LuaCheck
### Windows
Simply download luacheck.exe from
[the Github Releases page](https://github.com/mpeterv/luacheck/releases).
### Linux
First, you'll need to install LuaRocks:
sudo apt install luarocks
You can then install LuaCheck globally:
sudo luarocks install luacheck
Check that it's installed with the following command:
luacheck -v
## Running LuaCheck
The first time you run LuaCheck, it will probably pick up a lot of false
errors. This is because it still needs to be configured.
On Windows, open powershell or bash in the root folder of your project
and run `path\to\luacheck.exe .`
On Linux, run `luacheck .` whilst in the root folder of your project.
## Configuring LuaCheck
Create a file called .luacheckrc in the root of your project. This could be the
root of your game, modpack, or mod.
Put the following contents in it:
```lua
unused_args = false
allow_defined_top = true
globals = {
"minetest",
}
read_globals = {
string = {fields = {"split"}},
table = {fields = {"copy", "getn"}},
-- Builtin
"vector", "ItemStack",
"dump", "DIR_DELIM", "VoxelArea", "Settings",
-- MTG
"default", "sfinv", "creative",
}
```
Next, you'll need to test that it works by running LuaCheck. You should get a lot
fewer errors this time. Starting at the first error you get, modify the code to
remove the issue, or modify the configuration if the code is correct. See the list
below.
### Troubleshooting
* **accessing undefined variable foobar** - If `foobar` is meant to be a global,
add it to `read_globals`. Otherwise, add any missing `local`s to the mod.
* **setting non-standard global variable foobar** - If `foobar` is meant to be a global,
add it to `globals`. Remove from `read_globals` if present.
Otherwise, add any missing `local`s to the mod.
* **mutating read-only global variable 'foobar'** - Move `foobar` from `read_globals` to
`globals`, or stop writing to foobar.
## Using with editor
It is highly recommended that you find and install a plugin for your editor of choice
to show you errors without running a command. Most editors will likely have a plugin
available.
* **VSCode** - Ctrl+P, then paste: `ext install dwenegar.vscode-luacheck`
* **Sublime** - Install using package-control:
[SublimeLinter](https://github.com/SublimeLinter/SublimeLinter),
[SublimeLinter-luacheck](https://github.com/SublimeLinter/SublimeLinter-luacheck).

26
_ru/quality/readmore.md Normal file
View File

@ -0,0 +1,26 @@
---
title: Read More
layout: default
root: ../..
idx: 8.7
redirect_from: /en/chapters/readmore.html
---
## List of Resources
After you've read this book, take a look at the following.
### Minetest Modding
* Minetest's Lua API Reference - [multiple page version](https://minetest.gitlab.io/minetest/class-reference/#player-only-no-op-for-other-objects) |
[single page version](https://github.com/minetest/minetest/blob/master/doc/lua_api.md).
* Look at [existing mods](https://forum.minetest.net/viewforum.php?f=11).
### Lua Programming
* [Programming in Lua (PIL)](http://www.lua.org/pil/).
### 3D Modelling
* [Blender 3D: Noob to pro](https://en.wikibooks.org/wiki/Blender_3D:_Noob_to_Pro).
* [Using Blender with Minetest](http://wiki.minetest.net/Using_Blender).

166
_ru/quality/releasing.md Normal file
View File

@ -0,0 +1,166 @@
---
title: Releasing a Mod
layout: default
root: ../..
idx: 8.6
redirect_from: /en/chapters/releasing.html
---
## Introduction <!-- omit in toc -->
Releasing, or publishing, a mod allows other people to make use of it. Once a mod has been
released it might be used in singleplayer games or on servers, including public servers.
- [Choosing a License](#choosing-a-license)
- [LGPL and CC-BY-SA](#lgpl-and-cc-by-sa)
- [CC0](#cc0)
- [MIT](#mit)
- [Packaging](#packaging)
- [README.txt](#readmetxt)
- [mod.conf / game.conf](#modconf--gameconf)
- [screenshot.png](#screenshotpng)
- [Uploading](#uploading)
- [Version Control Systems](#version-control-systems)
- [Releasing on ContentDB](#releasing-on-contentdb)
- [Forum Topic](#forum-topic)
## Choosing a License
You need to specify a license for your mod. This is important because it tells other
people the ways in which they are allowed to use your work. If your mod doesn't have
a license, people won't know whether they are allowed to modify, distribute or use your
mod on a public server.
Your code and your art need different things from the licenses they use. For example,
Creative Commons licenses shouldn't be used with source code,
but can be suitable choices for artistic works such as images, text and meshes.
You are allowed any license; however, mods which disallow derivatives are banned from the
official Minetest forum. (For a mod to be allowed on the forum, other developers must be
able to modify it and release the modified version.)
Please note that **public domain is not a valid licence**, because the definition varies
in different countries.
It is important to note that WTFPL is
[strongly discouraged](https://content.minetest.net/help/wtfpl/) and people may
choose not to use your mod if it has this license.
### LGPL and CC-BY-SA
This is a common license combination in the Minetest community, and is what
Minetest and Minetest Game use.
You license your code under LGPL 2.1 and your art under CC-BY-SA.
This means that:
* Anyone can modify, redistribute and sell modified or unmodified versions.
* If someone modifies your mod, they must give their version the same license.
* Your copyright notice must be kept.
### CC0
This license can be used for both code and art, and allows anyone to do what
they want with your work. This means they can modify, redistribute, sell, or
leave-out attribution.
### MIT
This is a common license for code. The only restriction it places on users
of your code is that they must include the same copyright notice and license
in any copies of the code or of substantial parts of the code.
## Packaging
There are some files that are recommended to include in your mod or game
before you release it.
### README.txt
The README file should state:
* What the mod/game does, how to use it.
* What the license is.
* Optionally:
* where to report problems or get help.
* credits
### mod.conf / game.conf
Make sure you add a description key to explain what your mod or game does. Be
concise without being vague. It should be short because it will be displayed in
the content installer which has limited space.
Good example:
description = Adds soup, cakes, bakes and juices.
Avoid this:
description = The food mod for Minetest. (<-- BAD! It's vague)
### screenshot.png
Screenshots should be 3:2 (3 pixels of width for every 2 pixels of height)
and have a minimum size of 300 x 200px.
The screenshot is displayed inside of Minetest as a thumbnail for the content.
## Uploading
So that a potential user can download your mod, you need to upload it somewhere
publicly accessible. There are several ways to do this, but you should use the
approach that works best for you, as long as it meets these requirements, and any
others which may be added by forum moderators:
* **Stable** - The hosting website should be unlikely to shut down without warning.
* **Direct link** - You should be able to click a link and download the file
without having to view another page.
* **Virus Free** - Scammy upload hosts may contain insecure adverts.
ContentDB allows you to upload zip files, and meets these criteria.
### Version Control Systems
A Version Control System (VCS) is software that manages changes to software,
often making it easier to distribute and receive contributed changes.
The majority of Minetest modders use Git and a website like GitHub to distribute
their code.
Using git can be difficult at first. If you need help with this please see:
* [Pro Git book](http://git-scm.com/book/en/v1/Getting-Started) - Free to read online.
## Releasing on ContentDB
ContentDB is the official place to find and distribute content such as mods,
games, and texture packs. Users can find content using the website, or download
and install using the integration built into the Minetest main menu.
Sign up to [ContentDB](https://content.minetest.net) and add your content.
Make sure to read the guidance given in the Help section.
## Forum Topic
You can also create a forum topic to let users discuss your creation.
Mod topics should be created in ["WIP Mods"](https://forum.minetest.net/viewforum.php?f=9) (Work In Progress)
forum, and Game topics in the ["WIP Games"](https://forum.minetest.net/viewforum.php?f=50) forum.
When you no longer consider your mod a work in progress, you can
[request that it be moved](https://forum.minetest.net/viewtopic.php?f=11&t=10418)
to "Mod Releases."
The forum topic should contain similar content to the README, but should
be more promotional and also include a link to download the mod.
It's a good idea to include screenshots of your mod in action, if possible.
The subject of topic must be in one of these formats:
* [Mod] Mod Title [modname]
* [Mod] Mod Title [version number] [modname]
For example:
* [Mod] More Blox [0.1] [moreblox]

110
_ru/quality/security.md Normal file
View File

@ -0,0 +1,110 @@
---
title: Security
layout: default
root: ../..
idx: 8.3
---
## Introduction <!-- omit in toc -->
Security is very important in making sure that your mod doesn't cause the server
owner to lose data or control.
- [Core Concepts](#core-concepts)
- [Formspecs](#formspecs)
- [Never Trust Submissions](#never-trust-submissions)
- [Time of Check isn't Time of Use](#time-of-check-isnt-time-of-use)
- [(Insecure) Environments](#insecure-environments)
## Core Concepts
The most important concept in security is to **never trust the user**.
Anything the user submits should be treated as malicious.
This means that you should always check that the information they
enter is valid, that the user has the correct permissions,
and that they are otherwise allowed to do that action
(ie: in range or an owner).
A malicious action isn't necessarily the modification or destruction of data,
but can be accessing sensitive data, such as password hashes or
private messages.
This is especially bad if the server stores information such as emails or ages,
which some may do for verification purposes.
## Formspecs
### Never Trust Submissions
Any users can submit almost any formspec with any values at any time.
Here's some real code found in a mod:
```lua
core.register_on_player_receive_fields(function(player,
formname, fields)
for key, field in pairs(fields) do
local x,y,z = string.match(key,
"goto_([%d-]+)_([%d-]+)_([%d-]+)")
if x and y and z then
player:set_pos({ x=tonumber(x), y=tonumber(y),
z=tonumber(z) })
return true
end
end
end
```
Can you spot the problem? A malicious user could submit a formspec containing
their own position values, allowing them to teleport to anywhere they wish to.
This could even be automated using client modifications to essentially replicate
the `/teleport` command with no need for a privilege.
The solution for this kind of issue is to use a
[Context](../players/formspecs.html#contexts), as shown previously in
the Formspecs chapter.
### Time of Check isn't Time of Use
Any users can submit any formspec with any values at any time, except where the
engine forbids it:
* A node formspec submission will be blocked if the user is too far away.
* From 5.0 onward, named formspecs will be blocked if they haven't been shown yet.
This means that you should check in the handler that the user meets the
conditions for showing the formspec in the first place, as well as any
corresponding actions.
The vulnerability caused by checking for permissions in the show formspec but not
in the handle formspec is called Time Of Check is not Time Of Use (TOCTOU).
## (Insecure) Environments
Minetest allows mods to request an unsandboxed environment, giving them access
to the full Lua API.
Can you spot the vulnerability in the following?
```lua
local ie = core.request_insecure_environment()
ie.os.execute(("path/to/prog %d"):format(3))
```
`string.format` is a function in the global shared table `string`.
A malicious mod could override this function and pass stuff to os.execute:
```lua
string.format = function()
return "xdg-open 'http://example.com'"
end
```
The mod could pass something much more malicious than opening a website, such
as giving a remote user control over the machine.
Some rules for using an insecure environment:
* Always store it in a local and never pass it into a function.
* Make sure you can trust any input given to an insecure function, to avoid the
issue above. This means avoiding globally redefinable functions.

198
_ru/quality/translations.md Normal file
View File

@ -0,0 +1,198 @@
---
title: Translation (i18n / l10n)
layout: default
root: ../..
idx: 8.05
marked_text_encoding:
level: info
title: Marked Text Encoding
message: |
You don't need to know the exact format of marked text, but it might help
you understand.
```
"\27(T@mymod)Hello everyone!\27E"
```
* `\27` is the escape character - it's used to tell Minetest to pay attention as
something special is coming up. This is used for both translations and text
colorisation.
* `(T@mymod)` says that the following text is translatable using the `mymod`
textdomain.
* `Hello everyone!` is the translatable text in English, as passed to the
translator function.
* `\27E` is the escape character again and `E`, used to signal that the end has
been reached.
---
## Introduction <!-- omit in toc -->
Adding support for translation to your mods and games allows more people to
enjoy them. According to Google Play, 64% of Minetest Android users don't have
English as their primary language. Minetest doesn't track stats for user
languages across all platforms, but there's likely to be a high proportion of
non-English speaking users.
Minetest allows you to translate your mods and games into different languages by
writing your text in English, and using translation files to map into other
languages. Translation is done on each player's client, allowing each player to
see a different language.
- [How does client-side translation work?](#how-does-client-side-translation-work)
- [Marked up text](#marked-up-text)
- [Translation files](#translation-files)
- [Format strings](#format-strings)
- [Best practices and Common Falsehoods about Translation](#best-practices-and-common-falsehoods-about-translation)
- [Server-side translations](#server-side-translations)
- [Conclusion](#conclusion)
## How does client-side translation work?
### Marked up text
The server needs to tell clients how to translate text. This is done by placing
control characters in text, telling Minetest where and how to translate
text. This is referred to as marked up text, and will be discussed more later.
To mark text as translatable, use a translator function (`S()`), obtained using
`core.get_translator(textdomain)`:
```lua
local S = core.get_translator("mymod")
core.register_craftitem("mymod:item", {
description = S("My Item"),
})
```
The first argument of `get_translator` is the `textdomain`, which acts as a
namespace. Rather than having all translations for a language stored in the same
file, translations are separated into textdomains, with a file per textdomain
per language. The textdomain should be the same as the mod name, as it helps
avoid mod conflicts.
Marked up text can be used in most places where human-readable text is accepted,
including formspecs, item def fields, infotext, and more. When including marked
text in formspecs, you need to escape the text using `core.formspec_escape`.
When the client encounters translatable text, such as that passed to
`description`, it looks it up in the player's language's translation file. If a
translation cannot be found, it falls back to the English translation.
Translatable marked up text contains the English source text, the textdomain,
and any additional arguments passed to `S()`. It's essentially a text encoding
of the `S` call, containing all the required information.
Another type of marked up text is that returned by `core.colorize`.
{% include notice.html notice=page.marked_text_encoding %}
### Translation files
Translation files are media files that can be found in the `locale` folder for
each mod. Currently, the only supported format is `.tr`, but support for more
common formats is likely in the future. Translation files must be named
in the following way: `[textdomain].[lang].tr`.
Files in the `.tr` start with a comment specifying the textdomain, and then
further lines mapping from the English source text to the translation.
For example, `mymod.fr.tr`:
```
# textdomain: mymod
Hello everyone!=Bonjour à tous !
I like grapefruit=J'aime le pamplemousse
```
You should create translation files based on your mod/game's source code,
using a tool like
[update_translations](https://github.com/minetest-tools/update_translations).
This tool will look for `S(` in your Lua code, and automatically create a
template that translators can use to translate into their language.
It also handles updating the translation files when your source changes.
You should always put literal text (`"`) inside S rather than using a variable,
as it helps tools find translations.
## Format strings
It's common to need to include variable information within a translation
string. It's important that text isn't just concatenated, as that prevents
translators from changing the order of variables within a sentence. Instead,
you should use the translation system's format/arguments system:
```lua
core.register_on_joinplayer(function(player)
core.chat_send_all(S("Everyone, say hi to @1!", player:get_player_name()))
end)
```
If you want to include a literal `@` in your translation, you'll need to escape
by writing `@@`.
You should avoid concatenation *within* a sentence, but it's recommended that
you join multiple sentences using concatenation. This helps translators by
keeping strings smaller.
```lua
S("Hello @1!", player_name) .. " " .. S("You have @1 new messages.", #msgs)
```
## Best practices and Common Falsehoods about Translation
* Avoid concatenating text and use format arguments instead. This gives
translators full control over changing the order of things.
* Create translation files automatically, using
[update_translations](https://github.com/minetest-tools/update_translations).
* It's common for variables to change the surrounding text, for example, with
gender and pluralisation. This is often hard to deal with, so is
frequently glossed over or worked around with gender neutral phrasings.
* Translations may be much longer or much smaller than the English text. Make
sure to leave plenty of space.
* Other languages may write numbers in a different way, for example, with commas
as decimal points. `1.000,23`, `1'000'000,32`
* Don't assume that other languages use capitalisation in the same way.
## Server-side translations
Sometimes you need to know the translation of text on the server, for example,
to sort or search text. You can use `get_player_information` to get a player's
language and `get_translated_string` to translate marked text.
```lua
local list = {
S("Hello world!"),
S("Potato")
}
core.register_chatcommand("find", {
func = function(name, param)
local info = core.get_player_information(name)
local language = info and info.language or "en"
for _, line in ipairs(list) do
local trans = core.get_translated_string(language, line)
if trans:contains(query) then
return line
end
end
end,
})
```
## Conclusion
The translation API allows making mods and games more accessible, but care is
needed in order to use it correctly.
Minetest is continuously improving, and the translation API is likely to be
extended in the future. For example, support for gettext translation files will
allow common translator tools and platforms (like weblate) to be used, and
there's likely to be support for pluralisation and gender added.

172
_ru/quality/unit_testing.md Normal file
View File

@ -0,0 +1,172 @@
---
title: Automatic Unit Testing
layout: default
root: ../..
idx: 8.5
---
## Introduction <!-- omit in toc -->
Unit tests are an essential tool in proving and reassuring yourself that your code
is correct. This chapter will show you how to write tests for Minetest mods and
games using Busted. Writing unit tests for functions where you call Minetest
functions is quite difficult, but luckily [in the previous chapter](clean_arch.html),
we discussed how to structure your code avoid this.
- [Installing Busted](#installing-busted)
- [Your First Test](#your-first-test)
- [init.lua](#initlua)
- [api.lua](#apilua)
- [tests/api_spec.lua](#testsapi_speclua)
- [Mocking: Using External Functions](#mocking-using-external-functions)
- [Conclusion](#conclusion)
## Installing Busted
First, you'll need to install LuaRocks.
* Windows: Follow the [installation instructions on LuaRock's wiki](https://github.com/luarocks/luarocks/wiki/Installation-instructions-for-Windows).
* Debian/Ubuntu Linux: `sudo apt install luarocks`
Next, you should install Busted globally:
sudo luarocks install busted
Finally, check that it is installed:
busted --version
## Your First Test
Busted is Lua's leading unit test framework. Busted looks for Lua files with
names ending in `_spec`, and then executes them in a standalone Lua environment.
mymod/
├── init.lua
├── api.lua
└── tests
└── api_spec.lua
### init.lua
```lua
mymod = {}
dofile(core.get_modpath("mymod") .. "/api.lua")
```
### api.lua
```lua
function mymod.add(x, y)
return x + y
end
```
### tests/api_spec.lua
```lua
-- Look for required things in
package.path = "../?.lua;" .. package.path
-- Set mymod global for API to write into
_G.mymod = {} --_
-- Run api.lua file
require("api")
-- Tests
describe("add", function()
it("adds", function()
assert.equals(2, mymod.add(1, 1))
end)
it("supports negatives", function()
assert.equals(0, mymod.add(-1, 1))
assert.equals(-2, mymod.add(-1, -1))
end)
end)
```
You can now run the tests by opening a terminal in the mod's directory and
running `busted .`
It's important that the API file doesn't create the table itself, as globals in
Busted work differently. Any variable which would be global in Minetest is instead
a file local in busted. This would have been a better way for Minetest to do things,
but it's too late for that now.
Another thing to note is that any files you're testing should avoid calls to any
functions not inside of it. You tend to only write tests for a single file at once.
## Mocking: Using External Functions
Mocking is the practice of replacing functions that the thing you're testing depends
on. This can have two purposes; one, the function may not be available in the
test environment, and two, you may want to capture calls to the function and any
passed arguments.
If you follow the advice in the [Clean Architectures](clean_arch.html) chapter,
you'll already have a pretty clean file to test. You will still have to mock
things not in your area, however - for example, you'll have to mock the view when
testing the controller/API. If you didn't follow the advice, then things are a
little harder as you may have to mock the Minetest API.
```lua
-- As above, make a table
_G.minetest = {}
-- Define the mock function
local chat_send_all_calls = {}
function core.chat_send_all(name, message)
table.insert(chat_send_all_calls, { name = name, message = message })
end
-- Tests
describe("list_areas", function()
it("returns a line for each area", function()
chat_send_all_calls = {} -- reset table
mymod.list_areas_to_chat("singleplayer", "singleplayer")
assert.equals(2, #chat_send_all_calls)
end)
it("sends to right player", function()
chat_send_all_calls = {} -- reset table
mymod.list_areas_to_chat("singleplayer", "singleplayer")
for _, call in pairs(chat_send_all_calls) do --_
assert.equals("singleplayer", call.name)
end
end)
-- The above two tests are actually pointless,
-- as this one tests both things
it("returns correct thing", function()
chat_send_all_calls = {} -- reset table
mymod.list_areas_to_chat("singleplayer", "singleplayer")
local expected = {
{ name = "singleplayer", message = "Town Hall (2,43,63)" },
{ name = "singleplayer", message = "Airport (43,45,63)" },
}
assert.same(expected, chat_send_all_calls)
end)
end)
```
## Conclusion
Unit tests will greatly increase the quality and reliability of your project if used
well, but they require you to structure your code in a different way than usual.
For an example of a mod with lots of unit tests, see
[crafting by rubenwardy](https://github.com/rubenwardy/crafting).