From f802a7fae1a54ba514e00d9df96aaea6f41eab54 Mon Sep 17 00:00:00 2001 From: Marco <4279489-marco_a@users.noreply.gitlab.com> Date: Mon, 27 Apr 2020 15:11:54 +0200 Subject: [PATCH] Settings for Italian translation ready, _it folder created --- _data/languages.yml | 3 + _it/advmap/biomesdeco.md | 243 ++++++++++++++++ _it/advmap/lvm.md | 213 ++++++++++++++ _it/basics/getting_started.md | 221 +++++++++++++++ _it/basics/lua.md | 314 +++++++++++++++++++++ _it/games/games.md | 93 +++++++ _it/index.md | 35 +++ _it/items/creating_textures.md | 84 ++++++ _it/items/inventories.md | 342 +++++++++++++++++++++++ _it/items/node_drawtypes.md | 446 ++++++++++++++++++++++++++++++ _it/items/nodes_items_crafting.md | 382 +++++++++++++++++++++++++ _it/map/environment.md | 225 +++++++++++++++ _it/map/objects.md | 281 +++++++++++++++++++ _it/map/storage.md | 247 +++++++++++++++++ _it/map/timers.md | 120 ++++++++ _it/players/chat.md | 166 +++++++++++ _it/players/chat_complex.md | 183 ++++++++++++ _it/players/formspecs.md | 385 ++++++++++++++++++++++++++ _it/players/hud.md | 293 ++++++++++++++++++++ _it/players/player_physics.md | 76 +++++ _it/players/privileges.md | 138 +++++++++ _it/players/sfinv.md | 242 ++++++++++++++++ _it/quality/clean_arch.md | 253 +++++++++++++++++ _it/quality/common_mistakes.md | 167 +++++++++++ _it/quality/luacheck.md | 152 ++++++++++ _it/quality/readmore.md | 28 ++ _it/quality/releasing.md | 214 ++++++++++++++ _it/quality/security.md | 110 ++++++++ _it/quality/unit_testing.md | 195 +++++++++++++ _layouts/default.html | 2 + 30 files changed, 5853 insertions(+) create mode 100644 _it/advmap/biomesdeco.md create mode 100644 _it/advmap/lvm.md create mode 100644 _it/basics/getting_started.md create mode 100644 _it/basics/lua.md create mode 100644 _it/games/games.md create mode 100644 _it/index.md create mode 100644 _it/items/creating_textures.md create mode 100644 _it/items/inventories.md create mode 100644 _it/items/node_drawtypes.md create mode 100644 _it/items/nodes_items_crafting.md create mode 100644 _it/map/environment.md create mode 100644 _it/map/objects.md create mode 100644 _it/map/storage.md create mode 100644 _it/map/timers.md create mode 100644 _it/players/chat.md create mode 100644 _it/players/chat_complex.md create mode 100644 _it/players/formspecs.md create mode 100644 _it/players/hud.md create mode 100644 _it/players/player_physics.md create mode 100644 _it/players/privileges.md create mode 100644 _it/players/sfinv.md create mode 100644 _it/quality/clean_arch.md create mode 100644 _it/quality/common_mistakes.md create mode 100644 _it/quality/luacheck.md create mode 100644 _it/quality/readmore.md create mode 100644 _it/quality/releasing.md create mode 100644 _it/quality/security.md create mode 100644 _it/quality/unit_testing.md diff --git a/_data/languages.yml b/_data/languages.yml index de6bc3a..99124e3 100644 --- a/_data/languages.yml +++ b/_data/languages.yml @@ -1,3 +1,6 @@ - code: en name: English (UK) cta: This book is available in English +- code: it + name: Italiano + cta: Questo libro è disponibile in italiano diff --git a/_it/advmap/biomesdeco.md b/_it/advmap/biomesdeco.md new file mode 100644 index 0000000..64f669f --- /dev/null +++ b/_it/advmap/biomesdeco.md @@ -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 + +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 +biome’s 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 + +
+ Vernoi +
+ Voronoi diagram, showing the closest point. + By Balu Ertl, CC BY-SA 4.0. +
+
+ +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 +minetest.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 don’t 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 +minetest.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 +minetest.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 = minetest.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 +minetest.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 diff --git a/_it/advmap/lvm.md b/_it/advmap/lvm.md new file mode 100644 index 0000000..b5775b7 --- /dev/null +++ b/_it/advmap/lvm.md @@ -0,0 +1,213 @@ +--- +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 `minetest.get_voxel_manip()` with mapgen, as it can cause glitches. + Use `minetest.get_mapgen_object("voxelmanip")` instead. +--- + +## Introduction + +The functions outlined in the [Basic Map Operations](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 = minetest.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 = minetest.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 +``` + +It is recommended that you find and store the content IDs of nodes types +at load time because the IDs of a node type will never change. Make sure to store +the IDs in a local variable for performance reasons. + +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 `minetest.fix_light`. + +## Example + +```lua +-- Get content IDs during load time, and store into a local +local c_dirt = minetest.get_content_id("default:dirt") +local c_grass = minetest.get_content_id("default:dirt_with_grass") + +local function grass_to_dirt(pos1, pos2) + -- Read data into LVM + local vm = minetest.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°. +* 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? diff --git a/_it/basics/getting_started.md b/_it/basics/getting_started.md new file mode 100644 index 0000000..6d209f8 --- /dev/null +++ b/_it/basics/getting_started.md @@ -0,0 +1,221 @@ +--- +title: Getting Started +layout: default +root: ../.. +idx: 1.1 +description: Learn how to make a mod folder, including init.lua, mod.conf and more. +redirect_from: +- /en/chapters/folders.html +- /en/basics/folders.html +--- + +## Introduction + +Understanding the basic structure of a mod's folder is an essential skill when +creating mods. + +- [What are Games and Mods?](#what-are-games-and-mods) +- [Where are mods stored?](#where-are-mods-stored) +- [Mod Directory](#mod-directory) +- [Dependencies](#dependencies) + - [mod.conf](#modconf) + - [depends.txt](#dependstxt) +- [Mod Packs](#mod-packs) +- [Example](#example) + - [Mod Folder](#mod-folder) + - [depends.txt](#dependstxt-1) + - [init.lua](#initlua) + - [mod.conf](#modconf-1) + + +## What are Games and Mods? + +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. + +In Minetest, a game is a collection of modules which work together to provide the +content and behaviour of a game. +A module, commonly known as a mod, is a collection of scripts and resources. +It's possible to make a game using only one mod, but this is rarely done because it +reduces the ease by which parts of the game can be adjusted and replaced +independently of others. + +It's also possible to distribute mods outside of a game, in which case they +are also *mods* in the more traditional sense - modifications. These mods adjust +or extend the features of a game. + +Both the mods contained in a game and third-party mods use the same API. + +This book will cover the main parts of the Minetest API, +and is applicable for both game developers and modders. + + +## Where are mods stored? + + + +Each mod has its own directory where its Lua code, textures, models, and +sounds are placed. Minetest checks in a number of different locations for +mods. These locations are commonly called *mod load paths*. + +For a given world/save game, three mod locations are checked. +They are, in order: + +1. Game mods. These are the mods that form the game that the world is running. + Eg: `minetest/games/minetest_game/mods/`, `/usr/share/minetest/games/minetest/` +2. Global mods, the location to which mods are nearly always installed to. + If in doubt, place them here. + Eg: `minetest/mods/` +3. World mods, the location to store mods which are specific to a + particular world. + Eg: `minetest/worlds/world/worldmods/` + +Minetest will check the locations in the order given above. If it encounters a mod +with a name the same as one found previously, the later mod will be loaded in place +of the earlier mod. +This means that you can override game mods by placing a mod with the same name +in the global mod location. + +The actual location of each mod load path depends on what operating system you're +using, and how you installed Minetest. + +* **Windows:** + * For portable builds, ie: from a .zip file, just go to the directory where + you extracted the zip and look for the `games`, `mods`, and `worlds` + directories. + * For installed builds, ie: from a setup.exe, + look in C:\\\\Minetest or C:\\\\Games\\Minetest. +* **GNU/Linux:** + * For system-wide installs, look in `~/.minetest`. + Note that `~` means the user home directory, and that files and directories + starting with a dot (`.`) are hidden. + * For portable installs, look in the build directory. + * For Flatpak installs, look in `~/.var/app/net.minetest.Minetest/.minetest/mods/`. +* **MacOS** + * Look in `~/Library/Application Support/minetest/`. + Note that `~` means the user home, ie: `/Users/USERNAME/`. + +## Mod Directory + +![Find the mod's directory]({{ page.root }}/static/folder_modfolder.jpg) + +A *mod name* is used to refer to a mod. Each mod should have a unique name. +Mod names can include letters, numbers, and underscores. A good name should +describe what the mod does, and the directory which contains the components of a mod +must have the same name as the mod name. +To find out if a mod name is available, try searching for it on +[content.minetest.net](https://content.minetest.net). + + mymod + ├── init.lua (required) - Runs when the game loads. + ├── mod.conf (recommended) - Contains description and dependencies. + ├── textures (optional) + │   └── ... any textures or images + ├── sounds (optional) + │   └── ... any sounds + └── ... any other files or directories + +Only the init.lua file is required in a mod for it to run on game load; +however, mod.conf is recommended and other components may be needed +depending on the mod's functionality. + + +## Dependencies + +A dependency occurs when a mod requires another mod to be loaded before itself. +One mod may require another mod's code, items, or other resources to be available +for it to use. + +There are two types of dependencies: hard and optional dependencies. +Both require the mod to be loaded first. If the mod being depended on isn't +available, a hard dependency will cause the mod to fail to load, while an optional +dependency might lead to fewer features being enabled. + +An optional dependency is useful if you want to optionally support another mod; it can +enable extra content if the user wishes to use both the mods at the same time. + +Dependencies should be listed in mod.conf. + +### mod.conf + +This file is used for mod metadata including the mod's name, description, and other +information. For example: + + name = mymod + description = Adds foo, bar, and bo. + depends = modone, modtwo + optional_depends = modthree + +### depends.txt + +For compatibility with 0.4.x versions of Minetest, instead of only specifying +dependencies in mod.conf, you need to provide a depends.txt file in which +you list all dependencies: + + modone + modtwo + modthree? + +Each mod name is on its own line, and mod names with a question mark +following them are optional dependencies. +If an optional dependency is installed, it is loaded before the mod; +however, if the dependency is not installed, the mod still loads. +This is in contrast to normal dependencies which will cause the current +mod not to work if the dependency is not installed. + +## Mod Packs + +Mods can be grouped into mod packs which allow multiple mods to be packaged +and moved together. They are useful if you want to supply multiple mods to +a player, but don't want to make them download each one individually. + + modpack1 + ├── modpack.lua (required) - signals that this is a mod pack + ├── mod1 + │   └── ... mod files + └── mymod (optional) +    └── ... mod files + +Please note that a modpack is not a *game*. +Games have their own organisational structure which will be explained in the +Games chapter. + +## Example + +Here is an example which puts all of this together: + +### Mod Folder + mymod + ├── textures + │   └── mymod_node.png files + ├── depends.txt + ├── init.lua + └── mod.conf + +### depends.txt + default + +### init.lua +```lua +print("This file will be run at load time!") + +minetest.register_node("mymod:node", { + description = "This is a node", + tiles = {"mymod_node.png"}, + groups = {cracky = 1} +}) +``` + +### mod.conf + name = mymod + descriptions = Adds a node + depends = default + +This mod has the name "mymod". It has three text files: init.lua, mod.conf, +and depends.txt.\\ +The script prints a message and then registers a node – +which will be explained in the next chapter.\\ +There's a single dependency, the +[default mod](https://content.minetest.net/metapackages/default/), which is +usually found in Minetest Game.\\ +There is also a texture in textures/ for the node. diff --git a/_it/basics/lua.md b/_it/basics/lua.md new file mode 100644 index 0000000..e97f337 --- /dev/null +++ b/_it/basics/lua.md @@ -0,0 +1,314 @@ +--- +title: Lua Scripting +layout: default +root: ../.. +idx: 1.2 +description: A basic introduction to Lua, including a guide on global/local scope. +redirect_from: /en/chapters/lua.html +--- + +## Introduction + +In this chapter we will talk about scripting in Lua, the tools required, +and go over some techniques which you will probably find useful. + +- [Code Editors](#code-editors) +- [Coding in Lua](#coding-in-lua) + - [Program Flow](#program-flow) + - [Variable Types](#variable-types) + - [Arithmetic Operators](#arithmetic-operators) + - [Selection](#selection) + - [Logical Operators](#logical-operators) +- [Programming](#programming) +- [Local and Global Scope](#local-and-global-scope) + - [Locals should be used as much as possible](#locals-should-be-used-as-much-as-possible) +- [Including other Lua Scripts](#including-other-lua-scripts) + +## Code Editors + +A code editor with code highlighting is sufficient for writing scripts in Lua. +Code highlighting gives different colours to different words and characters +depending on what they mean. This allows you to spot mistakes. + +```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 +``` + +For example, keywords in the above snippet are highlighted such as if, then, end, and return. +table.insert is a function which comes with Lua by default. + +Here is a list of common editors well suited for Lua. +Other editors are available, of course. + +* Windows: [Notepad++](http://notepad-plus-plus.org/), [Atom](http://atom.io/), [VS Code](https://code.visualstudio.com/) +* Linux: Kate, Gedit, [Atom](http://atom.io/), [VS Code](https://code.visualstudio.com/) +* OSX: [Atom](http://atom.io/), [VS Code](https://code.visualstudio.com/) + +## Coding in Lua + +### Program Flow + +Programs are a series of commands that run one after another. +We call these commands "statements." +Program flow is how these statements are executed. +Different types of flow allow you to skip or jump over sets of commands. +There are three main types of flow: + +* Sequence: Just run one statement after another, no skipping. +* Selection: Skip over sequences depending on conditions. +* Iteration: Repeating, looping. Keep running the same + statements until a condition is met. + +So, what do statements in Lua look like? + +```lua +local a = 2 -- Set 'a' to 2 +local b = 2 -- Set 'b' to 2 +local result = a + b -- Set 'result' to a + b, which is 4 +a = a + 10 +print("Sum is "..result) +``` + +Whoa, what happened there? + +a, b, and result are *variables*. Local variables are declared +by using the local keyword, and then given an initial value. +Local will be discussed in a bit, as it's part of a very important concept called +*scope*. + +The `=` means *assignment*, so `result = a + b` means set "result" to a + b. +Variable names can be longer than one character unlike in mathematics, as seen with the "result" variable. +It's also worth noting that Lua is *case-sensitive*; A is a different variable to a. + +### Variable Types + +A variable will be only one of the following types and can change type after an +assignment. +It's good practice to make sure a variable is only ever nil or a single non-nil type. + +| Type | Description | Example | +|----------|---------------------------------|----------------| +| Nil | Not initialised. The variable is empty, it has no value | `local A`, `D = nil` | +| Number | A whole or decimal number. | `local A = 4` | +| String | A piece of text | `local D = "one two three"` | +| Boolean | True or False | `local is_true = false`, `local E = (1 == 1)` | +| Table | Lists | Explained below | +| Function | Can run. May require inputs and may return a value | `local result = func(1, 2, 3)` | + +### Arithmetic Operators + +Not an exhaustive list. Doesn't contain every possible operator. + +| Symbol | Purpose | Example | +|--------|----------------|---------------------------| +| A + B | Addition | 2 + 2 = 4 | +| A - B | Subtraction | 2 - 10 = -8 | +| A * B | Multiplication | 2 * 2 = 4 | +| A / B | Division | 100 / 50 = 2 | +| A ^ B | Powers | 2 ^ 2 = 22 = 4 | +| A .. B | Join strings | "foo" .. "bar" = "foobar" | + +### Selection + +The most basic selection is the if statement. It looks like this: + +```lua +local random_number = math.random(1, 100) -- Between 1 and 100. +if random_number > 50 then + print("Woohoo!") +else + print("No!") +end +``` + +That example generates a random number between 1 and 100. It then prints +"Woohoo!" if that number is bigger than 50, otherwise it prints "No!". +What else can you get apart from '>'? + +### Logical Operators + +| Symbol | Purpose | Example | +|---------|--------------------------------------|-------------------------------------------------------------| +| A == B | Equals | 1 == 1 (true), 1 == 2 (false) | +| A ~= B | Doesn't equal | 1 ~= 1 (false), 1 ~= 2 (true) | +| A > B | Greater than | 5 > 2 (true), 1 > 2 (false), 1 > 1 (false) | +| A < B | Less than | 1 < 3 (true), 3 < 1 (false), 1 < 1 (false) | +| A >= B | Greater than or equals | 5 >= 5 (true), 5 >= 3 (true), 5 >= 6 (false) | +| A <= B | Less than or equals | 3 <= 6 (true), 3 <= 3 (true) | +| A and B | And (both must be correct) | (2 > 1) and (1 == 1) (true), (2 > 3) and (1 == 1) (false) | +| A or B | either or. One or both must be true. | (2 > 1) or (1 == 2) (true), (2 > 4) or (1 == 3) (false) | +| not A | not true | not (1 == 2) (true), not (1 == 1) (false) | + +That doesn't contain every possible operator, and you can combine operators like this: + +```lua +if not A and B then + print("Yay!") +end +``` + +Which prints "Yay!" if A is false and B is true. + +Logical and arithmetic operators work exactly the same; +they both accept inputs and return a value which can be stored. + +```lua +local A = 5 +local is_equal = (A == 5) +if is_equal then + print("Is equal!") +end +``` + +## Programming + +Programming is the action of taking a problem, such as sorting a list +of items, and then turning it into steps that a computer can understand. + +Teaching you the logical process of programming is beyond the scope of this book; +however, the following websites are quite useful in developing this: + +* [Codecademy](http://www.codecademy.com/) is one of the best resources for + learning to 'code', it provides an interactive tutorial experience. +* [Scratch](https://scratch.mit.edu) is a good resource when starting from + absolute basics, learning the problem-solving techniques required to program.\\ + Scratch is **designed to teach children** how to program and isn't a serious + programming language. + +## Local and Global Scope + +Whether a variable is local or global determines where it can be written to or read to. +A local variable is only accessible from where it is defined. Here are some examples: + +```lua +-- Accessible from within this script file +local one = 1 + +function myfunc() + -- Accessible from within this function + local two = one + one + + if two == one then + -- Accessible from within this if statement + local three = one + two + end +end +``` + +Whereas global variables can be accessed from anywhere in the script file, and from any other mod. + +```lua +my_global_variable = "blah" + +function one() + my_global_variable = "three" +end + +print(my_global_variable) -- Output: "blah" +one() +print(my_global_variable) -- Output: "three" +``` + + +### Locals should be used as much as possible + +Lua is global by default (unlike most other programming languages). +Local variables must be identified as such. + +```lua +function one() + foo = "bar" +end + +function two() + print(dump(foo)) -- Output: "bar" +end + +one() +two() +``` + +dump() is a function that can turn any variable into a string so the programmer can +see what it is. The foo variable will be printed as "bar", including the quotes +which show it is a string. + +This is sloppy coding and Minetest will, in fact, warn about this: + + Assignment to undeclared global 'foo' inside function at init.lua:2 + +To correct this, use "local": + +```lua +function one() + local foo = "bar" +end + +function two() + print(dump(foo)) -- Output: nil +end + +one() +two() +``` + +Remember that nil means **not initialised**. +The variable hasn't been assigned a value yet, +doesn't exist, or has been uninitialised (ie: set to nil). + +The same goes for functions. Functions are variables of a special type, and +should be made local, as other mods could have functions of the same name. + +```lua +local function foo(bar) + return bar * 2 +end +``` + +API tables should be used to allow other mods to call the functions, like so: + +```lua +mymod = {} + +function mymod.foo(bar) + return "foo" .. bar +end + +-- In another mod, or script: +mymod.foo("foobar") +``` + +## Including other Lua Scripts + +The recommended way to include other Lua scripts in a mod is to use *dofile*. + +```lua +dofile(minetest.get_modpath("modname") .. "/script.lua") +``` + +"local" variables declared outside of any functions in a script file will be local to that script. +A script can return a value, which is useful for sharing private locals: + +```lua +-- script.lua +return "Hello world!" + +-- init.lua +local ret = dofile(minetest.get_modpath("modname") .. "/script.lua") +print(ret) -- Hello world! +``` + +Later chapters will discuss how to split up the code of a mod in a lot of detail. +However, the simplistic approach for now is to have different files for different +types of things - nodes.lua, crafts.lua, craftitems.lua, etc. diff --git a/_it/games/games.md b/_it/games/games.md new file mode 100644 index 0000000..2ead9eb --- /dev/null +++ b/_it/games/games.md @@ -0,0 +1,93 @@ +--- +title: Creating Games +layout: default +root: ../.. +idx: 7.1 +--- + +## Introduction + +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. diff --git a/_it/index.md b/_it/index.md new file mode 100644 index 0000000..630dc92 --- /dev/null +++ b/_it/index.md @@ -0,0 +1,35 @@ +--- +title: Front Cover +layout: default +homepage: true +no_header: true +root: .. +idx: 0.1 +--- + +
+

Minetest Modding Book

+ + by + with editing by Shara +
+ +## Introduction + +Minetest uses Lua scripts to provide modding support. +This book aims to teach you how to create your own mods, starting from the basics. +Each chapter focuses on a particular part of the API, and will soon get you making +your own mods. + +As well as [reading this book online](https://rubenwardy.com/minetest_modding_book), +you can also [download it in HTML form](https://github.com/rubenwardy/minetest_modding_book/releases). + +### Feedback and Contributions + +Noticed a mistake, or want to give feedback? Make sure to tell me about it. + +* Create a [GitHub Issue](https://github.com/rubenwardy/minetest_modding_book/issues). +* Post in the [Forum Topic](https://forum.minetest.net/viewtopic.php?f=14&t=10729). +* [Contact me](https://rubenwardy.com/contact/). +* Fancy contributing? + [Read the README](https://github.com/rubenwardy/minetest_modding_book/blob/master/README.md). diff --git a/_it/items/creating_textures.md b/_it/items/creating_textures.md new file mode 100644 index 0000000..8f4b66f --- /dev/null +++ b/_it/items/creating_textures.md @@ -0,0 +1,84 @@ +--- +title: Creating Textures +layout: default +root: ../.. +idx: 2.2 +description: An introduction to making textures in your editor of choice, an a guide on GIMP. +redirect_from: /en/chapters/creating_textures.html +--- + +## Introduction + +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) +- [Editors](#editors) + - [MS Paint](#ms-paint) + - [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. + + + +If you fail to match the edges correctly, the result is far less pleasing +to look at. + + + +### 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. + +## 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. + +### 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, the pencil tool can be selected from the Toolbox: + +
+ Pencil in GIMP +
+ +It's also advisable to select the Hard edge checkbox for the eraser tool. diff --git a/_it/items/inventories.md b/_it/items/inventories.md new file mode 100644 index 0000000..31d35ba --- /dev/null +++ b/_it/items/inventories.md @@ -0,0 +1,342 @@ +--- +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 + +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) +- [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 simply called *lists* in the context +of inventories. +The point of an inventory is to allow multiple grids when Players +and Nodes only have at most one inventory in them. + +## ItemStacks + +ItemStacks have three components to them. + +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 Metadata and Storage 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 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 +local inv = minetest.get_inventory({ type="node", pos={x=1, y=2, z=3} }) +``` + +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 can be obtained similarly or using a player reference. +The player must be online to access their inventory. + +```lua +local inv = minetest.get_inventory({ type="player", name="player1" }) +-- or +local inv = player:get_inventory() +``` + +A detached inventory is one which is independent of players or nodes. +Detached inventories also don't save over a restart. +Detached inventories need to be created before they can be used - +this will be covered later. + +```lua +local inv = minetest.get_inventory({ + type="detached", name="inventory_name" }) +``` + +Unlike the other types of inventory, you must first create a detached inventory: + +```lua +minetest.create_detached_inventory("inventory_name") +``` + +The create_detached_inventory function accepts 3 arguments, only first 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 +minetest.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 -1 -- don't allow taking + end, + + on_put = function(inv, listname, index, stack, player) + minetest.chat_send_all(player:get_player_name() .. + " gave " .. stack:to_string() .. + " to the donation chest at " .. minetest.pos_to_str(pos)) + end, +}) +``` + +Permission callbacks - ie: those starting with `allow_`- return the number +of items to transfer, with -1 being used to prevent transfer completely. + +Action callbacks - starting with `on_` - don't have a return value and +can't prevent transfers. + +## 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 a number of common lists +which 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. + +## Modifying Inventories and ItemStacks + +### Adding to a List + +To add items to a list named `"main"` while respecting maximum stack sizes: + +```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", {}) +``` diff --git a/_it/items/node_drawtypes.md b/_it/items/node_drawtypes.md new file mode 100644 index 0000000..b194706 --- /dev/null +++ b/_it/items/node_drawtypes.md @@ -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 + +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 +`minetest.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](#glasslikeframed) +- [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 + +
+ Normal Drawtype +
+ Normal Drawtype +
+
+ +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 +minetest.register_node("mymod:diamond", { + description = "Alien Diamond", + tiles = {"mymod_diamond.png"}, + groups = {cracky = 3}, +}) + +minetest.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. + +
+ Glasslike's Edges +
+ Glasslike's Edges +
+
+ +```lua +minetest.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: + +
+ Glasslike_framed's Edges +
+ Glasslike_Framed's Edges +
+
+ +You can use the glasslike_framed_optional drawtype to allow the user to *opt-in* +to the framed appearance. + +```lua +minetest.register_node("default:glass", { + description = "Glass", + drawtype = "glasslike_framed", + tiles = {"default_glass.png", "default_glass_detail.png"}, + inventory_image = minetest.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 +minetest.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 be 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 + +
+ Liquid Drawtype +
+ Liquid Drawtype +
+
+ +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. +minetest.register_node("default:water_source", { + drawtype = "liquid", + paramtype = "light", + + inventory_image = minetest.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 + +
+ Nodebox drawtype +
+ Nodebox drawtype +
+
+ +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 +minetest.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 +minetest.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 +minetest.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 +minetest.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 + +
+ Plantlike Drawtype +
+ Plantlike Drawtype +
+
+ +Plantlike nodes draw their tiles in an X like pattern. + +```lua +minetest.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. + +
+ Firelike nodes +
+ Firelike nodes +
+
+ +```lua +minetest.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](../../lua_api.html#node-drawtypes) +for the complete list. diff --git a/_it/items/nodes_items_crafting.md b/_it/items/nodes_items_crafting.md new file mode 100644 index 0000000..1d8e081 --- /dev/null +++ b/_it/items/nodes_items_crafting.md @@ -0,0 +1,382 @@ +--- +title: Nodes, Items, and Crafting +layout: default +root: ../.. +idx: 2.1 +description: Learn how to register node, items, and craft recipes using register_node, register_item, and register_craft. +redirect_from: /en/chapters/nodes_items_crafting.html +--- + +## Introduction + +Registering new nodes and craftitems, and creating craft recipes, are +basic requirements for many mods. + +- [What are Nodes and Items?](#what-are-nodes-and-items) +- [Registering Items](#registering-items) + - [Item Names and Aliases](#item-names-and-aliases) + - [Textures](#textures) +- [Registering a basic node](#registering-a-basic-node) +- [Actions and Callbacks](#actions-and-callbacks) + - [on_use](#onuse) +- [Crafting](#crafting) + - [Shaped](#shaped) + - [Shapeless](#shapeless) + - [Cooking and Fuel](#cooking-and-fuel) +- [Groups](#groups) +- [Tools, Capabilities, and Dig Types](#tools-capabilities-and-dig-types) + +## What are Nodes and Items? + +Nodes, craftitems, and tools are all Items. +An item is something that could be found in an inventory - +even though it may not be possible through normal gameplay. + +A node is an item which can be placed or be found in the world. +Every position in the world must be occupied with one and only one node - +seemingly blank positions are usually air nodes. + +A craftitem can't be placed and is only found in inventories or as a dropped item +in the world. + +A tool has the ability to wear and typically has non-default digging capabilities. +In the future, it's likely that craftitems and tools will merge into one type of +item, as the distinction between them is rather artificial. + +## Registering Items + +Item definitions consist of an *item name* and a *definition table*. +The definition table contains attributes which affect the behaviour of the item. + +```lua +minetest.register_craftitem("modname:itemname", { + description = "My Special Item", + inventory_image = "modname_itemname.png" +}) +``` + +### Item Names and Aliases + +Every item has an item name used to refer to it, which should be in the +following format: + + modname:itemname + +The modname is the name of the mod in which the item is registered, and the +item name is the name of the item itself. +The item name should be relevant to what the item is and can't already be registered. + +Items can also have *aliases* pointing to their name. +An *alias* is a pseudo-item name which results in the engine treating any +occurrences of the alias as if it were the item name. +There are two main common uses of this: + +* Renaming removed items to something else. + There may be unknown nodes in the world and in inventories if an item is + removed from a mod without any corrective code. + It's important to avoid aliasing to an unobtainable node if the remove node + could be obtained. +* Adding a shortcut. `/giveme dirt` is easier than `/giveme default:dirt`. + +Registering an alias is pretty simple. +A good way to remember the order of the arguments is `from → to` where +*from* is the alias and *to* is the target. + +```lua +minetest.register_alias("dirt", "default:dirt") +``` + +Mods need to make sure to resolve aliases before dealing directly with item names, +as the engine won't do this. +This is pretty simple though: + +```lua +itemname = minetest.registered_aliases[itemname] or itemname +``` + +### Textures + +Textures should be placed in the textures/ folder with names in the format +`modname_itemname.png`.\\ +JPEG textures are supported, but they do not support transparency and are generally +bad quality at low resolutions. +It is often better to use the PNG format. + +Textures in Minetest are usually 16 by 16 pixels. +They can be any resolution, but it is recommended that they are in the order of 2, +for example, 16, 32, 64, or 128. +This is because other resolutions may not be supported correctly on older devices, +resulting in decreased performance. + +## Registering a basic node + +```lua +minetest.register_node("mymod:diamond", { + description = "Alien Diamond", + tiles = {"mymod_diamond.png"}, + is_ground_content = true, + groups = {cracky=3, stone=1} +}) +``` + +The `tiles` property is a table of texture names the node will use. +When there is only one texture, this texture is used on every side. +To give a different texture per-side, supply the names of 6 textures in this order: + + up (+Y), down (-Y), right (+X), left (-X), back (+Z), front (-Z). + (+Y, -Y, +X, -X, +Z, -Z) + +Remember that +Y is upwards in Minetest, as is the convention with +3D computer graphics. + +```lua +minetest.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" + -- ^ Rather than dropping diamond, drop mymod:diamond_fragments +}) +``` + +The is_ground_content attribute allows caves to be generated over the stone. +This is essential for any node which may be placed during map generation underground. +Caves are cut out of the world after all the other nodes in an area have generated. + +## Actions and Callbacks + +Minetest heavily uses a callback-based modding design. +Callbacks can be placed in the item definition table to allow response to various +different user events. + +### on_use + +By default, the use callback is triggered when a player left-clicks with an item. +Having a use callback prevents the item being used to dig nodes. +One common use of the use callback is for food: + +```lua +minetest.register_craftitem("mymod:mudpie", { + description = "Alien Mud Pie", + inventory_image = "myfood_mudpie.png", + on_use = minetest.item_eat(20), +}) +``` + +The number supplied to the minetest.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. +Hitpoints don't have to be integers (whole numbers); they can be decimals. + +minetest.item_eat() is a function which returns a function, setting it +as the on_use callback. +This means the code above is roughly similar to this: + +```lua +minetest.register_craftitem("mymod:mudpie", { + description = "Alien Mud Pie", + inventory_image = "myfood_mudpie.png", + on_use = function(...) + return minetest.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 such as play a custom sound. + +## Crafting + +There are several types of crafting recipe available, indicated by the `type` +property. + +* shaped - Ingredients must be in the correct position. +* shapeless - It doesn't matter where the ingredients are, + just that there is the right amount. +* cooking - Recipes for the furnace to use. +* fuel - Defines items which can be burned in furnaces. +* tool_repair - Defines items which can be tool repaired. + +Craft recipes are not items, so they do not use Item Names to uniquely +identify themselves. + +### Shaped + +Shaped recipes are when the ingredients need to be in the right shape or +pattern to work. In the example below, the fragments need to be in a +chair-like pattern for the craft to work. + +```lua +minetest.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", ""} + } +}) +``` + +One thing to note is the blank column on the right-hand side. +This means that there *must* be an empty column to the right of the shape, otherwise +this won't work. +If this empty column shouldn't be required, then the empty strings can be left +out like so: + +```lua +minetest.register_craft({ + output = "mymod:diamond_chair 99", + recipe = { + {"mymod:diamond_fragments", "" }, + {"mymod:diamond_fragments", "mymod:diamond_fragments"}, + {"mymod:diamond_fragments", "mymod:diamond_fragments"} + } +}) +``` + +The type field isn't actually needed for shaped crafts, as shaped is the +default craft type. + +### Shapeless + +Shapeless recipes are a type of recipe which is used when it doesn't matter +where the ingredients are placed, just that they're there. + +```lua +minetest.register_craft({ + type = "shapeless", + output = "mymod:diamond 3", + recipe = { + "mymod:diamond_fragments", + "mymod:diamond_fragments", + "mymod:diamond_fragments", + }, +}) +``` + +### Cooking and Fuel + +Recipes with the type "cooking" are not made in the crafting grid, +but are cooked in furnaces, or other cooking tools that might be found in mods. + +```lua +minetest.register_craft({ + type = "cooking", + output = "mymod:diamond_fragments", + recipe = "default:coalblock", + cooktime = 10, +}) +``` + +The only real difference in the code is that the recipe is just a single item, +compared to being in a table (between braces). +They also have an optional "cooktime" parameter which +defines how long the item takes to cook. +If this is not set, it defaults to 3. + +The recipe above works when the coal block is in the input slot, +with some form of fuel below it. +It creates diamond fragments after 10 seconds! + +This type is an accompaniment to the cooking type, as it defines +what can be burned in furnaces and other cooking tools from mods. + +```lua +minetest.register_craft({ + type = "fuel", + recipe = "mymod:diamond", + burntime = 300, +}) +``` + +They don't have an output like other recipes, but they have a burn time +which defines how long they will last as fuel in seconds. +So, the diamond is good as fuel for 300 seconds! + +## Groups + +Items can be members of many groups and groups can have many members. +Groups are defined using the `groups` property in the definition table +and have an associated value. + +```lua +groups = {cracky = 3, wood = 1} +``` + +There are several reasons you use groups. +Firstly, groups are used to describe properties such as dig types and flammability. +Secondly, groups can be used in a craft recipe instead of an item name to allow +any item in the group to be used. + +```lua +minetest.register_craft({ + type = "shapeless", + output = "mymod:diamond_thing 3", + recipe = {"group:wood", "mymod:diamond"} +}) +``` + +## Tools, Capabilities, and Dig Types + +Dig types are groups which are used to define how strong a node is when dug +with different tools. +A dig type group with a higher associated value means the node is easier +and quicker to cut. +It's possible to combine multiple dig types to allow the more efficient use +of multiple types of tools. +A node with no dig types cannot be dug by any tools. + + +| Group | Best Tool | Description | +|--------|-----------|-------------| +| crumbly | spade | Dirt, sand | +| cracky | pickaxe | Tough (but brittle) stuff like stone | +| snappy | *any* | Can be cut using fine tools;
e.g. leaves, smallplants, wire, sheets of metal | +| choppy | axe | Can be cut using a sharp force; e.g. trees, wooden planks | +| fleshy | sword | Living things like animals and the player.
This could imply some blood effects when hitting. | +| explody | ? | Especially prone to explosions | +| oddly_breakable_by_hand | *any* | Torches and such - very quick to dig | + + +Every tool has a tool capability. +A capability includes a list of supported dig types, and associated properties +for each type such as dig times and the amount of wear. +Tools can also have a maximum supported hardness for each type, which makes +it possible to prevent weaker tools from digging harder nodes. +It's very common for tools to include all dig types in their capabilities, +with the less suitable ones having very inefficient properties. +If the item a player is currently wielding doesn't have an explicit tool +capability, then the capability of the current hand is used instead. + +```lua +minetest.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 is the list of supported dig types for digging nodes. +Damage groups are for controlling how tools damage objects, which will be +discussed later in the Objects, Players, and Entities chapter. diff --git a/_it/map/environment.md b/_it/map/environment.md new file mode 100644 index 0000000..73f251d --- /dev/null +++ b/_it/map/environment.md @@ -0,0 +1,225 @@ +--- +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 + +In this chapter, you will learn how to perform basic actions on the map. + +- [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, 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. + +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 = minetest.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 `minetest.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. + +```lua +local grow_speed = 1 +local node_pos = minetest.find_node_near(pos, 5, { "default:mese" }) +if node_pos then + minetest.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 which can find multiple nodes in 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 = + minetest.find_nodes_in_area(pos1, pos2, { "default:mese" }) +local grow_speed = 1 + #pos_list +``` + +The above code doesn't quite do what we want, as it checks based on area, whereas +`find_node_near` checks based on range. In order to fix this, we will, +unfortunately, 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 = + minetest.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 <= 5*5 then + grow_speed = grow_speed + 1 + end +end +``` + +Now your 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 you should avoid them 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, which means that set_node is fairly slow for large +numbers of nodes. + +```lua +minetest.set_node({ x = 1, y = 3, z = 4 }, { name = "default:mese" }) + +local node = minetest.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 think conceptually of it as one node, it's actually +two. + +You can set a node without deleting metadata or the inventory like so: + +```lua +minetest.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 +minetest.remove_node(pos) +minetest.set_node(pos, { name = "air" }) +``` + +In fact, remove_node will call set_node with the name being air. + +## Loading Blocks + +You can use `minetest.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 +minetest.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 + minetest.chat_send_all("Finished loading blocks!") + end + 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) + minetest.chat_send_all(msg) + end +end +``` + +This is not the only way of loading blocks; using an LVM 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) + +minetest.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. diff --git a/_it/map/objects.md b/_it/map/objects.md new file mode 100644 index 0000000..b9fe91f --- /dev/null +++ b/_it/map/objects.md @@ -0,0 +1,281 @@ +--- +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 + +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) +- [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 = minetest.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. + +However, entities differ in one very important way from items. When an entity is +emerged (ie: loaded or created), a new table is created for that entity that +*inherits* from the definition table using metatables. +This new table is commonly referred to as a Lua Entity table. + +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 member X on table A. If table A has that member, then +it will be returned as normal. However, if the table doesn't have that member but +it does have a metatable could table B, then table B will be checked to see if it +has that member. + + + +```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 +``` + +When an entity has emerged, a table is created for it by copying everything from +its type table. +This table can be used to store variables for that particular entity. + +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 " .. minetest.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.txt]({{ page.root }}/lua_api.html#registered-entities). + +```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 minetest.get_node(pos_down).name == "air" then + delta = vector.new(0, -1, 0) + elseif minetest.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) + minetest.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 minetest.write_json({ + message = self.message, + }) +end + +function MyEntity:on_activate(staticdata, dtime_s) + if staticdata ~= "" and staticdata ~= nil then + local data = minetest.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 +minetest.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 = minetest.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 + +## 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). diff --git a/_it/map/storage.md b/_it/map/storage.md new file mode 100644 index 0000000..534e40f --- /dev/null +++ b/_it/map/storage.md @@ -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 + +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 = minetest.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_()` and `set_()` 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 `minetest.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 when you need to exchange information +with another program. + +```lua +local data = { username = "player1", score = 1234 } +meta:set_string("foo", minetest.serialize(data)) + +data = minetest.deserialize(minetest:get_string("foo")) +``` + +### Private Metadata + +Entries in Node Metadata can be marked as private, and not sent to the client. +Entries not marked as private will be sent to the client. + +```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 = minetest.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(minetest.get_modpath("mymod") .. "/backend_sqlite.lua") +else + backend = + dofile(minetest.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 = minetest.get_mod_storage() +local backend = {} + +function backend.set_foo(key, value) + storage:set_string(key, minetest.serialize(value)) +end + +function backend.get_foo(key, value) + return minetest.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 = minetest.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 `minetest.set_node`.) diff --git a/_it/map/timers.md b/_it/map/timers.md new file mode 100644 index 0000000..f6239e6 --- /dev/null +++ b/_it/map/timers.md @@ -0,0 +1,120 @@ +--- +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 + +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 = minetest.get_node_timer(pos) +timer:start(10.5) -- in seconds +``` + +You can also check the status or stop the timer: + +```lua +if timer:is_started() then + print("The timer is running, and has " .. timer:get_timeout() .. "s remaining!") + print(timer:get_elapsed() .. "s has elapsed.") +end + +timer:stop() +``` + +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 +minetest.register_node("autodoors:door_open", { + on_timer = function(pos) + minetest.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. + +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 +minetest.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 = minetest.item_eat(20) +}) + +minetest.register_abm({ + nodenames = {"default:dirt_with_grass"}, + neighbors = {"default:water_source", "default:water_flowing"}, + interval = 10.0, -- Run every 10 seconds + chance = 50, -- Select every 1 in 50 nodes + action = function(pos, node, active_object_count, + active_object_count_wider) + local pos = {x = pos.x, y = pos.y + 1, z = pos.z} + minetest.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 minetest.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. diff --git a/_it/players/chat.md b/_it/players/chat.md new file mode 100644 index 0000000..d85238b --- /dev/null +++ b/_it/players/chat.md @@ -0,0 +1,166 @@ +--- +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

minetest.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 + +Mods can interact with player chat, including +sending messages, intercepting messages, and registering chat commands. + +- [Sending Messages to All Players](#sending-messages-to-all-players) +- [Sending Messages to Specific Players](#sending-messages-to-specific-players) +- [Chat Commands](#chat-commands) +- [Complex Subcommands](#complex-subcommands) +- [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 +minetest.chat_send_all("This is a chat message to all players") +``` + +Here is an example of how this appears in-game: + + Look at this entrance + This is a chat message to all players + What about it? + +The message appears on a separate line to distinguish it from in-game player chat. + +## Sending Messages to Specific Players + +To send a message to a specific player, call the chat_send_player function: + +```lua +minetest.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 +minetest.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. + +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 %} + +## Complex Subcommands + +It is often required to make complex chat commands, such as: + +* `/msg ` +* `/team join ` +* `/team leave ` +* `/team list` + +This is usually done using [Lua patterns](https://www.lua.org/pil/20.2.html). +Patterns are a way of extracting stuff from text using rules. + +```lua +local to, msg = string.match(param, "^([%a%d_-]+) (*+)$") +``` + +The above code implements `/msg `. 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. +* `[%d%a_-]` 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). + +

+There is also a library written by the author of this book which can be used +to make complex chat commands without patterns called +Chat Command Builder. +

+ + +## Intercepting Messages + +To intercept a message, use register_on_chat_message: + +```lua +minetest.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 +minetest.register_on_chat_message(function(name, message) + if message:sub(1, 1) == "/" then + print(name .. " ran chat command") + elseif minetest.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) +``` diff --git a/_it/players/chat_complex.md b/_it/players/chat_complex.md new file mode 100644 index 0000000..fcbd0bd --- /dev/null +++ b/_it/players/chat_complex.md @@ -0,0 +1,183 @@ +--- +title: Chat Command Builder +layout: default +root: ../.. +idx: 4.3 +description: Use ChatCmdBuilder to make a complex chat command +redirect_from: /en/chapters/chat_complex.html +--- + +## Introduction + +This chapter will show you how to make complex chat commands with ChatCmdBuilder, +such as `/msg `, `/team join ` or `/team leave `. + +Note that ChatCmdBuilder is a library created by the author of this book, and most +modders tend to use the method outlined in the +[Chat and Commands](chat.html#complex-subcommands) chapter. + +- [Why ChatCmdBuilder?](#why-chatcmdbuilder) +- [Routes](#routes) +- [Subcommand functions](#subcommand-functions) +- [Installing ChatCmdBuilder](#installing-chatcmdbuilder) +- [Admin complex command](#admin-complex-command) + +## Why ChatCmdBuilder? + +Traditionally mods implemented these complex commands using Lua patterns. + +```lua +local name = string.match(param, "^join ([%a%d_-]+)") +``` + +I, however, find Lua patterns annoying to write and unreadable. +Because of this, I created a library to do this for you. + +```lua +ChatCmdBuilder.new("sethp", function(cmd) + cmd:sub(":target :hp:int", function(name, target, hp) + local player = minetest.get_player_by_name(target) + if player then + player:set_hp(hp) + return true, "Killed " .. target + else + return false, "Unable to find " .. target + end + end) +end, { + description = "Set hp of player", + privs = { + kick = true + -- ^ probably better to register a custom priv + } +}) +``` + +`ChatCmdBuilder.new(name, setup_func, def)` creates a new chat command called +`name`. It then calls the function passed to it (`setup_func`), which then creates +subcommands. Each `cmd:sub(route, func)` is a subcommand. + +A subcommand is a particular response to an input param. When a player runs +the chat command, the first subcommand that matches their input will be run, +and no others. If no subcommands match, then the user will be told of the invalid +syntax. For example, in the above code snippet if a player +types something of the form `/sethp username 12` then the function passed +to cmd:sub will be called. If they type `/sethp 12 bleh`, then a wrong +input message will appear. + +`:name :hp:int` is a route. It describes the format of the param passed to /teleport. + +## Routes + +A route is made up of terminals and variables. Terminals must always be there. +For example, `join` in `/team join :username :teamname`. The spaces also count +as terminals. + +Variables can change value depending on what the user types. For example, `:username` +and `:teamname`. + +Variables are defined as `:name:type`. The `name` is used in the help documentation. +The `type` is used to match the input. If the type is not given, then the type is +`word`. + +Valid types are: + +* `word` - default. Any string without spaces. +* `int` - Any integer/whole number, no decimals. +* `number` - Any number, including ints and decimals. +* `pos` - 1,2,3 or 1.1,2,3.4567 or (1,2,3) or 1.2, 2 ,3.2 +* `text` - Any string. There can only ever be one text variable, + no variables or terminals can come afterwards. + +In `:name :hp:int`, there are two variables: + +* `name` - type of `word` as no type is specified. Accepts any string without spaces. +* `hp` - type of `int` + +## Subcommand functions + +The first argument is the caller's name. The variables are then passed to the +function in order. + +```lua +cmd:sub(":target :hp:int", function(name, target, hp) + -- subcommand function +end) +``` + +## Installing ChatCmdBuilder + +The source code can be found and downloaded on +[Github](https://github.com/rubenwardy/ChatCmdBuilder/). + +There are two ways to install: + +1. Install ChatCmdBuilder as a mod and depend on it. +2. Include the init.lua file in ChatCmdBuilder as chatcmdbuilder.lua in your mod, + and dofile it. + +## Admin complex command + +Here is an example that creates a chat command that allows us to do this: + +* `/admin kill ` - kill user +* `/admin move to ` - teleport user +* `/admin log ` - show report log +* `/admin log ` - log to report log + +```lua +local admin_log +local function load() + admin_log = {} +end +local function save() + -- todo +end +load() + +ChatCmdBuilder.new("admin", function(cmd) + cmd:sub("kill :name", function(name, target) + local player = minetest.get_player_by_name(target) + if player then + player:set_hp(0) + return true, "Killed " .. target + else + return false, "Unable to find " .. target + end + end) + + cmd:sub("move :name to :pos:pos", function(name, target, pos) + local player = minetest.get_player_by_name(target) + if player then + player:setpos(pos) + return true, "Moved " .. target .. " to " .. + minetest.pos_to_string(pos) + else + return false, "Unable to find " .. target + end + end) + + cmd:sub("log :username", function(name, target) + local log = admin_log[target] + if log then + return true, table.concat(log, "\n") + else + return false, "No entries for " .. target + end + end) + + cmd:sub("log :username :message", function(name, target, message) + local log = admin_log[target] or {} + table.insert(log, message) + admin_log[target] = log + save() + return true, "Logged" + end) +end, { + description = "Admin tools", + privs = { + kick = true, + ban = true + } +}) +``` diff --git a/_it/players/formspecs.md b/_it/players/formspecs.md new file mode 100644 index 0000000..cce823a --- /dev/null +++ b/_it/players/formspecs.md @@ -0,0 +1,385 @@ +--- +title: Formspecs +layout: default +root: ../.. +idx: 4.5 +redirect_from: /en/chapters/formspecs.html +minetest510: + level: warning + title: Real coordinates will be in 5.1.0 + classes: web-only + message: This chapter describes the use of a feature that hasn't been released yet. + You can still use this chapter and the code in Minetest 5.0, but elements will + be positioned differently to what is shown. +submit_vuln: + level: warning + title: Malicious clients can submit anything at anytime + 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 + +
+ Furnace Inventory +
+ Screenshot of furnace formspec, labelled. +
+
+ +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. + +{% include notice.html notice=page.minetest510 %} + + +## 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.txt](https://github.com/minetest/minetest/blob/master/doc/lua_api.txt#L1019) +for a list of all possible elements. Search for "Formspec" to locate the correct +part of the document. + + +### 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 [lua_api.txt](../../lua_api.html#sizewhfixed_size) + +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: + + size[2,2] + real_coordinates[true] + +Notice how we explicitly need to enable the use of the real coordinate system. +Without this, the legacy system will instead be used to size the formspec, which will +result in a larger size. This element is a special case, as it is the only element +which may appear both in the header and the body of a formspec. When in the header, +it must appear immediately after the size. + +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: + + size[2,2] + real_coordinates[true] + 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 + +
+ Guessing Formspec +
+ The guessing game formspec. +
+
+ +The best way to learn is to make something, so let's make a guessing game. +The principle is simple: the mod decides on a number, then the player makes +guesses on the number. The mod then says if the guess is higher or lower then +the actual number. + +First, let's make a function to create the formspec code. It's good practice to +do this, as it makes it easier to reuse elsewhere. + +
+ +```lua +guessing = {} + +function guessing.get_formspec(name) + -- TODO: display whether the last guess was higher or lower + local text = "I'm thinking of a number... Make a guess!" + + local formspec = { + "size[6,3.476]", + "real_coordinates[true]", + "label[0.375,0.5;", minetest.formspec_escape(text), "]", + "field[0.375,1.25;5.25,0.8;number;Number;]", + "button[1.5,2.3;3,0.8;guess;Guess]" + } + + -- table.concat is faster than string concatenation - `..` + return table.concat(formspec, "") +end +``` + +In the above code, we place a field, a label, and a button. A field allows text +entry, and a button is used to submit the form. You'll notice that the elements +are positioned carefully in order to add padding and spacing, this will be explained +later. + +Next, we want to allow the player to show the formspec. The main way to do this +is using `show_formspec`: + +```lua +function guessing.show_to(name) + minetest.show_formspec(name, "guessing:game", guessing.get_formspec(name)) +end + +minetest.register_chatcommand("game", { + func = function(name) + guessing.show_to(name) + end, +}) +``` + +The show_formspec function accepts a player name, the formspec name, and the +formspec itself. The formspec name should be a valid itemname, ie: in the format +`modname:itemname`. + + +### Padding and Spacing + +
+ Padding and spacing +
+ The guessing game formspec. +
+
+ +Padding is the gap between the edge of the formspec and its contents, or between unrelated +elements, shown in red. Spacing is the gap between related elements, shown in blue. + +It is fairly standard to have a padding of `0.375` and a spacing of `0.25`. + +
+ + +### Receiving Formspec Submissions + +When `show_formspec` is called, the formspec is sent to the client to be displayed. +For formspecs to be useful, information needs to be returned from the client to server. +The method for this is called formspec field submission, and for `show_formspec`, that +submission is received using a global callback: + +```lua +minetest.register_on_player_receive_fields(function(player, formname, fields) + if formname ~= "guessing:game" then + return + end + + if fields.guess then + local pname = player:get_player_name() + minetest.chat_send_all(pname .. " guessed " .. fields.number) + end +end) +``` + +The function given in minetest.register_on_player_receive_fields is called +every time a user submits a form. Most callbacks will need to check the formname given +to the function, and exit if it is not the right form; however, some callbacks +may need to work on multiple forms, or on all forms. + +The `fields` parameter to the function is a table of the values submitted by the +user, indexed by strings. Named elements will appear in the field under their own +name, but only if they are relevent for the event that caused the submission. +For example, a button element will only appear in fields if that particular button +was pressed. + +{% include notice.html notice=page.submit_vuln %} + +So, now the formspec is sent to the client and the client sends information back. +The next step is to somehow generate and remember the target value, and to update +the formspec based on guesses. The way to do this is using a concept called +"contexts". + + +### Contexts + +In many cases you want minetest.show_formspec to give information +to the callback which you don't want to send to the client. This might include +what a chat command was called with, or what the dialog is about. In this case, +the target value that needs to be remembered. + +A context is a per-player table to store information, and the contexts for all +online players are stored in a file-local variable: + +```lua +local _contexts = {} +local function get_context(name) + local context = _contexts[name] or {} + _contexts[name] = context + return context +end + +minetest.register_on_leaveplayer(function(player) + _contexts[player:get_player_name()] = nil +end) +``` + +Next, we need to modify the show code to update the context +before showing the formspec: + +```lua +function guessing.show_to(name) + local context = get_context(name) + context.target = context.target or math.random(1, 10) + + local fs = guessing.get_formspec(name, context) + minetest.show_formspec(name, "guessing:game", fs) +end +``` + +We also need to modify the formspec generation code to use the context: + +```lua +function guessing.get_formspec(name, context) + local text + if not context.guess then + text = "I'm thinking of a number... Make a guess!" + elseif context.guess == context.target then + text = "Hurray, you got it!" + elseif context.guess > context.target then + text = "To high!" + else + text = "To low!" + end +``` + +Note that it's good practice for get_formspec to only read the context, and not +update it at all. This can make the function simpler, and also easier to test. + +And finally, we need to update the handler to update the context with the guess: + +```lua +if fields.guess then + local name = player:get_player_name() + local context = get_context(name) + context.guess = tonumber(fields.number) + guessing.show_to(name) +end +``` + + +## Formspec Sources + +There are three different ways that a formspec can be delivered to the client: + +* [show_formspec](#guessing-game): the method used above, fields are received by register_on_player_receive_fields. +* [Node Meta Formspecs](#node-meta-formspecs): the node contains a formspec in its meta data, and the client + shows it *immediately* when the player rightclicks. Fields are received by a + method in the node definition called `on_receive_fields`. +* [Player Inventory Formspecs](#player-inventory-formspecs): the formspec is sent to the client at some point, and then + shown immediately when the player presses `i`. Fields are received by + register_on_player_receive_fields. + +### Node Meta Formspecs + +minetest.show_formspec is not the only way to show a formspec; you can also +add formspecs to a [node's metadata](node_metadata.html). For example, +this is used with chests to allow for faster opening times - +you don't need to wait for the server to send the player the chest formspec. + +```lua +minetest.register_node("mymod:rightclick", { + description = "Rightclick me!", + tiles = {"mymod_rightclick.png"}, + groups = {cracky = 1}, + after_place_node = function(pos, placer) + -- This function is run when the chest node is placed. + -- The following code sets the formspec for chest. + -- Meta is a way of storing data onto a node. + + local meta = minetest.get_meta(pos) + meta:set_string("formspec", + "size[5,5]".. + "label[1,1;This is shown on right click]".. + "field[1,2;2,1;x;x;]") + end, + on_receive_fields = function(pos, formname, fields, player) + if(fields.quit) then return end + print(fields.x) + end +}) +``` + +Formspecs set this way do not trigger the same callback. In order to +receive form input for meta formspecs, you must include an +`on_receive_fields` entry when registering the node. + +This style of callback triggers when you press enter +in a field, which is impossible with `minetest.show_formspec`; +however, this kind of form can only be shown by right-clicking on a +node. It cannot be triggered programmatically. + +### Player Inventory Formspecs + +The player inventory formspec is the one shown when the player presses i. +The global callback is used to receive events from this formspec, and the +formname is `""`. + +There are a number of different mods which allow multiple mods to customise +the player inventory. The officially recommended mod is +[Simple Fast Inventory (sfinv)](sfinv.html), and is included in Minetest Game. + + +### Your Turn + +* Extend the Guessing Game to keep track of each player's top score, where the + top score is how many guesses it took. +* Make a node called "Inbox" where users can open up a formspec and leave messages. + This node should store the placers' name as `owner` in the meta, and should use + `show_formspec` to show different formspecs to different players. diff --git a/_it/players/hud.md b/_it/players/hud.md new file mode 100644 index 0000000..c17dfe9 --- /dev/null +++ b/_it/players/hud.md @@ -0,0 +1,293 @@ +--- +title: HUD +layout: default +root: ../.. +idx: 4.6 +redirect_from: /en/chapters/hud.html +--- + +## Introduction + +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 + +
+ Diagram showing a centered text element +
+ +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. + +
+ +### 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. + +
+ Diagram showing alignment +
+ +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: + +
+ screenshot of the HUD we're aiming for +
+ +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 = minetest.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: + +
+ screenshot of the HUD we're aiming for +
+ + +## 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: + +
+ screenshot of the HUD so far +
+ +### 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 "Test". + +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 + +minetest.register_on_joinplayer(score.update_hud) + +minetest.register_on_leaveplayer(function(player) + saved_huds[player:get_player_name()] = nil +end) +``` + + +## Other Elements + +Read [lua_api.txt]({{ page.root }}/lua_api.html#hud-element-types) for a complete list of HUD elements. diff --git a/_it/players/player_physics.md b/_it/players/player_physics.md new file mode 100644 index 0000000..b84ed8c --- /dev/null +++ b/_it/players/player_physics.md @@ -0,0 +1,76 @@ +--- +title: Player Physics +layout: default +root: ../.. +idx: 4.4 +redirect_from: /en/chapters/player_physics.html +--- + +## Introduction + +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 +minetest.register_chatcommand("antigravity", { + func = function(name, param) + local player = minetest.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.txt]({{ page.root }}/lua_api.html#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. diff --git a/_it/players/privileges.md b/_it/players/privileges.md new file mode 100644 index 0000000..cacc68f --- /dev/null +++ b/_it/players/privileges.md @@ -0,0 +1,138 @@ +--- +title: Privileges +layout: default +root: ../.. +idx: 4.1 +description: Registering privs. +redirect_from: /en/chapters/privileges.html +--- + +## Introduction + +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 +minetest.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 = minetest.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 = minetest.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 minetest.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 = minetest.get_player_privs(name) +print(dump(privs)) + +privs.vote = true +minetest.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. diff --git a/_it/players/sfinv.md b/_it/players/sfinv.md new file mode 100644 index 0000000..19c908e --- /dev/null +++ b/_it/players/sfinv.md @@ -0,0 +1,242 @@ +--- +title: "SFINV: Inventory Formspec" +layout: default +root: ../.. +idx: 4.7 +redirect_from: /en/chapters/sfinv.html +--- + +## Introduction + +Simple Fast Inventory (SFINV) is a mod found in Minetest Game that is used to +create the player's inventory [formspec](formspecs.html). SFINV comes with +an API that allows you to add and otherwise manage the pages shown. + +Whilst SFINV by default shows pages as tabs, pages are called pages +because it is entirely possible that a mod or game decides to show them in +some other format instead. +For example, multiple pages could be shown in one formspec. + +- [Registering a Page](#registering-a-page) +- [Receiving events](#receiving-events) +- [Conditionally showing to players](#conditionally-showing-to-players) +- [on_enter and on_leave callbacks](#onenter-and-onleave-callbacks) +- [Adding to an existing page](#adding-to-an-existing-page) + +## Registering a Page + +SFINV provides the aptly named `sfinv.register_page` function to create pages. +Simply call the function with the page's name and its definition: + +```lua +sfinv.register_page("mymod:hello", { + title = "Hello!", + get = function(self, player, context) + return sfinv.make_formspec(player, context, + "label[0.1,0.1;Hello world!]", true) + end +}) +``` + +The `make_formspec` function surrounds your formspec with SFINV's formspec code. +The fourth parameter, currently set as `true`, determines whether the +player's inventory is shown. + +Let's make things more exciting; here is the code for the formspec generation +part of a player admin tab. This tab will allow admins to kick or ban players by +selecting them in a list and clicking a button. + +```lua +sfinv.register_page("myadmin:myadmin", { + title = "Tab", + get = function(self, player, context) + local players = {} + context.myadmin_players = players + + -- Using an array to build a formspec is considerably faster + local formspec = { + "textlist[0.1,0.1;7.8,3;playerlist;" + } + + -- Add all players to the text list, and to the players list + local is_first = true + for _ , player in pairs(minetest.get_connected_players()) do + local player_name = player:get_player_name() + players[#players + 1] = player_name + if not is_first then + formspec[#formspec + 1] = "," + end + formspec[#formspec + 1] = + minetest.formspec_escape(player_name) + is_first = false + end + formspec[#formspec + 1] = "]" + + -- Add buttons + formspec[#formspec + 1] = "button[0.1,3.3;2,1;kick;Kick]" + formspec[#formspec + 1] = "button[2.1,3.3;2,1;ban;Kick + Ban]" + + -- Wrap the formspec in sfinv's layout + -- (ie: adds the tabs and background) + return sfinv.make_formspec(player, context, + table.concat(formspec, ""), false) + end, +}) +``` + +There's nothing new about the above code; all the concepts are +covered above and in previous chapters. + +
+ Player Admin Page +
+ +## Receiving events + +You can receive formspec events by adding a `on_player_receive_fields` function +to a sfinv definition. + +```lua +on_player_receive_fields = function(self, player, context, fields) + -- TODO: implement this +end, +``` + +`on_player_receive_fields` works the same as +`minetest.register_on_player_receive_fields`, except that `context` is +given instead of `formname`. +Please note that SFINV will consume events relevant to itself, such as +navigation tab events, so you won't receive them in this callback. + +Now let's implement the `on_player_receive_fields` for our admin mod: + +```lua +on_player_receive_fields = function(self, player, context, fields) + -- text list event, check event type and set index if selection changed + if fields.playerlist then + local event = minetest.explode_textlist_event(fields.playerlist) + if event.type == "CHG" then + context.myadmin_selected_idx = event.index + end + + -- Kick button was pressed + elseif fields.kick then + local player_name = + context.myadmin_players[context.myadmin_selected_idx] + if player_name then + minetest.chat_send_player(player:get_player_name(), + "Kicked " .. player_name) + minetest.kick_player(player_name) + end + + -- Ban button was pressed + elseif fields.ban then + local player_name = + context.myadmin_players[context.myadmin_selected_idx] + if player_name then + minetest.chat_send_player(player:get_player_name(), + "Banned " .. player_name) + minetest.ban_player(player_name) + minetest.kick_player(player_name, "Banned") + end + end +end, +``` + +There's a rather large problem with this, however. Anyone can kick or ban players! You +need a way to only show this to players with the kick or ban privileges. +Luckily SFINV allows you to do this! + +## Conditionally showing to players + +You can add an `is_in_nav` function to your page's definition if you'd like to +control when the page is shown: + +```lua +is_in_nav = function(self, player, context) + local privs = minetest.get_player_privs(player:get_player_name()) + return privs.kick or privs.ban +end, +``` + +If you only need to check one priv or want to perform an 'and', you should use +`minetest.check_player_privs()` instead of `get_player_privs`. + +Note that the `is_in_nav` is only called when the player's inventory formspec is +generated. This happens when a player joins the game, switches tabs, or a mod +requests for SFINV to regenerate. + +This means that you need to manually request that SFINV regenerates the inventory +formspec on any events that may change `is_in_nav`'s result. In our case, +we need to do that whenever kick or ban is granted or revoked to a player: + +```lua +local function on_grant_revoke(grantee, granter, priv) + if priv ~= "kick" and priv ~= "ban" then + return + end + + local player = minetest.get_player_by_name(grantee) + if not player then + return + end + + local context = sfinv.get_or_create_context(player) + if context.page ~= "myadmin:myadmin" then + return + end + + sfinv.set_player_inventory_formspec(player, context) +end + +minetest.register_on_priv_grant(on_grant_revoke) +minetest.register_on_priv_revoke(on_grant_revoke) +``` + +## on_enter and on_leave callbacks + +A player *enters* a tab when the tab is selected and *leaves* a +tab when another tab is about to be selected. +It's possible for multiple pages to be selected if a custom theme is +used. + +Note that these events may not be triggered by the player. +The player may not even have the formspec open at that time. +For example, on_enter is called for the home page when a player +joins the game even before they open their inventory. + +It's not possible to cancel a page change, as that would potentially +confuse the player. + +```lua +on_enter = function(self, player, context) + +end, + +on_leave = function(self, player, context) + +end, +``` + +## Adding to an existing page + +To add content to an existing page, you will need to override the page +and modify the returned formspec. + +```lua +local old_func = sfinv.registered_pages["sfinv:crafting"].get +sfinv.override_page("sfinv:crafting", { + get = function(self, player, context, ...) + local ret = old_func(self, player, context, ...) + + if type(ret) == "table" then + ret.formspec = ret.formspec .. "label[0,0;Hello]" + else + -- Backwards compatibility + ret = ret .. "label[0,0;Hello]" + end + + return ret + end +}) +``` diff --git a/_it/quality/clean_arch.md b/_it/quality/clean_arch.md new file mode 100644 index 0000000..c3a6b2c --- /dev/null +++ b/_it/quality/clean_arch.md @@ -0,0 +1,253 @@ +--- +title: Intro to Clean Architectures +layout: default +root: ../.. +idx: 8.4 +--- + +## Introduction + +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. + +> 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 (`minetest.*`, 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 + +minetest.register_chatcommand("/land", { + privs = { land = true }, + func = function(name) + land.handle_creation_request(name) + end, +}) + +minetest.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. + +
+ Diagram showing a centered text element +
+ +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 + `minetest.` 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. diff --git a/_it/quality/common_mistakes.md b/_it/quality/common_mistakes.md new file mode 100644 index 0000000..293a5aa --- /dev/null +++ b/_it/quality/common_mistakes.md @@ -0,0 +1,167 @@ +--- +title: Common Mistakes +layout: default +root: ../.. +idx: 8.1 +redirect_from: /en/chapters/common_mistakes.html +--- + +## Introduction + +This chapter details common mistakes, and how to avoid them. + +- [Never Store ObjectRefs (ie: players or entities)](#never-store-objectrefs-ie-players-or-entities) +- [Don't Trust Formspec Submissions](#dont-trust-formspec-submissions) +- [Set ItemStacks After Changing Them](#set-itemstacks-after-changing-them) + +## Never Store ObjectRefs (ie: players or entities) + +If the object an ObjectRef represents is deleted - for example, if the player goes +offline or the entity is unloaded - then calling methods on that object +will result in a crash. + +For example, don't do this: + +```lua +minetest.register_on_joinplayer(function(player) + local function func() + local pos = player:get_pos() -- BAD! + -- `player` is stored then accessed later. + -- If the player leaves in that second, the server *will* crash. + end + + minetest.after(1, func) + + foobar[player:get_player_name()] = player + -- RISKY + -- It's not recommended to do this. + -- Use minetest.get_connected_players() and + -- minetest.get_player_by_name() instead. +end) +``` + +Do this instead: + +```lua +minetest.register_on_joinplayer(function(player) + local function func(name) + -- Attempt to get the ref again + local player = minetest.get_player_by_name(name) + + -- Check that the player is still online + if player then + -- Yay! This is fine + local pos = player:get_pos() + end + end + + -- Pass the name into the function + minetest.after(1, func, player:get_player_name()) +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 minetest.check_player_privs(name, { privs = true }) then + return false + end + + minetest.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 +}) + +minetest.register_on_player_receive_fields(function(player, + formname, fields) + -- BAD! Missing privilege check here! + + local privs = minetest.get_player_privs(fields.target) + privs.kick = true + privs.ban = true + minetest.set_player_privs(fields.target, privs) + return true +end) +``` + +Add a privilege check to solve this: + +```lua +minetest.register_on_player_receive_fields(function(player, + formname, fields) + if not minetest.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 +minetest.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 +minetest.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. diff --git a/_it/quality/luacheck.md b/_it/quality/luacheck.md new file mode 100644 index 0000000..d9b4410 --- /dev/null +++ b/_it/quality/luacheck.md @@ -0,0 +1,152 @@ +--- +title: Automatic Error Checking +layout: default +root: ../.. +idx: 8.2 +description: Use LuaCheck to find errors +redirect_from: /en/chapters/luacheck.html +--- + +## Introduction + +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) +- [Checking Commits with Travis](#checking-commits-with-travis) + +## 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. + +* **Atom** - `linter-luacheck`. +* **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). + +## Checking Commits with Travis + +If your project is public and is on Github, you can use TravisCI - a free service +to run jobs on commits to check them. This means that every commit you push will +be checked against LuaCheck, and a green tick or red cross will be displayed next to them +depending on whether LuaCheck finds any mistakes. This is especially helpful for +when your project receives a pull request - you'll be able to see the LuaCheck output +without downloading the code. + +First, you should visit [travis-ci.org](https://travis-ci.org/) and sign in with +your Github account. Then find your project's repo in your Travis profile, +and enable Travis by flipping the switch. + +Next, create a file called .travis.yml with the following content: + +```yml +language: generic +sudo: false +addons: + apt: + packages: + - luarocks +before_install: + - luarocks install --local luacheck +script: +- $HOME/.luarocks/bin/luacheck . +notifications: + email: false +``` + +If your project is a game rather than a mod or mod pack, +change the line after `script:` to: + +```yml +- $HOME/.luarocks/bin/luacheck mods/ +``` + +Now commit and push to Github. Go to your project's page on Github, and click +'commits'. You should see an orange disc next to the commit you just made. +After awhile it should change either into a green tick or a red cross depending on the +outcome of LuaCheck. In either case, you can click the icon to see the build logs +and the output of LuaCheck. diff --git a/_it/quality/readmore.md b/_it/quality/readmore.md new file mode 100644 index 0000000..8dc1218 --- /dev/null +++ b/_it/quality/readmore.md @@ -0,0 +1,28 @@ +--- +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 - [HTML version]({{ page.root }}/lua_api.html) | + [Text version](https://github.com/minetest/minetest/blob/master/doc/lua_api.txt). +* Explore the [Developer Wiki](http://dev.minetest.net/Main_Page). +* Look at [existing mods](https://forum.minetest.net/viewforum.php?f=11). + +### Lua Programming + +* [Programming in Lua (PIL)](http://www.lua.org/pil/). +* [Lua Crash Course](http://luatut.com/crash_course.html). + +### 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). diff --git a/_it/quality/releasing.md b/_it/quality/releasing.md new file mode 100644 index 0000000..8d31957 --- /dev/null +++ b/_it/quality/releasing.md @@ -0,0 +1,214 @@ +--- +title: Releasing a Mod +layout: default +root: ../.. +idx: 8.6 +redirect_from: /en/chapters/releasing.html +--- + +## Introduction + +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. + +- [License Choices](#license-choices) + - [LGPL and CC-BY-SA](#lgpl-and-cc-by-sa) + - [CC0](#cc0) + - [MIT](#mit) +- [Packaging](#packaging) + - [README.txt](#readmetxt) + - [description.txt](#descriptiontxt) + - [screenshot.png](#screenshotpng) +- [Uploading](#uploading) + - [Version Control Systems](#version-control-systems) + - [Forum Attachments](#forum-attachments) +- [Forum Topic](#forum-topic) + - [Subject](#subject) + +## License Choices + +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. + +### 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 + +These licenses allow anyone to do what they want with your mod, +which means they can modify, redistribute, sell, or leave-out attribution. +These licenses can be used for both code and art. + +It is important to note that WTFPL is strongly discouraged and people may +choose not to use your mod if it has this license. + +### MIT + +This is a common license for mod code. The only restriction it places on users +of your mod is that they must include the same copyright notice and license +in any copies of the mod or of substantial parts of the mod. + +## Packaging + +There are some files that are recommended to include in your mod +before you release it. + +### README.txt + +The README file should state: + +* What the mod does. +* What the license is. +* What dependencies there are. +* How to install the mod. +* Current version of the mod. +* Optionally, the where to report problems or get help. + +### description.txt + +This should explain what your mod 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: + + Adds soup, cakes, bakes and juices. + +Avoid this: + + (BAD) The food mod for Minetest. + +### 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 in the mod store. + +## 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 on the forum and download the file + without having to view another page. +* **Virus Free** - Mods with malicious content will be removed from the forum. + +### Version Control Systems + +It is recommended that you use a version control system which: + +* Allows other developers to easily submit changes. +* Allows the code to be previewed before downloading. +* Allows users to submit bug reports. + +The majority of Minetest modders use GitHub as a website to host their code, +but alternatives are possible. + +Using a GitHub can be difficult at first. If you need help with this, for +information on using GitHub, please see: + +* [Pro Git book](http://git-scm.com/book/en/v1/Getting-Started) - Free to read online. +* [GitHub for Windows app](https://help.github.com/articles/getting-started-with-github-for-windows/) - +Using a graphical interface on Windows to upload your code. + +### Forum Attachments + +As an alternative to using a version management system, you can use forum attachments to share +your mods. This can be done when creating a mod's forum topic (covered below). + +You need to zip the files for the mod into a single file. How to do this varies from +operating system to operating system. +This is nearly always done using the right click menu after selecting all files. + +When making a forum topic, on the "Create a Topic" page (see below), go to the +"Upload Attachment" tab at the bottom. +Click "Browse" and select the zipped file. It is recommended that you +enter the version of your mod in the comment field. + +
+ Upload Attachment +
+ Upload Attachment tab. +
+
+ +## Forum Topic + +You can now create a forum topic. You should create it in +the ["WIP Mods"](https://forum.minetest.net/viewforum.php?f=9) (Work In Progress) +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 Minetest forum uses bbcode for formatting. Here is an example for a +mod named superspecial: + + + Adds magic, rainbows and other special things. + + See download attached. + + [b]Version:[/b] 1.1 + [b]License:[/b] LGPL 2.1 or later + + Dependencies: default mod (found in minetest_game) + + Report bugs or request help on the forum topic. + + [h]Installation[/h] + + Unzip the archive, rename the folder to superspecial and + place it in minetest/mods/ + + ( GNU/Linux: If you use a system-wide installation place + it in ~/.minetest/mods/. ) + + ( If you only want this to be used in a single world, place + the folder in worldmods/ in your world directory. ) + + For further information or help see: + [url]https://wiki.minetest.net/Installing_Mods[/url] + +If you modify the above example for your mod topic, remember to +change "superspecial" to the name of your mod. + +### Subject + +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] diff --git a/_it/quality/security.md b/_it/quality/security.md new file mode 100644 index 0000000..c37eba5 --- /dev/null +++ b/_it/quality/security.md @@ -0,0 +1,110 @@ +--- +title: Security +layout: default +root: ../.. +idx: 8.3 +--- + +## Introduction + +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 +minetest.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 = minetest.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. diff --git a/_it/quality/unit_testing.md b/_it/quality/unit_testing.md new file mode 100644 index 0000000..643380a --- /dev/null +++ b/_it/quality/unit_testing.md @@ -0,0 +1,195 @@ +--- +title: Automatic Unit Testing +layout: default +root: ../.. +idx: 8.5 +--- + +## Introduction + +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](#testsapispeclua) +- [Mocking: Using External Functions](#mocking-using-external-functions) +- [Checking Commits with Travis](#checking-commits-with-travis) +- [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(minetest.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 minetest.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) +``` + + +## Checking Commits with Travis + +The Travis script from the [Automatic Error Checking](luacheck.html) +chapter can be modified to also run Busted. + +```yml +language: generic +sudo: false +addons: + apt: + packages: + - luarocks +before_install: + - luarocks install --local luacheck && luarocks install --local busted +script: +- $HOME/.luarocks/bin/luacheck . +- $HOME/.luarocks/bin/busted . +notifications: + email: false +``` + + +## 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). diff --git a/_layouts/default.html b/_layouts/default.html index 4b8249f..217b93e 100644 --- a/_layouts/default.html +++ b/_layouts/default.html @@ -22,6 +22,8 @@ layout: compress {% if language == "en" %} {% assign links = site.en | sort: "idx" %} + {% else if language == "it" %} + {% assign links = site.it | sort: "idx" %} {% else %} {% assign language = "en" %} {% assign links = site.en | sort: "idx" %}