Compare commits

..

1 Commits

Author SHA1 Message Date
rubenwardy
6929d5fd0d Build lua_api.html in CI pipeline 2020-08-19 00:20:01 +01:00
84 changed files with 9946 additions and 2914 deletions

View File

@ -5,13 +5,14 @@ variables:
LC_ALL: C.UTF-8
before_script:
- apk add python3 py3-markdown
- rm Gemfile.lock
- bundle install
test:
stage: test
interruptible: true
script:
- python3 utils/update_lua_api.py
- bundle exec jekyll build -d test
artifacts:
paths:
@ -23,6 +24,7 @@ pages:
stage: deploy
interruptible: true
script:
- python3 utils/update_lua_api.py
- bundle exec jekyll build -d public
artifacts:
paths:

View File

@ -1,10 +1,8 @@
source "https://rubygems.org"
gem "jekyll"
gem "webrick"
group :jekyll_plugins do
gem "jekyll-sitemap"
gem "jekyll-redirect-from"
gem "jekyll-sass-converter", "~> 2.0"
end

View File

@ -4,31 +4,31 @@ GEM
addressable (2.7.0)
public_suffix (>= 2.0.2, < 5.0)
colorator (1.1.0)
concurrent-ruby (1.1.8)
em-websocket (0.5.2)
concurrent-ruby (1.1.7)
em-websocket (0.5.1)
eventmachine (>= 0.12.9)
http_parser.rb (~> 0.6.0)
eventmachine (1.2.7)
ffi (1.15.0)
ffi (1.13.1)
forwardable-extended (2.6.0)
http_parser.rb (0.6.0)
i18n (1.8.10)
i18n (1.8.5)
concurrent-ruby (~> 1.0)
jekyll (4.2.0)
jekyll (4.1.1)
addressable (~> 2.4)
colorator (~> 1.0)
em-websocket (~> 0.5)
i18n (~> 1.0)
jekyll-sass-converter (~> 2.0)
jekyll-watch (~> 2.0)
kramdown (~> 2.3)
kramdown (~> 2.1)
kramdown-parser-gfm (~> 1.0)
liquid (~> 4.0)
mercenary (~> 0.4.0)
pathutil (~> 0.9)
rouge (~> 3.0)
safe_yaml (~> 1.0)
terminal-table (~> 2.0)
terminal-table (~> 1.8)
jekyll-redirect-from (0.16.0)
jekyll (>= 3.3, < 5.0)
jekyll-sass-converter (2.1.0)
@ -37,39 +37,37 @@ GEM
jekyll (>= 3.7, < 5.0)
jekyll-watch (2.2.1)
listen (~> 3.0)
kramdown (2.3.1)
kramdown (2.3.0)
rexml
kramdown-parser-gfm (1.1.0)
kramdown (~> 2.0)
liquid (4.0.3)
listen (3.5.1)
listen (3.2.1)
rb-fsevent (~> 0.10, >= 0.10.3)
rb-inotify (~> 0.9, >= 0.9.10)
mercenary (0.4.0)
pathutil (0.16.2)
forwardable-extended (~> 2.6)
public_suffix (4.0.6)
rb-fsevent (0.11.0)
public_suffix (4.0.5)
rb-fsevent (0.10.4)
rb-inotify (0.10.1)
ffi (~> 1.0)
rexml (3.2.5)
rouge (3.26.0)
rexml (3.2.4)
rouge (3.22.0)
safe_yaml (1.0.5)
sassc (2.4.0)
ffi (~> 1.9)
terminal-table (2.0.0)
terminal-table (1.8.0)
unicode-display_width (~> 1.1, >= 1.1.1)
unicode-display_width (1.7.0)
webrick (1.7.0)
PLATFORMS
x86_64-linux
ruby
DEPENDENCIES
jekyll
jekyll-redirect-from
jekyll-sitemap
webrick
BUNDLED WITH
2.2.16
2.0.2

427
LICENSE
View File

@ -1,427 +0,0 @@
Attribution-ShareAlike 4.0 International
=======================================================================
Creative Commons Corporation ("Creative Commons") is not a law firm and
does not provide legal services or legal advice. Distribution of
Creative Commons public licenses does not create a lawyer-client or
other relationship. Creative Commons makes its licenses and related
information available on an "as-is" basis. Creative Commons gives no
warranties regarding its licenses, any material licensed under their
terms and conditions, or any related information. Creative Commons
disclaims all liability for damages resulting from their use to the
fullest extent possible.
Using Creative Commons Public Licenses
Creative Commons public licenses provide a standard set of terms and
conditions that creators and other rights holders may use to share
original works of authorship and other material subject to copyright
and certain other rights specified in the public license below. The
following considerations are for informational purposes only, are not
exhaustive, and do not form part of our licenses.
Considerations for licensors: Our public licenses are
intended for use by those authorized to give the public
permission to use material in ways otherwise restricted by
copyright and certain other rights. Our licenses are
irrevocable. Licensors should read and understand the terms
and conditions of the license they choose before applying it.
Licensors should also secure all rights necessary before
applying our licenses so that the public can reuse the
material as expected. Licensors should clearly mark any
material not subject to the license. This includes other CC-
licensed material, or material used under an exception or
limitation to copyright. More considerations for licensors:
wiki.creativecommons.org/Considerations_for_licensors
Considerations for the public: By using one of our public
licenses, a licensor grants the public permission to use the
licensed material under specified terms and conditions. If
the licensor's permission is not necessary for any reason--for
example, because of any applicable exception or limitation to
copyright--then that use is not regulated by the license. Our
licenses grant only permissions under copyright and certain
other rights that a licensor has authority to grant. Use of
the licensed material may still be restricted for other
reasons, including because others have copyright or other
rights in the material. A licensor may make special requests,
such as asking that all changes be marked or described.
Although not required by our licenses, you are encouraged to
respect those requests where reasonable. More considerations
for the public:
wiki.creativecommons.org/Considerations_for_licensees
=======================================================================
Creative Commons Attribution-ShareAlike 4.0 International Public
License
By exercising the Licensed Rights (defined below), You accept and agree
to be bound by the terms and conditions of this Creative Commons
Attribution-ShareAlike 4.0 International Public License ("Public
License"). To the extent this Public License may be interpreted as a
contract, You are granted the Licensed Rights in consideration of Your
acceptance of these terms and conditions, and the Licensor grants You
such rights in consideration of benefits the Licensor receives from
making the Licensed Material available under these terms and
conditions.
Section 1 -- Definitions.
a. Adapted Material means material subject to Copyright and Similar
Rights that is derived from or based upon the Licensed Material
and in which the Licensed Material is translated, altered,
arranged, transformed, or otherwise modified in a manner requiring
permission under the Copyright and Similar Rights held by the
Licensor. For purposes of this Public License, where the Licensed
Material is a musical work, performance, or sound recording,
Adapted Material is always produced where the Licensed Material is
synched in timed relation with a moving image.
b. Adapter's License means the license You apply to Your Copyright
and Similar Rights in Your contributions to Adapted Material in
accordance with the terms and conditions of this Public License.
c. BY-SA Compatible License means a license listed at
creativecommons.org/compatiblelicenses, approved by Creative
Commons as essentially the equivalent of this Public License.
d. Copyright and Similar Rights means copyright and/or similar rights
closely related to copyright including, without limitation,
performance, broadcast, sound recording, and Sui Generis Database
Rights, without regard to how the rights are labeled or
categorized. For purposes of this Public License, the rights
specified in Section 2(b)(1)-(2) are not Copyright and Similar
Rights.
e. Effective Technological Measures means those measures that, in the
absence of proper authority, may not be circumvented under laws
fulfilling obligations under Article 11 of the WIPO Copyright
Treaty adopted on December 20, 1996, and/or similar international
agreements.
f. Exceptions and Limitations means fair use, fair dealing, and/or
any other exception or limitation to Copyright and Similar Rights
that applies to Your use of the Licensed Material.
g. License Elements means the license attributes listed in the name
of a Creative Commons Public License. The License Elements of this
Public License are Attribution and ShareAlike.
h. Licensed Material means the artistic or literary work, database,
or other material to which the Licensor applied this Public
License.
i. Licensed Rights means the rights granted to You subject to the
terms and conditions of this Public License, which are limited to
all Copyright and Similar Rights that apply to Your use of the
Licensed Material and that the Licensor has authority to license.
j. Licensor means the individual(s) or entity(ies) granting rights
under this Public License.
k. Share means to provide material to the public by any means or
process that requires permission under the Licensed Rights, such
as reproduction, public display, public performance, distribution,
dissemination, communication, or importation, and to make material
available to the public including in ways that members of the
public may access the material from a place and at a time
individually chosen by them.
l. Sui Generis Database Rights means rights other than copyright
resulting from Directive 96/9/EC of the European Parliament and of
the Council of 11 March 1996 on the legal protection of databases,
as amended and/or succeeded, as well as other essentially
equivalent rights anywhere in the world.
m. You means the individual or entity exercising the Licensed Rights
under this Public License. Your has a corresponding meaning.
Section 2 -- Scope.
a. License grant.
1. Subject to the terms and conditions of this Public License,
the Licensor hereby grants You a worldwide, royalty-free,
non-sublicensable, non-exclusive, irrevocable license to
exercise the Licensed Rights in the Licensed Material to:
a. reproduce and Share the Licensed Material, in whole or
in part; and
b. produce, reproduce, and Share Adapted Material.
2. Exceptions and Limitations. For the avoidance of doubt, where
Exceptions and Limitations apply to Your use, this Public
License does not apply, and You do not need to comply with
its terms and conditions.
3. Term. The term of this Public License is specified in Section
6(a).
4. Media and formats; technical modifications allowed. The
Licensor authorizes You to exercise the Licensed Rights in
all media and formats whether now known or hereafter created,
and to make technical modifications necessary to do so. The
Licensor waives and/or agrees not to assert any right or
authority to forbid You from making technical modifications
necessary to exercise the Licensed Rights, including
technical modifications necessary to circumvent Effective
Technological Measures. For purposes of this Public License,
simply making modifications authorized by this Section 2(a)
(4) never produces Adapted Material.
5. Downstream recipients.
a. Offer from the Licensor -- Licensed Material. Every
recipient of the Licensed Material automatically
receives an offer from the Licensor to exercise the
Licensed Rights under the terms and conditions of this
Public License.
b. Additional offer from the Licensor -- Adapted Material.
Every recipient of Adapted Material from You
automatically receives an offer from the Licensor to
exercise the Licensed Rights in the Adapted Material
under the conditions of the Adapter's License You apply.
c. No downstream restrictions. You may not offer or impose
any additional or different terms or conditions on, or
apply any Effective Technological Measures to, the
Licensed Material if doing so restricts exercise of the
Licensed Rights by any recipient of the Licensed
Material.
6. No endorsement. Nothing in this Public License constitutes or
may be construed as permission to assert or imply that You
are, or that Your use of the Licensed Material is, connected
with, or sponsored, endorsed, or granted official status by,
the Licensor or others designated to receive attribution as
provided in Section 3(a)(1)(A)(i).
b. Other rights.
1. Moral rights, such as the right of integrity, are not
licensed under this Public License, nor are publicity,
privacy, and/or other similar personality rights; however, to
the extent possible, the Licensor waives and/or agrees not to
assert any such rights held by the Licensor to the limited
extent necessary to allow You to exercise the Licensed
Rights, but not otherwise.
2. Patent and trademark rights are not licensed under this
Public License.
3. To the extent possible, the Licensor waives any right to
collect royalties from You for the exercise of the Licensed
Rights, whether directly or through a collecting society
under any voluntary or waivable statutory or compulsory
licensing scheme. In all other cases the Licensor expressly
reserves any right to collect such royalties.
Section 3 -- License Conditions.
Your exercise of the Licensed Rights is expressly made subject to the
following conditions.
a. Attribution.
1. If You Share the Licensed Material (including in modified
form), You must:
a. retain the following if it is supplied by the Licensor
with the Licensed Material:
i. identification of the creator(s) of the Licensed
Material and any others designated to receive
attribution, in any reasonable manner requested by
the Licensor (including by pseudonym if
designated);
ii. a copyright notice;
iii. a notice that refers to this Public License;
iv. a notice that refers to the disclaimer of
warranties;
v. a URI or hyperlink to the Licensed Material to the
extent reasonably practicable;
b. indicate if You modified the Licensed Material and
retain an indication of any previous modifications; and
c. indicate the Licensed Material is licensed under this
Public License, and include the text of, or the URI or
hyperlink to, this Public License.
2. You may satisfy the conditions in Section 3(a)(1) in any
reasonable manner based on the medium, means, and context in
which You Share the Licensed Material. For example, it may be
reasonable to satisfy the conditions by providing a URI or
hyperlink to a resource that includes the required
information.
3. If requested by the Licensor, You must remove any of the
information required by Section 3(a)(1)(A) to the extent
reasonably practicable.
b. ShareAlike.
In addition to the conditions in Section 3(a), if You Share
Adapted Material You produce, the following conditions also apply.
1. The Adapter's License You apply must be a Creative Commons
license with the same License Elements, this version or
later, or a BY-SA Compatible License.
2. You must include the text of, or the URI or hyperlink to, the
Adapter's License You apply. You may satisfy this condition
in any reasonable manner based on the medium, means, and
context in which You Share Adapted Material.
3. You may not offer or impose any additional or different terms
or conditions on, or apply any Effective Technological
Measures to, Adapted Material that restrict exercise of the
rights granted under the Adapter's License You apply.
Section 4 -- Sui Generis Database Rights.
Where the Licensed Rights include Sui Generis Database Rights that
apply to Your use of the Licensed Material:
a. for the avoidance of doubt, Section 2(a)(1) grants You the right
to extract, reuse, reproduce, and Share all or a substantial
portion of the contents of the database;
b. if You include all or a substantial portion of the database
contents in a database in which You have Sui Generis Database
Rights, then the database in which You have Sui Generis Database
Rights (but not its individual contents) is Adapted Material,
including for purposes of Section 3(b); and
c. You must comply with the conditions in Section 3(a) if You Share
all or a substantial portion of the contents of the database.
For the avoidance of doubt, this Section 4 supplements and does not
replace Your obligations under this Public License where the Licensed
Rights include other Copyright and Similar Rights.
Section 5 -- Disclaimer of Warranties and Limitation of Liability.
a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE
EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS
AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF
ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS,
IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION,
WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR
PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS,
ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT
KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT
ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU.
b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE
TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION,
NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT,
INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES,
COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR
USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN
ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR
DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR
IN PART, THIS LIMITATION MAY NOT APPLY TO YOU.
c. The disclaimer of warranties and limitation of liability provided
above shall be interpreted in a manner that, to the extent
possible, most closely approximates an absolute disclaimer and
waiver of all liability.
Section 6 -- Term and Termination.
a. This Public License applies for the term of the Copyright and
Similar Rights licensed here. However, if You fail to comply with
this Public License, then Your rights under this Public License
terminate automatically.
b. Where Your right to use the Licensed Material has terminated under
Section 6(a), it reinstates:
1. automatically as of the date the violation is cured, provided
it is cured within 30 days of Your discovery of the
violation; or
2. upon express reinstatement by the Licensor.
For the avoidance of doubt, this Section 6(b) does not affect any
right the Licensor may have to seek remedies for Your violations
of this Public License.
c. For the avoidance of doubt, the Licensor may also offer the
Licensed Material under separate terms or conditions or stop
distributing the Licensed Material at any time; however, doing so
will not terminate this Public License.
d. Sections 1, 5, 6, 7, and 8 survive termination of this Public
License.
Section 7 -- Other Terms and Conditions.
a. The Licensor shall not be bound by any additional or different
terms or conditions communicated by You unless expressly agreed.
b. Any arrangements, understandings, or agreements regarding the
Licensed Material not stated herein are separate from and
independent of the terms and conditions of this Public License.
Section 8 -- Interpretation.
a. For the avoidance of doubt, this Public License does not, and
shall not be interpreted to, reduce, limit, restrict, or impose
conditions on any use of the Licensed Material that could lawfully
be made without permission under this Public License.
b. To the extent possible, if any provision of this Public License is
deemed unenforceable, it shall be automatically reformed to the
minimum extent necessary to make it enforceable. If the provision
cannot be reformed, it shall be severed from this Public License
without affecting the enforceability of the remaining terms and
conditions.
c. No term or condition of this Public License will be waived and no
failure to comply consented to unless expressly agreed to by the
Licensor.
d. Nothing in this Public License constitutes or may be interpreted
as a limitation upon, or waiver of, any privileges and immunities
that apply to the Licensor or You, including from the legal
processes of any jurisdiction or authority.
=======================================================================
Creative Commons is not a party to its public
licenses. Notwithstanding, Creative Commons may elect to apply one of
its public licenses to material it publishes and in those instances
will be considered the “Licensor.” The text of the Creative Commons
public licenses is dedicated to the public domain under the CC0 Public
Domain Dedication. Except for the limited purpose of indicating that
material is shared under a Creative Commons public license or as
otherwise permitted by the Creative Commons policies published at
creativecommons.org/policies, Creative Commons does not authorize the
use of the trademark "Creative Commons" or any other trademark or logo
of Creative Commons without its prior written consent including,
without limitation, in connection with any unauthorized modifications
to any of its public licenses or any other arrangements,
understandings, or agreements concerning use of licensed material. For
the avoidance of doubt, this paragraph does not form part of the
public licenses.
Creative Commons may be contacted at creativecommons.org.

View File

@ -1,4 +1,4 @@
# Luanti Modding Book
# Minetest Modding Book
[![Build status](https://gitlab.com/rubenwardy/minetest_modding_book/badges/master/pipeline.svg)](https://gitlab.com/rubenwardy/minetest_modding_book/pipelines)<br>
[Read Online](https://rubenwardy.com/minetest_modding_book/)
@ -23,25 +23,15 @@ License: CC-BY-SA 3.0
I'm happy to fix the formatting of any chapters. It is
the writing which is the hard bit, not the formatting.
### Chapter and Writing Guide
### Chapter Guidelines
Grammar and such:
* British English, except when referring common code words like `color` and
`initialize`.
* Prefer pronounless text, but `you` if you must. Never `we` nor `I`.
* Titles and subheadings should be in Title Case.
* References to code (such as function names) should be formatted as \`inline-code`.
* Italics used for emphasis, not necessarily for technical words.
* Full stops and correct punctionation, except for lists without full sentences.
Formatting:
* Do not rely on anything that isn't printable to a physical book.
* Any links must be invisible - ie: if they're removed, then the chapter must
still make sense.
* Table of contents for each chapter with anchor links.
* Add `your turn`s to the end of a chapter when relevant.
* Titles and subheadings should be in Title Case.
### Making a Chapter

View File

@ -1,6 +1,3 @@
url: "https://rubenwardy.com"
baseurl: "/minetest_modding_book"
sass:
# nested (default), compact, compressed, expanded
style: compressed

View File

@ -130,7 +130,7 @@ programs such as [Geogebra](https://www.geogebra.org).
The following code registers a simple biome named grasslands biome:
```lua
core.register_biome({
minetest.register_biome({
name = "grasslands",
node_top = "default:dirt_with_grass",
depth_top = 1,
@ -174,7 +174,7 @@ details for where it can be placed, and how frequently it occurs.
For example:
```lua
core.register_decoration({
minetest.register_decoration({
deco_type = "simple",
place_on = {"base:dirt_with_grass"},
sidelen = 16,
@ -199,7 +199,7 @@ Schematic decorations are very similar to simple decoration, but involve the pla
of a schematic instead of the placement of a single node. For example:
```lua
core.register_decoration({
minetest.register_decoration({
deco_type = "schematic",
place_on = {"base:desert_sand"},
sidelen = 16,
@ -207,7 +207,7 @@ core.register_decoration({
biomes = {"desert"},
y_max = 200,
y_min = 1,
schematic = core.get_modpath("plants") .. "/schematics/cactus.mts",
schematic = minetest.get_modpath("plants") .. "/schematics/cactus.mts",
flags = "place_center_x, place_center_z",
rotation = "random",
})
@ -229,7 +229,7 @@ to consider registering mapgen aliases of your own if you are making your own ga
Mapgen aliases provide information to the core mapgen, and can be registered in the form:
```lua
core.register_alias("mapgen_stone", "base:smoke_stone")
minetest.register_alias("mapgen_stone", "base:smoke_stone")
```
At a minimum you should register:

View File

@ -10,13 +10,13 @@ redirect_from:
mapgen_object:
level: warning
title: LVMs and Mapgen
message: Don't use `core.get_voxel_manip()` with mapgen, as it can cause glitches.
Use `core.get_mapgen_object("voxelmanip")` instead.
message: Don't use `minetest.get_voxel_manip()` with mapgen, as it can cause glitches.
Use `minetest.get_mapgen_object("voxelmanip")` instead.
---
## Introduction <!-- omit in toc -->
The functions outlined in the [Basic Map Operations](../map/environment.html) chapter
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
@ -45,7 +45,7 @@ and maximum positions that you need to modify. Then you can create and read into
an LVM. For example:
```lua
local vm = core.get_voxel_manip()
local vm = minetest.get_voxel_manip()
local emin, emax = vm:read_from_map(pos1, pos2)
```
@ -93,7 +93,7 @@ You can find out the content ID for a particular type of node with
`get_content_id()`. For example:
```lua
local c_stone = core.get_content_id("default:stone")
local c_stone = minetest.get_content_id("default:stone")
```
You can then check whether the node is stone:
@ -105,8 +105,9 @@ if data[idx] == c_stone then
end
```
Content IDs of a node type may change during load time, so it is recommended that
you don't try getting them during this time.
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:
@ -164,17 +165,18 @@ For setting lighting and param2 data, use the appropriately named
`write_to_map()` takes a Boolean which is true if you want lighting to be
calculated. If you pass false, you need to recalculate lighting at a future
time using `core.fix_light`.
time using `minetest.fix_light`.
## Example
```lua
local function grass_to_dirt(pos1, pos2)
local c_dirt = core.get_content_id("default:dirt")
local c_grass = core.get_content_id("default:dirt_with_grass")
-- 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 = core.get_voxel_manip()
local vm = minetest.get_voxel_manip()
local emin, emax = vm:read_from_map(pos1, pos2)
local a = VoxelArea:new{
MinEdge = emin,

View File

@ -12,18 +12,20 @@ redirect_from:
## Introduction <!-- omit in toc -->
Understanding the basic structure of a mod's folder is an essential skill when
creating mods. In this chapter, you'll learn about how modding in Minetest works
and create your first mod.
creating mods.
- [What are Games and Mods?](#what-are-games-and-mods)
- [Where are mods stored?](#where-are-mods-stored)
- [Creating your first mod](#creating-your-first-mod)
- [Mod directory](#mod-directory)
- [mod.conf](#modconf)
- [init.lua](#initlua)
- [Summary](#summary)
- [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?
@ -53,7 +55,7 @@ and is applicable for both game developers and modders.
<a name="mod-locations"></a>
Each mod has its own directory where its Lua code, textures, models, and
sounds are placed. Minetest checks in several different locations for
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.
@ -68,115 +70,103 @@ They are, in order:
particular world.
Eg: `minetest/worlds/world/worldmods/`
`minetest` is the user-data directory. You can find the location of the
user-data directory by opening up Minetest and clicking
"Open User Data Directory" in the Credits tab.
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.
When loading mods, Minetest will check each of the above locations in order.
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/`.
## Creating your first mod
## Mod Directory
### Mod directory
![Find the mod's directory]({{ page.root }}/static/folder_modfolder.jpg)
Go to the global mods directory (About > Open user data directory > mods) and
create a new folder called "mymod". `mymod` is the mod name.
Each mod should have a unique *mod name*, a technical identifier (id) used to
refer to the mod. Mod names can include letters, numbers, and underscores. A
good name should describe what the mod does, and the directory that 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
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
├── textures
│   └── mymod_node.png files
├── init.lua
└── mod.conf
├── 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
Mods only require an init.lua file;
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.
### mod.conf
Create a mod.conf file with the following content:
```
name = mymod
description = Adds foo, bar, and bo.
depends = default
```
This file is used for mod metadata including the mod's name, description, and other
information.
### init.lua
Create an init.lua file with the following content:
```lua
print("This file will be run at load time!")
core.register_node("mymod:node", {
description = "This is a node",
tiles = {"mymod_node.png"},
groups = {cracky = 1}
})
core.register_craft({
type = "shapeless",
output = "mymod:node 3",
recipe = { "default:dirt", "default:stone" },
})
```
The init.lua file is the entrypoint to a mod, and runs when the mod is loaded.
### Summary
This mod has the name "mymod". It has two text files: init.lua and mod.conf. The
script prints a message and then registers a node and a craft recipe these
will be explained later on. 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.
## 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.
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.
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 are specified in a comma-separated list in mod.conf.
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.
## Mod Packs
Mods can be grouped into mod packs, which allow multiple mods to be packaged
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.conf (required) - signals that this is a mod pack
├── modpack.lua (required) - signals that this is a mod pack
├── mod1
│   └── ... mod files
└── mymod (optional)
@ -185,3 +175,43 @@ a player, but don't want to make them download each one individually.
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.

View File

@ -9,47 +9,26 @@ redirect_from: /en/chapters/lua.html
## Introduction <!-- omit in toc -->
In this chapter, you'll learn about scripting in Lua, the tools required
to help with this, and some techniques that you may find useful.
In this chapter we will talk about scripting in Lua, the tools required,
and go over some techniques which you will probably find useful.
- [Programming](#programming)
- [Coding in Lua](#coding-in-lua)
- [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)
## Programming
Programming is the action of taking a problem, such as sorting a list
of items, and 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 write code. It provides an interactive tutorial experience.
* [Scratch](https://scratch.mit.edu) is a good resource for starting from
absolute basics, and learning the problem-solving techniques required to program.
It's great for children and teenagers.
* [Programming with Mosh](https://www.youtube.com/user/programmingwithmosh) is
a good YouTube series to learn programming.
### Coding in Lua
It's also beyond the scope of this book to teach Lua coding.
The [Programming in Lua (PiL)](https://www.lua.org/pil/contents.html) book is an
excellent introduction to Lua programming.
## Code Editors
A code editor with code highlighting is sufficient for writing scripts in Lua.
Code highlighting uses different colours for words and characters
depending on what they represent. This allows you to easily notice
mistakes and inconsistencies.
For example:
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)
@ -67,41 +46,151 @@ function ctf.post(team,msg)
end
```
Keywords in this example are highlighted, including `if`, `then`, `end`, and `return`.
Functions which come with Lua by default, such as `table.insert`, are also highlighted.
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.
Commonly used editors which are well-suited for Lua include:
Here is a list of common editors well suited for Lua.
Other editors are available, of course.
* [VSCode](https://code.visualstudio.com/):
open source (as Code-OSS or VSCodium), popular, and has
[plugins for Minetest](https://marketplace.visualstudio.com/items?itemName=GreenXenith.minetest-tools).
* [Notepad++](http://notepad-plus-plus.org/): simple, Windows-only
* 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/)
Other suitable editors are also available.
## 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 = 2<sup>2</sup> = 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 from. Global variables can be accessed from anywhere in the script file,
and from any other mod:
```lua
function one()
foo = "bar"
end
function two()
print(dump(foo)) -- Output: "bar"
end
one()
two()
```
In constrast, a local variable is only accessible from where it is defined.
Lua defaults to variables being global, so you need to explicitly use the
`local` keyword:
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
@ -118,12 +207,44 @@ function myfunc()
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
Local variables should be used whenever possible. Mods should only create one
global at most, with the same name as the mod. Creating other globals is sloppy
coding, and Minetest will warn about this:
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
@ -142,11 +263,12 @@ one()
two()
```
Remember that nil means **not initialised**. The variable hasn't been assigned a
value yet, doesn't exist, or has been uninitialised (meaning set to nil).
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).
Functions are variables of a special type, but should also be made local,
because other mods could have functions with the same names.
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)
@ -154,9 +276,7 @@ local function foo(bar)
end
```
To allow mods to call your functions, you should create a table with the same
name as the mod and add your function to it. This table is often called an API
table or namespace.
API tables should be used to allow other mods to call the functions, like so:
```lua
mymod = {}
@ -169,29 +289,25 @@ end
mymod.foo("foobar")
```
`function mymod.foo()` is equivalent to `mymod.foo = function()`, it's just a
nicer way to write it.
## Including other Lua Scripts
The recommended way to include other Lua scripts in a mod is to use *dofile*.
```lua
dofile(core.get_modpath("modname") .. "/script.lua")
dofile(minetest.get_modpath("modname") .. "/script.lua")
```
A script can return a value, which is useful for sharing private locals:
```lua
-- script.lua
local module = {}
module.message = "Hello World!"
return module
return "Hello world!"
-- init.lua
local ret = dofile(core.get_modpath("modname") .. "/script.lua")
print(ret.message) -- Hello world!
local ret = dofile(minetest.get_modpath("modname") .. "/script.lua")
print(ret) -- Hello world!
```
[Later chapters](../quality/clean_arch.html) will discuss how best to split up
code for a mod.
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.

View File

@ -57,7 +57,7 @@ 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,
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.

View File

@ -1,7 +1,6 @@
---
title: Front Cover
layout: default
description: An easy guide to learn how to create mods for Minetest
homepage: true
no_header: true
root: ..
@ -9,7 +8,7 @@ idx: 0.1
---
<header>
<h1>Luanti Modding Book (formerly Minetest)</h1>
<h1>Minetest Modding Book</h1>
<span>by <a href="https://rubenwardy.com" rel="author">rubenwardy</a></span>
<span>with editing by <a href="http://rc.minetest.tv/">Shara</a></span>

View File

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

View File

@ -25,10 +25,8 @@ available, which cover pixel art in much more detail.
- [Using the Pencil](#using-the-pencil)
- [Tiling](#tiling)
- [Transparency](#transparency)
- [Color Palettes](#color-palettes)
- [Editors](#editors)
- [MS Paint](#ms-paint)
- [Aseprite / LibreSprite](#aseprite--libresprite)
- [GIMP](#gimp)
## Techniques
@ -61,13 +59,6 @@ and some nodes, such as glass.
Not all editors support transparency, so make sure you choose an
editor which is suitable for the textures you wish to create.
### Color Palettes
Using a consistent color palette is an easy way to make your art look a lot
better. It's a good idea to use one with a limited number of colors, perhaps 32
at most. Premade palettes can be found at
[lospec.com](https://lospec.com/palette-list).
## Editors
### MS Paint
@ -78,21 +69,16 @@ This usually won't matter when making textures for the sides of nodes,
but if you need transparency in your textures you should choose a
different editor.
### Aseprite / LibreSprite
[Aseprite](https://www.aseprite.org/) is a proprietary pixel art editor.
It contains a lot of useful features by default such as color palettes and
animation tools.
[LibreSprite](https://libresprite.github.io/) is an open-source fork of Aseprite
from before it went proprietary.
### GIMP
GIMP is commonly used in the Minetest community. It has quite a high
learning curve because many of its features are not immediately
obvious.
When using GIMP, make sure to use the Pencil tool with the Pixel brush and a
size of 1. It's also advisable to select the "Hard edge" checkbox for the Eraser
tool.
When using GIMP, the pencil tool can be selected from the Toolbox:
<figure>
<img src="{{ page.root }}//static/pixel_art_gimp_pencil.png" alt="Pencil in GIMP">
</figure>
It's also advisable to select the Hard edge checkbox for the eraser tool.

View File

@ -19,9 +19,6 @@ that be a player inventory, a node inventory, or a detached inventory.
- [What are ItemStacks and Inventories?](#what-are-itemstacks-and-inventories)
- [ItemStacks](#itemstacks)
- [Inventory Locations](#inventory-locations)
- [Node Inventories](#node-inventories)
- [Player Inventories](#player-inventories)
- [Detached Inventories](#detached-inventories)
- [Lists](#lists)
- [Size and Width](#size-and-width)
- [Checking Contents](#checking-contents)
@ -36,21 +33,21 @@ that be a player inventory, a node inventory, or a detached inventory.
An ItemStack is the data behind a single cell in an inventory.
An *inventory* is a collection of *inventory lists*, each of which is a 2D grid
of ItemStacks. Inventory lists are referred to as *lists* in the context of
inventories.
Players and nodes only have a single inventory; lists enable you to have
multiple grids within that inventory. By default, the player has the "main" list
for the bulk of its inventory and a few lists for the crafting system.
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 four components to them: `name`, `count`, `wear`, and metadata.
ItemStacks have four components to them: name, count, wear and metadata.
The item name may be the item name of a registered item, an alias, or an unknown
item name. Unknown items are common when users uninstall mods, or when mods
remove items without precautions, such as registering aliases.
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())
@ -61,14 +58,19 @@ if not stack:is_known() then
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.
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
@ -76,7 +78,7 @@ print(stack:get_count())
stack:set_count(10)
```
ItemStacks can be constructed in multiple ways using the ItemStack function:
ItemStacks can be constructed in multiple ways using the ItemStack function.
```lua
ItemStack() -- name="", count=0
@ -85,30 +87,24 @@ ItemStack("default:stone 30")
ItemStack({ name = "default:wood", count = 10 })
```
Item metadata is an unlimited key-value store for data about the item. Key-value
means that you use a name (called the key) to access the data (called the
value). Some keys have special meaning, such as `description` which is used to
have a per-stack item description. This will be covered in more detail in the
[Storage and Metadata](../map/storage.html) chapter.
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.
An Inventory Location is where and how the inventory is stored.
There are three types of inventory location: player, node, and detached.
An inventory is directly tied to one and only one location - updating the inventory
will cause it to update immediately.
### Node Inventories
Node inventories are related to the position of a specific node, such as a
chest. The node must be loaded because it is stored in
[node metadata](../map/storage.html#metadata).
Node inventories are related to the position of a specific node, such as a chest.
The node must be loaded because it is stored in [node metadata](../map/storage.html#metadata).
```lua
on_punch = function(pos, node)
local inv = core.get_inventory({ type="node", pos=pos })
-- now use the inventory
end,
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*.
@ -122,24 +118,20 @@ The location of an inventory reference can be found like so:
local location = inv:get_location()
```
### Player Inventories
Player inventories can be obtained similarly or using a player reference.
The player must be online to access their inventory.
```lua
local inv = core.get_inventory({ type="player", name="player1" })
local inv = minetest.get_inventory({ type="player", name="player1" })
-- or
local inv = player:get_inventory()
```
### Detached Inventories
A detached inventory is one that is independent of players or nodes. Detached
inventories also don't save over a restart.
A detached inventory is one which is independent of players or nodes.
Detached inventories also don't save over a restart.
```lua
local inv = core.get_inventory({
local inv = minetest.get_inventory({
type="detached", name="inventory_name" })
```
@ -147,16 +139,17 @@ Unlike the other types of inventory, you must first create a detached inventory
before accessing it:
```lua
core.create_detached_inventory("inventory_name")
minetest.create_detached_inventory("inventory_name")
```
The `create_detached_inventory` function accepts 3 arguments, where only the
first - the inventory name - is required. The second argument takes a table of
callbacks, which can be used to control how players interact with the inventory:
The create_detached_inventory function accepts 3 arguments, where only the first - the inventory name -
is required.
The second argument takes a table of callbacks, which can be used to control how
players interact with the inventory:
```lua
-- Input only detached inventory
core.create_detached_inventory("inventory_name", {
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,
@ -166,28 +159,27 @@ core.create_detached_inventory("inventory_name", {
end,
allow_take = function(inv, listname, index, stack, player)
return 0 -- don't allow taking
return -1 -- don't allow taking
end,
on_put = function(inv, listname, index, stack, player)
core.chat_send_all(player:get_player_name() ..
minetest.chat_send_all(player:get_player_name() ..
" gave " .. stack:to_string() ..
" to the donation chest from " .. core.pos_to_string(player:get_pos()))
" to the donation chest from " .. minetest.pos_to_string(player:get_pos()))
end,
})
```
Permission callbacks - ie: those starting with `allow_` - return the number
of items to transfer, with 0 being used to prevent transfer completely.
of items to transfer, with -1 being used to prevent transfer completely.
On the contrary, action callbacks - starting with `on_` - don't have a return value.
## Lists
Inventory Lists are a concept used to allow multiple grids to be stored inside a
single location. This is especially useful for the player as there are several
common lists that all games have, such as the *main* inventory and *craft*
slots.
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

View File

@ -19,7 +19,7 @@ In the previous chapter, the concept of nodes and items was introduced, but a
full definition of a node wasn't given. The Minetest world is a 3D grid of
positions. Each position is called a node, and consists of the node type
(name) and two parameters (param1 and param2). The function
`core.register_node` is a bit misleading in that it doesn't actually
`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.
@ -28,7 +28,7 @@ The node params are used to control how a node is individually rendered.
- [Cubic Nodes: Normal and Allfaces](#cubic-nodes-normal-and-allfaces)
- [Glasslike Nodes](#glasslike-nodes)
- [Glasslike_Framed](#glasslike_framed)
- [Glasslike_Framed](#glasslikeframed)
- [Airlike Nodes](#airlike-nodes)
- [Lighting and Sunlight Propagation](#lighting-and-sunlight-propagation)
- [Liquid Nodes](#liquid-nodes)
@ -60,13 +60,13 @@ leaf nodes. You can use the allfaces_optional drawtype to allow users to opt-out
of the slower drawing, in which case it'll act like a normal node.
```lua
core.register_node("mymod:diamond", {
minetest.register_node("mymod:diamond", {
description = "Alien Diamond",
tiles = {"mymod_diamond.png"},
groups = {cracky = 3},
})
core.register_node("default:leaves", {
minetest.register_node("default:leaves", {
description = "Leaves",
drawtype = "allfaces_optional",
tiles = {"default_leaves.png"}
@ -92,7 +92,7 @@ drawtype would result in the ability to see through the world.
</figure>
```lua
core.register_node("default:obsidian_glass", {
minetest.register_node("default:obsidian_glass", {
description = "Obsidian Glass",
drawtype = "glasslike",
tiles = {"default_obsidian_glass.png"},
@ -120,11 +120,11 @@ You can use the glasslike_framed_optional drawtype to allow the user to *opt-in*
to the framed appearance.
```lua
core.register_node("default:glass", {
minetest.register_node("default:glass", {
description = "Glass",
drawtype = "glasslike_framed",
tiles = {"default_glass.png", "default_glass_detail.png"},
inventory_image = core.inventorycube("default_glass.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},
@ -138,7 +138,7 @@ core.register_node("default:glass", {
These nodes are not rendered and thus have no textures.
```lua
core.register_node("myair:air", {
minetest.register_node("myair:air", {
description = "MyAir (you hacker you!)",
drawtype = "airlike",
paramtype = "light",
@ -147,7 +147,7 @@ core.register_node("myair:air", {
walkable = false, -- Would make the player collide with the air node
pointable = false, -- You can't select the node
diggable = false, -- You can't dig the node
buildable_to = true, -- Nodes can replace this node.
buildable_to = true, -- Nodes can be replace this node.
-- (you can place a node and remove the air node
-- that used to be there)
@ -192,11 +192,11 @@ another for flowing liquid.
```lua
-- Some properties have been removed as they are beyond
-- the scope of this chapter.
core.register_node("default:water_source", {
minetest.register_node("default:water_source", {
drawtype = "liquid",
paramtype = "light",
inventory_image = core.inventorycube("default_water.png"),
inventory_image = minetest.inventorycube("default_water.png"),
-- ^ this is required to stop the inventory image from being animated
tiles = {
@ -271,7 +271,7 @@ Node boxes allow you to create a node which is not cubic, but is instead made ou
of as many cuboids as you like.
```lua
core.register_node("stairs:stair_stone", {
minetest.register_node("stairs:stair_stone", {
drawtype = "nodebox",
paramtype = "light",
node_box = {
@ -305,7 +305,7 @@ create node boxes by dragging the edges, it is more visual than doing it by hand
Sometimes you want different nodeboxes for when it is placed on the floor, wall, or ceiling like with torches.
```lua
core.register_node("default:sign_wall", {
minetest.register_node("default:sign_wall", {
drawtype = "nodebox",
node_box = {
type = "wallmounted",
@ -341,7 +341,7 @@ invisible but still rendered.
You can register a mesh node as so:
```lua
core.register_node("mymod:meshy", {
minetest.register_node("mymod:meshy", {
drawtype = "mesh",
-- Holds the texture for each "material"
@ -370,7 +370,7 @@ instead use the `nodebox` drawtype to provide a 3D effect. The `signlike` drawty
is, however, commonly used by ladders.
```lua
core.register_node("default:ladder_wood", {
minetest.register_node("default:ladder_wood", {
drawtype = "signlike",
tiles = {"default_ladder_wood.png"},
@ -397,7 +397,7 @@ core.register_node("default:ladder_wood", {
Plantlike nodes draw their tiles in an X like pattern.
```lua
core.register_node("default:papyrus", {
minetest.register_node("default:papyrus", {
drawtype = "plantlike",
-- Only one texture used
@ -423,7 +423,7 @@ and ceilings.
</figure>
```lua
core.register_node("mymod:clingere", {
minetest.register_node("mymod:clingere", {
drawtype = "firelike",
-- Only one texture used
@ -442,5 +442,5 @@ This is not a comprehensive list, there are more types including:
The torches in Minetest Game actually use two different node definitions of
mesh nodes (default:torch and default:torch_wall).
As always, read the [Lua API documentation](https://minetest.gitlab.io/minetest/nodes/#node-drawtypes)
As always, read the [Lua API documentation](../../lua_api.html#node-drawtypes)
for the complete list.

View File

@ -14,10 +14,11 @@ basic requirements for many mods.
- [What are Nodes and Items?](#what-are-nodes-and-items)
- [Registering Items](#registering-items)
- [Item Names](#item-names)
- [Item Aliases](#item-aliases)
- [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)
@ -27,63 +28,62 @@ basic requirements for many mods.
## What are Nodes and Items?
Nodes, craftitems, and tools are all Items. An item is something that could be
found in an inventory - even if it isn't possible through normal gameplay.
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 that 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 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 is like a craftitem but has the ability to wear. As you use the tool, the
wear bar goes down until the tool breaks. Tools can also never be stacked. In
the future, it's likely that craftitems and tools will merge into one type of
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 that affect the behaviour of the item.
The definition table contains attributes which affect the behaviour of the item.
```lua
core.register_craftitem("modname:itemname", {
minetest.register_craftitem("modname:itemname", {
description = "My Special Item",
inventory_image = "modname_itemname.png"
})
```
### Item Names
### 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.
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.
Both `modname` and `itemname` should only contain lowercase letters, numbers,
and underscores.
### Item Aliases
Items can also have *aliases* pointing to their name. An *alias* is a
pseudo-item name that 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:
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.
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
core.register_alias("dirt", "default:dirt")
minetest.register_alias("dirt", "default:dirt")
```
Mods need to make sure to resolve aliases before dealing directly with item names,
@ -91,7 +91,7 @@ as the engine won't do this.
This is pretty simple though:
```lua
itemname = core.registered_aliases[itemname] or itemname
itemname = minetest.registered_aliases[itemname] or itemname
```
### Textures
@ -102,18 +102,16 @@ JPEG textures are supported, but they do not support transparency and are genera
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, especially phones, resulting in degraded performance.
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
Registering nodes is similar to registering items, just with a different
function:
```lua
core.register_node("mymod:diamond", {
minetest.register_node("mymod:diamond", {
description = "Alien Diamond",
tiles = {"mymod_diamond.png"},
is_ground_content = true,
@ -121,9 +119,6 @@ core.register_node("mymod:diamond", {
})
```
Node definitions can contain any property in an item definition, and also
contain additional properties specific to nodes.
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:
@ -132,10 +127,10 @@ To give a different texture per-side, supply the names of 6 textures in this ord
(+Y, -Y, +X, -X, +Z, -Z)
Remember that +Y is upwards in Minetest, as is the convention with
most 3D computer games.
3D computer graphics.
```lua
core.register_node("mymod:diamond", {
minetest.register_node("mymod:diamond", {
description = "Alien Diamond",
tiles = {
"mymod_diamond_up.png", -- y+
@ -152,10 +147,53 @@ core.register_node("mymod:diamond", {
})
```
The `is_ground_content` attribute allows caves to be generated over the stone.
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`
@ -178,7 +216,7 @@ pattern to work. In the example below, the fragments need to be in a
chair-like pattern for the craft to work.
```lua
core.register_craft({
minetest.register_craft({
type = "shaped",
output = "mymod:diamond_chair 99",
recipe = {
@ -196,7 +234,7 @@ If this empty column shouldn't be required, then the empty strings can be left
out like so:
```lua
core.register_craft({
minetest.register_craft({
output = "mymod:diamond_chair 99",
recipe = {
{"mymod:diamond_fragments", "" },
@ -215,7 +253,7 @@ 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
core.register_craft({
minetest.register_craft({
type = "shapeless",
output = "mymod:diamond 3",
recipe = {
@ -232,7 +270,7 @@ 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
core.register_craft({
minetest.register_craft({
type = "cooking",
output = "mymod:diamond_fragments",
recipe = "default:coalblock",
@ -254,7 +292,7 @@ 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
core.register_craft({
minetest.register_craft({
type = "fuel",
recipe = "mymod:diamond",
burntime = 300,
@ -281,14 +319,13 @@ 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
core.register_craft({
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
@ -322,7 +359,7 @@ 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
core.register_tool("mymod:tool", {
minetest.register_tool("mymod:tool", {
description = "My Tool",
inventory_image = "mymod_tool.png",
tool_capabilities = {

View File

@ -9,8 +9,7 @@ redirect_from: /en/chapters/environment.html
## Introduction <!-- omit in toc -->
In this chapter, you will learn how to perform basic actions on the map, such as
adding, removing, and finding nodes.
In this chapter, you will learn how to perform basic actions on the map.
- [Map Structure](#map-structure)
- [Reading](#reading)
@ -24,17 +23,15 @@ adding, removing, and finding nodes.
## Map Structure
The Minetest map is split into MapBlocks, each MapBlocks being a cube of
size 16. As players travel around the map, MapBlocks are created, loaded,
activated, and unloaded. Areas of the map which are not yet loaded are full of
*ignore* nodes, an impassable unselectable placeholder node. Empty space is
full of *air* nodes, an invisible node you can walk through.
An active MapBlock is one which is loaded and has updates running on it.
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.
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
@ -48,7 +45,7 @@ loaded from the world database outside of the generation limit.
You can read from the map once you have a position:
```lua
local node = core.get_node({ x = 1, y = 3, z = 4 })
local node = minetest.get_node({ x = 1, y = 3, z = 4 })
print(dump(node)) --> { name=.., param1=.., param2=.. }
```
@ -62,7 +59,7 @@ The function will always return a table containing the node information:
It's worth noting that the function won't load the containing block if the block
is inactive, but will instead return a table with `name` being `ignore`.
You can use `core.get_node_or_nil` instead, which will return `nil` rather
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
@ -77,53 +74,48 @@ For example, say we wanted to make a certain type of plant that grows
better near mese; you would need to search for any nearby mese nodes,
and adapt the growth rate accordingly.
`core.find_node_near` will return the first found node in a certain radius
which matches the node names or groups given. In the following example,
we look for a mese node within 5 nodes of the position:
```lua
local grow_speed = 1
local node_pos = core.find_node_near(pos, 5, { "default:mese" })
local node_pos = minetest.find_node_near(pos, 5, { "default:mese" })
if node_pos then
core.chat_send_all("Node found at: " .. dump(node_pos))
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 that can find multiple nodes in the area:
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 =
core.find_nodes_in_area(pos1, pos2, { "default:mese" })
minetest.find_nodes_in_area(pos1, pos2, { "default:mese" })
local grow_speed = 1 + #pos_list
```
The above code finds the number of nodes in a *cuboid volume*. This is different
to `find_node_near`, which uses the distance to the position (ie: a *sphere*). In
order to fix this, we will need to manually check the range ourselves:
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 =
core.find_nodes_in_area(pos1, pos2, { "default:mese" })
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 + delta.z*delta.z <= 5*5 then
if delta.x*delta.x + delta.y*delta.y <= 5*5 then
grow_speed = grow_speed + 1
end
end
```
Now the code will correctly increase `grow_speed` based on mese nodes in range.
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 they should avoided as much as possible.
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
@ -134,26 +126,26 @@ and are useful in other circumstances.
### Writing Nodes
You can use `set_node` to write to the map. Each call to set_node will cause
lighting to be recalculated and node callbacks to run, which means that set_node
is fairly slow for large numbers of nodes.
lighting to be recalculated, which means that set_node is fairly slow for large
numbers of nodes.
```lua
core.set_node({ x = 1, y = 3, z = 4 }, { name = "default:mese" })
minetest.set_node({ x = 1, y = 3, z = 4 }, { name = "default:mese" })
local node = core.get_node({ x = 1, y = 3, z = 4 })
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 conceptually think of it as one node, it's actually
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
core.swap_node({ x = 1, y = 3, z = 4 }, { name = "default:mese" })
minetest.swap_node({ x = 1, y = 3, z = 4 }, { name = "default:mese" })
```
### Removing Nodes
@ -163,15 +155,15 @@ A node must always be present. To remove a node, you set the position to `air`.
The following two lines will both remove a node, and are both identical:
```lua
core.remove_node(pos)
core.set_node(pos, { name = "air" })
minetest.remove_node(pos)
minetest.set_node(pos, { name = "air" })
```
In fact, remove_node is just a helper function that calls set_node with `"air"`.
In fact, remove_node will call set_node with the name being air.
## Loading Blocks
You can use `core.emerge_area` to load map blocks. Emerge area is asynchronous,
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.
@ -182,7 +174,7 @@ local pos1 = vector.subtract(pos, halfsize)
local pos2 = vector.add (pos, halfsize)
local context = {} -- persist data between callback calls
core.emerge_area(pos1, pos2, emerge_callback, context)
minetest.emerge_area(pos1, pos2, emerge_callback, context)
```
Minetest will call `emerge_callback` whenever it loads a block, with some
@ -202,18 +194,17 @@ local function emerge_callback(pos, action,
-- Send progress message
if context.total_blocks == context.loaded_blocks then
core.chat_send_all("Finished loading blocks!")
else
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)
core.chat_send_all(msg)
minetest.chat_send_all(msg)
end
end
```
This is not the only way of loading blocks; using an
[Lua Voxel Manipulator (LVM)](../advmap/lvm.html) will also cause the
This is not the only way of loading blocks; using an LVM will also cause the
encompassed blocks to be loaded synchronously.
## Deleting Blocks
@ -226,7 +217,7 @@ local halfsize = { x = 10, y = 10, z = 10 }
local pos1 = vector.subtract(pos, halfsize)
local pos2 = vector.add (pos, halfsize)
core.delete_area(pos1, pos2)
minetest.delete_area(pos1, pos2)
```
This will delete all map blocks in that area, *inclusive*. This means that some

View File

@ -20,16 +20,12 @@ own.
- [Position and Velocity](#position-and-velocity)
- [Object Properties](#object-properties)
- [Entities](#entities)
- [Health and Damage](#health-and-damage)
- [Health Points (HP)](#health-points-hp)
- [Punch, Damage Groups, and Armor Groups](#punch-damage-groups-and-armor-groups)
- [Example Damage Calculation](#example-damage-calculation)
- [Attachments](#attachments)
- [Your Turn](#your-turn)
## What are Objects, Players, and Entities?
Players and Entities are both types of Objects. An object is something that can move
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.
@ -48,7 +44,7 @@ which is referred to as a Lua entity, as discussed later.
`get_pos` and `set_pos` exist to allow you to get and set an entity's position.
```lua
local object = core.get_player_by_name("bob")
local 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 })
```
@ -107,6 +103,28 @@ 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.
<!--table A is a metatable of table B, then table
B will have all the properties and methods of table A if the derived table doesn't
have any itself.-->
```lua
local MyEntity = {
initial_properties = {
@ -129,34 +147,20 @@ function MyEntity:set_message(msg)
end
```
Entity definitions differ in one very important way from Item definitions.
When an entity is emerged (ie: loaded or created), a new table is created for
that entity that *inherits* from the definition table.
<!--
This inheritance is done using a metatables.
Metatables are an important Lua feature that you will need to be aware of, as it
is an essential part of the Lua language. In layman's terms, a metatable allows
you to control how the table behaves when using certain Lua syntax. The most
common use of metatables is the ability to use another table as a prototype,
defaulting to the other table's properties and methods when they do not exist in
the current table.
Say you want to access `a.x`. If the table `a` has that member, then it will be
returned as normal. However, if the table doesn't have that member and the
metatable lists a table `b` as a prototype, then table `b` will be checked to
see if it has that member.
-->
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 " .. core.pos_to_string(object:get_pos()))
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.md](https://minetest.gitlab.io/minetest/minetest-namespace-reference/#registered-definition-tables).
A complete list can be found in [lua_api.txt]({{ page.root }}/lua_api.html#registered-entities).
```lua
function MyEntity:on_step(dtime)
@ -164,9 +168,9 @@ function MyEntity:on_step(dtime)
local pos_down = vector.subtract(pos, vector.new(0, 1, 0))
local delta
if core.get_node(pos_down).name == "air" then
if minetest.get_node(pos_down).name == "air" then
delta = vector.new(0, -1, 0)
elseif core.get_node(pos).name == "air" then
elseif minetest.get_node(pos).name == "air" then
delta = vector.new(0, 0, 1)
else
delta = vector.new(0, 1, 0)
@ -178,7 +182,7 @@ function MyEntity:on_step(dtime)
end
function MyEntity:on_punch(hitter)
core.chat_send_player(hitter:get_player_name(), self.message)
minetest.chat_send_player(hitter:get_player_name(), self.message)
end
```
@ -192,14 +196,14 @@ needs to stored.
```lua
function MyEntity:get_staticdata()
return core.write_json({
return minetest.write_json({
message = self.message,
})
end
function MyEntity:on_activate(staticdata, dtime_s)
if staticdata ~= "" and staticdata ~= nil then
local data = core.parse_json(staticdata) or {}
local data = minetest.parse_json(staticdata) or {}
self:set_message(data.message)
end
end
@ -217,14 +221,14 @@ This means that staticdata could be empty.
Finally, you need to register the type table using the aptly named `register_entity`.
```lua
core.register_entity("mymod:entity", MyEntity)
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 = core.add_entity(pos, "mymod:entity", nil)
local obj = minetest.add_entity(pos, "mymod:entity", nil)
```
The third parameter is the initial staticdata.
@ -239,90 +243,6 @@ use a [chat command](../players/chat.html) to spawn entities:
/spawnentity mymod:entity
## Health and Damage
### Health Points (HP)
Each object has a Health Points (HP) number, which represents the current health.
Players have a maximum hp set using the `hp_max` object property.
An object will die if its hp reaches 0.
```lua
local hp = object:get_hp()
object:set_hp(hp + 3)
```
### Punch, Damage Groups, and Armor Groups
Damage is the reduction of an object's HP. An object can *punch* another object to
inflict damage. A punch isn't necessarily an actual punch - it can be an
explosion, a sword slash, or something else.
The total damage is calculated by multiplying the punch's damage groups with the
target's vulnerabilities. This is then limited depending on how recent the last
punch was. We will go over an example of this calculation in a bit.
Just like [node dig groups](../items/nodes_items_crafting.html#tools-capabilities-and-dig-types),
these groups can take any name and do not need to be registered. However, it's
common to use the same group names as with node digging.
How vulnerable an object is to particular types of damage depends on its
`armor_groups`. Despite its misleading name, `armor_groups` specify the
percentage damage taken from particular damage groups, not the resistance. If a
damage group is not listed in an object's armor groups, that object is
completely invulnerable to it.
```lua
target:set_armor_groups({
fleshy = 90,
crumbly = 50,
})
```
In the above example, the object will take 90% of `fleshy` damage and 50% of
`crumbly` damage.
When a player punches an object, the damage groups come from the item they are
currently wielding. In other cases, mods decide which damage groups are used.
### Example Damage Calculation
Let's punch the `target` object:
```lua
local tool_capabilities = {
full_punch_interval = 0.8,
damage_groups = { fleshy = 5, choppy = 10 },
-- This is only used for digging nodes, but is still required
max_drop_level=1,
groupcaps={
fleshy={times={[1]=2.5, [2]=1.20, [3]=0.35}, uses=30, maxlevel=2},
},
}
local time_since_last_punch = tool_capabilities.full_punch_interval
target:punch(object, time_since_last_punch, tool_capabilities)
```
Now, let's work out what the damage will be. The punch's damage groups are
`fleshy=5` and `choppy=10`, and `target` will take 90% damage from fleshy and 0%
from choppy.
First, we multiply the damage groups by the vulnerability and sum the result.
We then multiply by a number between 0 or 1 depending on the `time_since_last_punch`.
```lua
= (5*90/100 + 10*0/100) * limit(time_since_last_punch / full_punch_interval, 0, 1)
= (5*90/100 + 10*0/100) * 1
= 4.5
```
As HP is an integer, the damage is rounded to 5 points.
## Attachments
Attached objects will move when the parent - the object they are attached to -
@ -333,7 +253,7 @@ An object can have an unlimited number of children, but at most one parent.
child:set_attach(parent, bone, position, rotation)
```
An object's `get_pos()` will always return the global position of the object, no
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.

View File

@ -53,7 +53,7 @@ The data itself, such as a node's type or an stack's count, is not metadata.
If you know the position of a node, you can retrieve its metadata:
```lua
local meta = core.get_meta({ x = 1, y = 2, z = 3 })
local meta = minetest.get_meta({ x = 1, y = 2, z = 3 })
```
Player and ItemStack metadata are obtained using `get_meta()`:
@ -99,7 +99,7 @@ This is useful when showing the ownership or status of a node.
`description` is used in ItemStack Metadata to override the description when
hovering over the stack in an inventory.
You can use colours by encoding them with `core.colorize()`.
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.
@ -111,20 +111,20 @@ Minetest offers two formats for doing this: Lua and JSON.
The Lua method tends to be a lot faster and matches the format Lua
uses for tables, while JSON is a more standard format, is better
structured, and is well suited for when you need to exchange information
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", core.serialize(data))
meta:set_string("foo", minetest.serialize(data))
data = core.deserialize(meta:get_string("foo"))
data = minetest.deserialize(minetest:get_string("foo"))
```
### Private Metadata
By default, all node metadata is sent to the client.
You can mark keys as private to prevent this.
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")
@ -149,7 +149,7 @@ Mod storage is per-mod, and can only be obtained during load time in order to
know which mod is requesting it.
```lua
local storage = core.get_mod_storage()
local storage = minetest.get_mod_storage()
```
You can now manipulate the storage just like metadata:
@ -169,10 +169,10 @@ it is used.
local backend
if use_database then
backend =
dofile(core.get_modpath("mymod") .. "/backend_sqlite.lua")
dofile(minetest.get_modpath("mymod") .. "/backend_sqlite.lua")
else
backend =
dofile(core.get_modpath("mymod") .. "/backend_storage.lua")
dofile(minetest.get_modpath("mymod") .. "/backend_storage.lua")
end
backend.get_foo("a")
@ -182,15 +182,15 @@ backend.set_foo("a", { score = 3 })
The backend_storage.lua file should include a mod storage implementation:
```lua
local storage = core.get_mod_storage()
local storage = minetest.get_mod_storage()
local backend = {}
function backend.set_foo(key, value)
storage:set_string(key, core.serialize(value))
storage:set_string(key, minetest.serialize(value))
end
function backend.get_foo(key)
return core.deserialize(storage:get_string(key))
function backend.get_foo(key, value)
return minetest.deserialize(storage:get_string(key))
end
return backend
@ -207,7 +207,7 @@ Insecure environments will be covered in more detail in the
[Security](../quality/security.html) chapter.
```lua
local ie = core.request_insecure_environment()
local ie = minetest.request_insecure_environment()
assert(ie, "Please add mymod to secure.trusted_mods in the settings")
local _sql = ie.require("lsqlite3")
@ -244,4 +244,4 @@ They're well suited for large data sets.
## Your Turn
* Make a node which disappears after it has been punched five times.
(Use `on_punch` in the node definition and `core.set_node`.)
(Use `on_punch` in the node definition and `minetest.set_node`.)

View File

@ -37,25 +37,35 @@ Node timers are directly tied to a single node.
You can manage node timers by obtaining a NodeTimerRef object.
```lua
local timer = core.get_node_timer(pos)
local timer = minetest.get_node_timer(pos)
timer:start(10.5) -- in seconds
```
When a node timer is up, the `on_timer` method in the node's definition table will
be called. The method only takes a single parameter, the position of the node:
You can also check the status or stop the timer:
```lua
core.register_node("autodoors:door_open", {
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)
core.set_node(pos, { name = "autodoors:door" })
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.
It's also possible to use `get_node_timer(pos)` inside of `on_timer`, just make
sure you return false to avoid conflict.
You may have noticed a limitation with timers: for optimisation reasons, it's
only possible to have one type of timer per node type, and only one timer running per node.
@ -68,23 +78,23 @@ has a chance to appear near water.
```lua
core.register_node("aliens:grass", {
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 = core.item_eat(20)
on_use = minetest.item_eat(20)
})
core.register_abm({
minetest.register_abm({
nodenames = {"default:dirt_with_grass"},
neighbors = {"default:water_source", "default:water_flowing"},
interval = 10.0, -- Run every 10 seconds
chance = 50, -- One node has a chance of 1 in 50 to get selected
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}
core.set_node(pos, {name = "aliens:grass"})
minetest.set_node(pos, {name = "aliens:grass"})
end
})
```
@ -93,7 +103,7 @@ This ABM runs every ten seconds, and for each matching node, there is
a 1 in 50 chance of it running.
If the ABM runs on a node, an alien grass node is placed above it.
Please be warned, this will delete any node previously located in that position.
To prevent this you should include a check using core.get_node to make sure there is space for the grass.
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

View File

@ -8,19 +8,17 @@ 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
message: <p>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.
bridge allows players to run commands without joining the game.</p>
So make sure that you don't assume that the player is online.
You can check by seeing if `core.get_player_by_name` returns a player.
<p>So make sure that you don't assume that the player is online.
You can check by seeing if <pre>minetest.get_player_by_name</pre> returns a player.</p>
cb_cmdsprivs:
level: warning
title: Privileges and Chat Commands
message: |
The shout privilege isn't needed for a player to trigger this callback.
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 /.
@ -31,23 +29,18 @@ cb_cmdsprivs:
Mods can interact with player chat, including
sending messages, intercepting messages, and registering chat commands.
- [Sending Messages](#sending-messages)
- [To All Players](#to-all-players)
- [To Specific Players](#to-specific-players)
- [Sending Messages to All Players](#sending-messages-to-all-players)
- [Sending Messages to Specific Players](#sending-messages-to-specific-players)
- [Chat Commands](#chat-commands)
- [Accepting Multiple Arguments](#accepting-multiple-arguments)
- [Using string.split](#using-stringsplit)
- [Using Lua patterns](#using-lua-patterns)
- [Complex Subcommands](#complex-subcommands)
- [Intercepting Messages](#intercepting-messages)
## Sending Messages
## Sending Messages to All Players
### To All Players
To send a message to every player in the game, call the `chat_send_all` function.
To send a message to every player in the game, call the chat_send_all function.
```lua
core.chat_send_all("This is a chat message to all players")
minetest.chat_send_all("This is a chat message to all players")
```
Here is an example of how this appears in-game:
@ -58,12 +51,12 @@ Here is an example of how this appears in-game:
The message appears on a separate line to distinguish it from in-game player chat.
### To Specific Players
## Sending Messages to Specific Players
To send a message to a specific player, call the `chat_send_player` function:
To send a message to a specific player, call the chat_send_player function:
```lua
core.chat_send_player("player1", "This is a chat message for player1")
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
@ -74,7 +67,7 @@ only visible to the named player, in this case, player1.
To register a chat command, for example `/foo`, use `register_chatcommand`:
```lua
core.register_chatcommand("foo", {
minetest.register_chatcommand("foo", {
privs = {
interact = true,
},
@ -87,57 +80,26 @@ core.register_chatcommand("foo", {
In the above snippet, `interact` is listed as a required
[privilege](privileges.html) meaning that only players with the `interact` privilege can run the command.
`param` is a string containing everything a player writes after the chatcommand
name. For example, if a user types `/grantme one,two,three` then `param` will be
`one,two,three`.
Chat commands can return up to two values,
the first being a Boolean indicating success, and the second being a
message to send to the user.
{% include notice.html notice=page.cmd_online %}
### Accepting Multiple Arguments
## Complex Subcommands
<a name="complex-subcommands"></a>
It is often required to make complex chat commands, such as:
`param` gives you all the arguments to a chat command in a single string. It's
common for chat commands to need to extract multiple arguments. There are two
ways of doing this, either using Minetest's string split or Lua patterns.
* `/msg <to> <message>`
* `/team join <teamname>`
* `/team leave <teamname>`
* `/team list`
#### Using string.split
A string can be split up into words using `string.split(" ")`:
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 parts = param:split(" ")
local cmd = parts[1]
if cmd == "join" then
local team_name = parts[2]
team.join(name, team_name)
return true, "Joined team!"
elseif cmd == "max_users" then
local team_name = parts[2]
local max_users = tonumber(parts[3])
if team_name and max_users then
return true, "Set max users of team " .. team_name .. " to " .. max_users
else
return false, "Usage: /team max_users <team_name> <number>"
end
else
return false, "Command needed"
end
```
#### Using Lua patterns
[Lua patterns](https://www.lua.org/pil/20.2.html) are a way of extracting stuff
from text using rules. They're best suited for when there are arguments that can
contain spaces or more control is needed on how parameters are captured.
```lua
local to, msg = param:match("^([%a%d_-]+) (.+)$")
local to, msg = string.match(param, "^([%a%d_-]+) (*+)$")
```
The above code implements `/msg <to> <message>`. Let's go through left to right:
@ -147,9 +109,9 @@ The above code implements `/msg <to> <message>`. Let's go through left to right:
returned from string.match.
* `[]` means accept characters in this list.
* `%a` means accept any letter and `%d` means accept any digit.
* `[%a%d_-]` means accept any letter or digit or `_` or `-`.
* `[%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 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/-/_),
@ -161,12 +123,19 @@ 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).
<p class="book_hide">
There is also a library written by the author of this book which can be used
to make complex chat commands without patterns called
<a href="chat_complex.html">Chat Command Builder</a>.
</p>
## Intercepting Messages
To intercept a message, use register_on_chat_message:
```lua
core.register_on_chat_message(function(name, message)
minetest.register_on_chat_message(function(name, message)
print(name .. " said " .. message)
return false
end)
@ -182,10 +151,10 @@ You should make sure you take into account that it may be a chat command,
or the user may not have `shout`.
```lua
core.register_on_chat_message(function(name, message)
minetest.register_on_chat_message(function(name, message)
if message:sub(1, 1) == "/" then
print(name .. " ran chat command")
elseif core.check_player_privs(name, { shout = true }) then
elseif minetest.check_player_privs(name, { shout = true }) then
print(name .. " said " .. message)
else
print(name .. " tried to say " .. message ..

183
_en/players/chat_complex.md Normal file
View File

@ -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 <!-- omit in toc -->
This chapter will show you how to make complex chat commands with ChatCmdBuilder,
such as `/msg <name> <message>`, `/team join <teamname>` or `/team leave <teamname>`.
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 <username>` - kill user
* `/admin move <username> to <pos>` - teleport user
* `/admin log <username>` - show report log
* `/admin log <username> <message>` - 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
}
})
```

View File

@ -56,8 +56,6 @@ called real coordinates which aims to rectify this by introducing a consistent
coordinate system. The use of real coordinates is highly recommended, and so
this chapter will use them exclusively.
Using a formspec_version of 2 or above will enable real coordinates.
## Anatomy of a Formspec
### Elements
@ -77,7 +75,7 @@ on multiple lines, like so:
Elements are items such as text boxes or buttons, or can be metadata such
as size or background. You should refer to
[lua_api.md](https://minetest.gitlab.io/minetest/formspec/)
[lua_api.txt](../../lua_api.html#elements)
for a list of all possible elements.
@ -89,18 +87,18 @@ game-wide theme should be applied.
The elements in the header must be defined in a specific order, otherwise you
will see an error. This order is given in the above paragraph, and, as always,
documented in the Lua API reference.
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:
formspec_version[4]
formspec_version[3]
size[2,2]
Notice how we explicitly defined the formspec language version.
Without this, the legacy system will instead be used instead - which will
prevent the use of consistent element positioning and other new features.
prevent the use of consistent element positioning and other new feautures.
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
@ -108,8 +106,9 @@ the center (`0.5,0.5`). The anchor sets where on the formspec the position is,
allowing you to line the formspec up with the edge of the screen. The formspec
can be placed to the left of the screen like so:
formspec_version[4]
formspec_version[3]
size[2,2]
real_coordinates[true]
position[0,0.5]
anchor[0,0.5]
@ -144,9 +143,9 @@ function guessing.get_formspec(name)
local text = "I'm thinking of a number... Make a guess!"
local formspec = {
"formspec_version[4]",
"formspec_version[3]",
"size[6,3.476]",
"label[0.375,0.5;", core.formspec_escape(text), "]",
"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]"
}
@ -166,10 +165,10 @@ is using `show_formspec`:
```lua
function guessing.show_to(name)
core.show_formspec(name, "guessing:game", guessing.get_formspec(name))
minetest.show_formspec(name, "guessing:game", guessing.get_formspec(name))
end
core.register_chatcommand("game", {
minetest.register_chatcommand("game", {
func = function(name)
guessing.show_to(name)
end,
@ -206,28 +205,28 @@ The method for this is called formspec field submission, and for `show_formspec`
submission is received using a global callback:
```lua
core.register_on_player_receive_fields(function(player, formname, fields)
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()
core.chat_send_all(pname .. " guessed " .. fields.number)
minetest.chat_send_all(pname .. " guessed " .. fields.number)
end
end)
```
The function given in `core.register_on_player_receive_fields` is called
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, depending on the event. Some elements will only be submitted if they caused
the event, such as buttons, and some elements will always appear in submissions,
such as fields.
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 %}
@ -239,7 +238,7 @@ the formspec based on guesses. The way to do this is using a concept called
### Contexts
In many cases you want core.show_formspec to give information
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.
@ -255,7 +254,7 @@ local function get_context(name)
return context
end
core.register_on_leaveplayer(function(player)
minetest.register_on_leaveplayer(function(player)
_contexts[player:get_player_name()] = nil
end)
```
@ -269,7 +268,7 @@ function guessing.show_to(name)
context.target = context.target or math.random(1, 10)
local fs = guessing.get_formspec(name, context)
core.show_formspec(name, "guessing:game", fs)
minetest.show_formspec(name, "guessing:game", fs)
end
```
@ -318,13 +317,13 @@ There are three different ways that a formspec can be delivered to the client:
### Node Meta Formspecs
`core.show_formspec` is not the only way to show a formspec; you can also
add formspecs to a [node's metadata](../map/storage.html). For example,
`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
core.register_node("mymod:rightclick", {
minetest.register_node("mymod:rightclick", {
description = "Rightclick me!",
tiles = {"mymod_rightclick.png"},
groups = {cracky = 1},
@ -333,9 +332,9 @@ core.register_node("mymod:rightclick", {
-- The following code sets the formspec for chest.
-- Meta is a way of storing data onto a node.
local meta = core.get_meta(pos)
local meta = minetest.get_meta(pos)
meta:set_string("formspec",
"formspec_version[4]" ..
"formspec_version[3]" ..
"size[5,5]" ..
"label[1,1;This is shown on right click]" ..
"field[1,2;2,1;x;x;]")
@ -355,7 +354,7 @@ receive form input for meta formspecs, you must include an
`on_receive_fields` entry when registering the node.
This style of callback triggers when you press enter
in a field, which is impossible with `core.show_formspec`;
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.
@ -365,9 +364,9 @@ The player inventory formspec is the one shown when the player presses i.
The global callback is used to receive events from this formspec, and the
formname is `""`.
There are a number of different mods which allow multiple mods to customise the
player inventory. Minetest Game uses
[SFINV](https://github.com/rubenwardy/sfinv/blob/master/Tutorial.md).
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

View File

@ -89,7 +89,7 @@ to the right of the window, but to resize without breaking.
You can create a HUD element once you have a copy of the player object:
```lua
local player = core.get_player_by_name("username")
local player = minetest.get_player_by_name("username")
local idx = player:hud_add({
hud_elem_type = "text",
position = {x = 0.5, y = 0.5},
@ -281,9 +281,9 @@ function score.update_hud(player)
end
end
core.register_on_joinplayer(score.update_hud)
minetest.register_on_joinplayer(score.update_hud)
core.register_on_leaveplayer(function(player)
minetest.register_on_leaveplayer(function(player)
saved_huds[player:get_player_name()] = nil
end)
```
@ -291,4 +291,4 @@ end)
## Other Elements
Read [lua_api.md](https://minetest.gitlab.io/minetest/hud/) for a complete list of HUD elements.
Read [lua_api.txt]({{ page.root }}/lua_api.html#hud-element-types) for a complete list of HUD elements.

View File

@ -28,9 +28,9 @@ Here is an example of how to add an antigravity command, which
puts the caller in low G:
```lua
core.register_chatcommand("antigravity", {
minetest.register_chatcommand("antigravity", {
func = function(name, param)
local player = core.get_player_by_name(name)
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)
@ -42,7 +42,7 @@ core.register_chatcommand("antigravity", {
## Available Overrides
`player:set_physics_override()` is given a table of overrides.\\
According to [lua_api.md](https://minetest.gitlab.io/minetest/class-reference/#player-only-no-op-for-other-objects),
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)

View File

@ -48,7 +48,7 @@ Privileges are **not** for indicating class or status.
Use `register_privilege` to declare a new privilege:
```lua
core.register_privilege("vote", {
minetest.register_privilege("vote", {
description = "Can vote on issues",
give_to_singleplayer = true
})
@ -62,7 +62,7 @@ actually needed in the above definition.
To quickly check whether a player has all the required privileges:
```lua
local has, missing = core.check_player_privs(player_or_name, {
local has, missing = minetest.check_player_privs(player_or_name, {
interact = true,
vote = true })
```
@ -72,7 +72,7 @@ If `has` is false, then `missing` will contain a key-value table
of the missing privileges.
```lua
local has, missing = core.check_player_privs(name, {
local has, missing = minetest.check_player_privs(name, {
interact = true,
vote = true })
@ -87,7 +87,7 @@ If you don't need to check the missing privileges, you can put
`check_player_privs` directly into the if statement.
```lua
if not core.check_player_privs(name, { interact=true }) then
if not minetest.check_player_privs(name, { interact=true }) then
return false, "You need interact for this!"
end
```
@ -99,11 +99,11 @@ being online.
```lua
local privs = core.get_player_privs(name)
local privs = minetest.get_player_privs(name)
print(dump(privs))
privs.vote = true
core.set_player_privs(name, privs)
minetest.set_player_privs(name, privs)
```
Privileges are always specified as a key-value table with the key being

View File

@ -1,5 +1,242 @@
---
sitemap: false
title: "SFINV: Inventory Formspec"
layout: default
root: ../..
idx: 4.7
redirect_from: /en/chapters/sfinv.html
redirect_to: "https://github.com/rubenwardy/sfinv/blob/master/Tutorial.md"
---
## Introduction <!-- omit in toc -->
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.
<figure>
<img src="{{ page.root }}//static/sfinv_admin_fs.png" alt="Player Admin Page">
</figure>
## 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
})
```

View File

@ -100,7 +100,7 @@ what is listening to something.
In the next chapter, we will discuss how to automatically test your
code and one of the problems we will have is how to separate your logic
(calculations, what should be done) from API calls (`core.*`, other mods)
(calculations, what should be done) from API calls (`minetest.*`, other mods)
as much as possible.
One way to do this is to think about:
@ -173,14 +173,14 @@ function land.show_create_formspec(name)
]]
end
core.register_chatcommand("/land", {
minetest.register_chatcommand("/land", {
privs = { land = true },
func = function(name)
land.handle_creation_request(name)
end,
})
core.register_on_player_receive_fields(function(player,
minetest.register_on_player_receive_fields(function(player,
formname, fields)
land.handle_create_submit(player:get_player_name(),
fields.area_name)
@ -227,7 +227,7 @@ this isn't the real world. A good compromise is to reduce the mod into two
parts:
* **API** - This was the model and controller above. There should be no uses of
`core.` here.
`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.

View File

@ -10,27 +10,54 @@ redirect_from: /en/chapters/common_mistakes.html
This chapter details common mistakes, and how to avoid them.
- [Be Careful When Storing ObjectRefs (ie: players or entities)](#be-careful-when-storing-objectrefs-ie-players-or-entities)
- [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)
## Be Careful When Storing ObjectRefs (ie: players or entities)
## Never Store ObjectRefs (ie: players or entities)
An ObjectRef is invalidated when the player or entity it represents leaves
the game. This may happen when the player goes offline, or the entity is unloaded
or removed.
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.
The methods of ObjectRefs will always return nil when invalid, since Minetest 5.2.
Any call will essentially be ignored.
You should avoid storing ObjectRefs where possible. If you do to store an
ObjectRef, you should make sure you check it before use, like so:
For example, don't do this:
```lua
-- This only works in Minetest 5.2+
if obj:get_pos() then
-- is valid!
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
@ -43,11 +70,11 @@ give themselves moderator privileges:
```lua
local function show_formspec(name)
if not core.check_player_privs(name, { privs = true }) then
if not minetest.check_player_privs(name, { privs = true }) then
return false
end
core.show_formspec(name, "modman:modman", [[
minetest.show_formspec(name, "modman:modman", [[
size[3,2]
field[0,0;3,1;target;Name;]
button_exit[0,1;3,1;sub;Promote]
@ -55,14 +82,14 @@ local function show_formspec(name)
return true
})
core.register_on_player_receive_fields(function(player,
minetest.register_on_player_receive_fields(function(player,
formname, fields)
-- BAD! Missing privilege check here!
local privs = core.get_player_privs(fields.target)
local privs = minetest.get_player_privs(fields.target)
privs.kick = true
privs.ban = true
core.set_player_privs(fields.target, privs)
minetest.set_player_privs(fields.target, privs)
return true
end)
```
@ -70,9 +97,9 @@ end)
Add a privilege check to solve this:
```lua
core.register_on_player_receive_fields(function(player,
minetest.register_on_player_receive_fields(function(player,
formname, fields)
if not core.check_player_privs(name, { privs = true }) then
if not minetest.check_player_privs(name, { privs = true }) then
return false
end
@ -111,7 +138,7 @@ are given will change it for the caller too, and any subsequent callbacks. Howev
it will only be saved in the engine if the callback caller sets it.
```lua
core.register_on_item_eat(function(hp_change, replace_with_item,
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
@ -125,7 +152,7 @@ but if a callback does cancel this, then the update may be lost.
It's better to do this instead:
```lua
core.register_on_item_eat(function(hp_change, replace_with_item,
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(),

View File

@ -20,6 +20,7 @@ editor to provide alerts to any mistakes.
- [Configuring LuaCheck](#configuring-luacheck)
- [Troubleshooting](#troubleshooting)
- [Using with editor](#using-with-editor)
- [Checking Commits with Travis](#checking-commits-with-travis)
## Installing LuaCheck
@ -101,7 +102,51 @@ It is highly recommended that you find and install a plugin for your editor of c
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.

View File

@ -12,13 +12,15 @@ After you've read this book, take a look at the following.
### Minetest Modding
* Minetest's Lua API Reference - [multiple page version](https://minetest.gitlab.io/minetest/class-reference/#player-only-no-op-for-other-objects) |
[single page version](https://github.com/minetest/minetest/blob/master/doc/lua_api.md).
* 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

View File

@ -11,20 +11,21 @@ redirect_from: /en/chapters/releasing.html
Releasing, or publishing, a mod allows other people to make use of it. Once a mod has been
released it might be used in singleplayer games or on servers, including public servers.
- [Choosing a License](#choosing-a-license)
- [License Choices](#license-choices)
- [LGPL and CC-BY-SA](#lgpl-and-cc-by-sa)
- [CC0](#cc0)
- [MIT](#mit)
- [Packaging](#packaging)
- [README.txt](#readmetxt)
- [mod.conf / game.conf](#modconf--gameconf)
- [description.txt](#descriptiontxt)
- [screenshot.png](#screenshotpng)
- [Uploading](#uploading)
- [Version Control Systems](#version-control-systems)
- [Releasing on ContentDB](#releasing-on-contentdb)
- [Forum Attachments](#forum-attachments)
- [Forum Topic](#forum-topic)
- [Subject](#subject)
## Choosing a License
## 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
@ -42,17 +43,11 @@ able to modify it and release the modified version.)
Please note that **public domain is not a valid licence**, because the definition varies
in different countries.
It is important to note that WTFPL is
[strongly discouraged](https://content.minetest.net/help/wtfpl/) and people may
choose not to use your mod if it has this license.
### LGPL and CC-BY-SA
This is a common license combination in the Minetest community, and is what
Minetest and Minetest Game use.
You license your code under LGPL 2.1 and your art under CC-BY-SA.
This means that:
* Anyone can modify, redistribute and sell modified or unmodified versions.
@ -61,51 +56,55 @@ This means that:
### CC0
This license can be used for both code and art, and allows anyone to do what
they want with your work. This means they can modify, redistribute, sell, or
leave-out attribution.
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 code. The only restriction it places on users
of your code is that they must include the same copyright notice and license
in any copies of the code or of substantial parts of the code.
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 or game
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/game does, how to use it.
* What the mod does.
* What the license is.
* Optionally:
* where to report problems or get help.
* credits
* What dependencies there are.
* How to install the mod.
* Current version of the mod.
* Optionally, the where to report problems or get help.
### mod.conf / game.conf
### description.txt
Make sure you add a description key to explain what your mod or game does. Be
concise without being vague. It should be short because it will be displayed in
the content installer which has limited space.
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:
description = Adds soup, cakes, bakes and juices.
Adds soup, cakes, bakes and juices.
Avoid this:
description = The food mod for Minetest. (<-- BAD! It's vague)
(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 inside of Minetest as a thumbnail for the content.
The screenshot is displayed in the mod store.
## Uploading
@ -115,39 +114,54 @@ approach that works best for you, as long as it meets these requirements, and an
others which may be added by forum moderators:
* **Stable** - The hosting website should be unlikely to shut down without warning.
* **Direct link** - You should be able to click a link and download the file
* **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** - Scammy upload hosts may contain insecure adverts.
ContentDB allows you to upload zip files, and meets these criteria.
* **Virus Free** - Mods with malicious content will be removed from the forum.
### Version Control Systems
A Version Control System (VCS) is software that manages changes to software,
often making it easier to distribute and receive contributed changes.
It is recommended that you use a version control system which:
The majority of Minetest modders use Git and a website like GitHub to distribute
their code.
* Allows other developers to easily submit changes.
* Allows the code to be previewed before downloading.
* Allows users to submit bug reports.
Using git can be difficult at first. If you need help with this please see:
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.
## Releasing on ContentDB
### Forum Attachments
ContentDB is the official place to find and distribute content such as mods,
games, and texture packs. Users can find content using the website, or download
and install using the integration built into the Minetest main menu.
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).
Sign up to [ContentDB](https://content.minetest.net) and add your content.
Make sure to read the guidance given in the Help section.
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.
<figure>
<img src="{{ page.root }}/static/releasing_attachments.png" alt="Upload Attachment">
<figcaption>
Upload Attachment tab.
</figcaption>
</figure>
## Forum Topic
You can also create a forum topic to let users discuss your creation.
Mod topics should be created in ["WIP Mods"](https://forum.minetest.net/viewforum.php?f=9) (Work In Progress)
forum, and Game topics in the ["WIP Games"](https://forum.minetest.net/viewforum.php?f=50) forum.
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."
@ -156,6 +170,40 @@ 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]

View File

@ -40,7 +40,7 @@ Any users can submit almost any formspec with any values at any time.
Here's some real code found in a mod:
```lua
core.register_on_player_receive_fields(function(player,
minetest.register_on_player_receive_fields(function(player,
formname, fields)
for key, field in pairs(fields) do
local x,y,z = string.match(key,
@ -87,7 +87,7 @@ to the full Lua API.
Can you spot the vulnerability in the following?
```lua
local ie = core.request_insecure_environment()
local ie = minetest.request_insecure_environment()
ie.os.execute(("path/to/prog %d"):format(3))
```

View File

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

View File

@ -17,8 +17,9 @@ we discussed how to structure your code avoid this.
- [Your First Test](#your-first-test)
- [init.lua](#initlua)
- [api.lua](#apilua)
- [tests/api_spec.lua](#testsapi_speclua)
- [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
@ -54,7 +55,7 @@ names ending in `_spec`, and then executes them in a standalone Lua environment.
```lua
mymod = {}
dofile(core.get_modpath("mymod") .. "/api.lua")
dofile(minetest.get_modpath("mymod") .. "/api.lua")
```
@ -122,7 +123,7 @@ _G.minetest = {}
-- Define the mock function
local chat_send_all_calls = {}
function core.chat_send_all(name, message)
function minetest.chat_send_all(name, message)
table.insert(chat_send_all_calls, { name = name, message = message })
end
@ -163,6 +164,28 @@ 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

View File

@ -14,7 +14,7 @@ Questo capitolo mostra come registrare nuovi biomi, come controllarne la distrib
- [Cosa sono i biomi?](#cosa-sono-i-biomi)
- [Collocare un bioma](#collocare-un-bioma)
- [Calore e umidità](#calore-e-umidità)
- [Calore e umidità](#calore-e-umidita)
- [Visualizzare i confini usando i diagrammi di Voronoi](#visualizzare-i-confini-usando-i-diagrammi-di-voronoi)
- [Creare un diagramma di Voronoi usando Geogebra](#creare-un-diagramma-di-voronoi-usando-geogebra)
- [Registrare un bioma](#registrare-un-bioma)
@ -107,7 +107,7 @@ Oltre che farli a mano, per creare dei diagrammi di Voronoi si possono usare pro
Il seguente codice registra un semplice bioma chiamato "distesa_erbosa":
```lua
core.register_biome({
minetest.register_biome({
name = "distesa_erbosa",
node_top = "default:dirt_with_grass",
depth_top = 1,
@ -144,7 +144,7 @@ Ricordati che devi specificare il nodo che vuoi usare in quanto decorazione, i d
Per esempio:
```lua
core.register_decoration({
minetest.register_decoration({
deco_type = "simple",
place_on = {"base:dirt_with_grass"},
sidelen = 16,
@ -167,7 +167,7 @@ Le schematic sono molto simili alle decorazioni semplici, solo che piazzano più
Per esempio:
```lua
core.register_decoration({
minetest.register_decoration({
deco_type = "schematic",
place_on = {"base:desert_sand"},
sidelen = 16,
@ -175,7 +175,7 @@ core.register_decoration({
biomes = {"desert"},
y_max = 200,
y_min = 1,
schematic = core.get_modpath("plants") .. "/schematics/cactus.mts",
schematic = minetest.get_modpath("plants") .. "/schematics/cactus.mts",
flags = "place_center_x, place_center_z",
rotation = "random",
})
@ -195,7 +195,7 @@ I giochi disponibili dovrebbero già includere un alias del generatore mappa (*m
Gli alias del generatore mappa forniscono informazioni al generatore principale, e possono essere registrati secondo lo schema:
```lua
core.register_alias("mapgen_stone", "base:smoke_stone")
minetest.register_alias("mapgen_stone", "base:smoke_stone")
```
Almeno almeno dovresti registrare:

View File

@ -10,13 +10,13 @@ redirect_from:
mapgen_object:
level: warning
title: LVM e generatore mappa
message: Non usare `core.get_voxel_manip()` con il generatore mappa, in quanto può causare glitch.
Usa invece `core.get_mapgen_object("voxelmanip")`.
message: Non usare `minetest.get_voxel_manip()` con il generatore mappa, in quanto può causare glitch.
Usa invece `minetest.get_mapgen_object("voxelmanip")`.
---
## Introduzione <!-- omit in toc -->
Le funzioni introdotte nel capitolo [Mappa: operazioni base](../map/environment.html) sono comode e facili da usare, ma per le grandi aree non sono efficienti.
Le funzioni introdotte nel capitolo [Mappa: operazioni base](environment.html) sono comode e facili da usare, ma per le grandi aree non sono efficienti.
Ogni volta che `set_node` e `get_node` vengono chiamati da una mod, la mod deve comunicare con il motore di gioco.
Ciò risulta in una costante copia individuale dei singoli nodi, che è lenta e abbasserà notevolmente le performance del gioco.
Usare un Manipolatore di Voxel Lua (*Lua Voxel Manipulator*, da qui LVM) può essere un'alternativa migliore.
@ -39,7 +39,7 @@ Si possono caricare solamente aree cubiche negli LVM, quindi devi capire da te q
Fatto ciò, puoi creare l'LVM:
```lua
local vm = core.get_voxel_manip()
local vm = minetest.get_voxel_manip()
local emin, emax = vm:read_from_map(pos1, pos2)
```
@ -82,7 +82,7 @@ Per scoprire qual è l'ID assegnato a un tipo di nodo, si usa `get_content_id()`
Per esempio:
```lua
local c_pietra = core.get_content_id("default:stone")
local c_pietra = minetest.get_content_id("default:stone")
```
Si può ora controllare se un nodo è effettivamente di pietra:
@ -94,7 +94,7 @@ if data[idx] == c_pietra then
end
```
Gli ID di contenuto di un nodo potrebbero cambiare durante la fase di caricamento, quindi è consigliato non tentare di ottenerli durante tale fase.
Si consiglia di ottenere e salvare (in una variabile locale) gli ID di contenuto al caricare della mod in quanto questi non possono cambiare.
Le coordinate dei nodi nell'array di un LVM sono salvate in ordine inverso (`z, y, x`), quindi se le si vuole iterare, si tenga presente che si inizierà dalla Z:
@ -143,16 +143,18 @@ vm:write_to_map(true)
Per la luce e param2, invece si usano `set_light_data()` e `set_param2_data()`.
`write_to_map()` richiede un booleano che è `true` se si vuole che venga calcolata anche la luce.
Se si passa `false` invece, ci sarà bisogno di ricalcolarla in un secondo tempo usando `core.fix_light`.
Se si passa `false` invece, ci sarà bisogno di ricalcolarla in un secondo tempo usando `minetest.fix_light`.
## Esempio
```lua
-- ottiene l'ID di contenuto al caricare della mod, salvandolo in variabili locali
local c_terra = minetest.get_content_id("default:dirt")
local c_erba = minetest.get_content_id("default:dirt_with_grass")
local function da_erba_a_terra(pos1, pos2)
local c_terra = core.get_content_id("default:dirt")
local c_erba = core.get_content_id("default:dirt_with_grass")
-- legge i dati nella LVM
local vm = core.get_voxel_manip()
local vm = minetest.get_voxel_manip()
local emin, emax = vm:read_from_map(pos1, pos2)
local a = VoxelArea:new{
MinEdge = emin,

View File

@ -11,64 +11,80 @@ redirect_from:
## Introduzione <!-- omit in toc -->
Capire la struttura base della cartella di una mod è un requisito essenziale per creare qualsivoglia contenuto.
Capire la struttura base della cartella di una mod è un requisito essenziale per creare mod.
- [Cosa sono i giochi e le mod?](#cosa-sono-i-giochi-e-le-mod)
- [Dove vengono salvate le mod?](#dove-vengono-salvate-le-mod)
- [Cartella mod](#cartella-mod)
- [mod.conf](#modconf)
- [Dipendenze](#dipendenze)
- [mod.conf](#modconf)
- [depends.txt](#dependstxt)
- [Pacchetti mod](#pacchetti-mod-mod-pack)
- [Esempio](#esempio)
- [Cartella mod](#cartella-mod-1)
- [depends.txt](#dependstxt-1)
- [init.lua](#initlua)
- [mod.conf](#modconf-1)
## Cosa sono i giochi e le mod?
Il punto forte di Minetest è l'abilità di sviluppare facilmente giochi senza il bisogno di crearsi da zero il motore grafico, gli algoritmi voxel o tutta la parte di rete.
Il punto forte di Minetest è l'abilità di sviluppare facilmente giochi senza il bisogno
di crearti da zero il motore grafico, gli algoritmi voxel o tutta la parte network.
In Minetest, un gioco è un insieme di moduli che lavorano fianco a fianco per fornire il contenuto e il comportamento di un gioco.
Un modulo, solitamente conosciuto come "mod", è una collezione di script e risorse, e in teoria ne potrebbe bastare uno per creare un intero gioco.
Tuttavia, questo non accade spesso, perché ridurrebbe la comodità di poter sostituire o calibrare alcune parti in maniera indipendente dalle altre.
In Minetest, un gioco è una collezione di moduli che lavorano insieme per fornire il contenuto
e il comportamento di un gioco.
Un modulo, solitamente conosciuto come "mod" (femminile), è una collezione di script e risorse.
È possibile creare un gioco usando semplicemente una mod, ma questo non accade spesso perché
riduce la comodità di poter sostituire o calibrare alcune parti del gioco in maniera indipendente
dalle altre.
È poi anche possibile distribuire singolarmente le varie mod, che diventano mod nel senso più tradizionale del termine: modifiche, per calibrano o espandere le proprietà di un gioco.
È anche possibile distribuire le mod al di fuori di un gioco, nel qual caso sono sempre mod
nel senso più tradizionale del termine: modifiche. Queste mod calibrano o espandono le proprietà
di un gioco.
Indipendentemente da come le si voglia usare (specifiche per un gioco o come estensioni generiche) usano la stessa API.
Sia le mod presenti in un gioco che quelle a sé stanti usano la stessa API.
Questo libro coprirà le parti principali dell'API di Minetest, ed è pensato sia per chi sviluppa il motore di gioco (Minetest, in C++) che per chi crea mod.
Questo libro coprirà le parti principali della API di Minetest,
ed è applicabile sia per gli sviluppatori che per i creatori di mod.
## Dove vengono salvate le mod?
<a name="mod-locations"></a>
Ogni mod ha la sua cartella personale dove viene messo il suo codice in Lua, le sue texture, i suoi modelli e i suoi file audio.
Minetest esegue controlli in più posti e questi posti sono generalmente chiamati *percorsi di caricamento mod* (*mod load paths*).
Ogni mod ha la sua cartella personale dove viene messo il suo codice in Lua, le sue texture,
i suoi modelli e i suoi file audio. Minetest fa un check in più punti per le mod. Questi punti
sono generalmente chiamati *percorsi di caricamento mod* (in inglese *mod load paths*).
Per un dato mondo/salvataggio, vengono controllati tre percorsi.
Per un dato mondo/salvataggio, vengono controllati tre punti.
Essi sono, in ordine:
1. Mod di gioco. Queste sono le mod che compongono il gioco che il mondo sta eseguendo.
Es: `minetest/games/minetest_game/mods/`, `/usr/share/minetest/games/minetest/`
2. Mod globali. Il luogo dove le mod vengono quasi sempre installate. Se si è in dubbio, le si metta qui.
2. Mod globali. Il luogo dove le mod vengono quasi sempre installate. Se si è in dubbio,
le si metta qui.
Es: `minetest/mods/`
3. Mod del mondo. Il luogo dove mettere le mod che sono specifiche di un dato mondo.
Es: `minetest/worlds/world/worldmods/`
Minetest controllerà questi percorsi nell'ordine sopraelencato.
In caso dovesse incontrare una mod con lo stesso nome di una incontrata in precedenza, l'ultima verrebbe caricata al posto della prima.
Ciò significa, per esempio, che è possibile sovrascriverne una di gioco se ve n'è una omonima nelle globali.
Minetest controllerà questi punti nell'ordine sopraelencato. Se incontra una mod con lo
stesso nome di una incontrata in precedenza, l'ultima verrà caricata al posto della prima.
Questo significa che si può sovrascrivere le mod di gioco piazzando una mod con lo stesso
nome nella cartella delle mod globali.
La posizione di ogni percorso dipende da quale sistema operativo si sta usando, e da come è stato installato Minetest.
La posizione di ogni percorso di caricamento mod dipende da quale sistema operativo si sta
usando, e come è stato installato Minetest.
* **Windows:**
* Per le versioni portatili, per esempio da un file .zip, vai dove hai estratto lo zip e cerca le cartelle `games`, `mods` e `worlds`.
* Per le versioni installate, per esempio da un setup.exe, guarda in C:\\\\Minetest o C:\\\\Games\\Minetest.
* Per le build portatili, per esempio da un file .zip, vai dove hai estratto lo zip e
cerca le cartelle `games`, `mods` e `worlds`.
* Per le build installate, per esempio da un setup.exe,
guarda in C:\\\\Minetest o C:\\\\Games\\Minetest.
* **GNU/Linux:**
* Per le installazioni di sistema, guarda in `~/.minetest`.
Attenzione che `~` equivale alla cartella home dell'utente, e che i file e le cartelle che iniziano con un punto (`.`) sono nascosti di default.
Attenzione che `~` equivale alla cartella home dell'utente, e che i file e le cartelle
che iniziano con un punto (`.`) sono nascosti di default.
* Per le installazioni portatili, guarda nella cartella di build.
* Per installazioni Flatpak, guarda in `~/.var/app/net.minetest.Minetest/.minetest/mods/`.
* **MacOS**
@ -79,13 +95,15 @@ La posizione di ogni percorso dipende da quale sistema operativo si sta usando,
![Find the mod's directory]({{ page.root }}/static/folder_modfolder.jpg)
Il *nome mod* è usato per riferirsi a una mod e ognuna di esse dovrebbe averne uno unico.
Questi possono includere lettere, numeri e trattini bassi, e un buon nome dovrebbe descrivere brevemente cosa fa la mod (è anche consigliato rinominare la cartella della mod con il nome di quest'ultima).
Per scoprire se un nome è disponibile, prova a cercarlo su
Il *nome mod* è usato per riferirsi a una mod. Ogni mod dovrebbe avere un nome unico.
I nomi mod possono includere lettere, numeri e trattini bassi. Un buon nome dovrebbe
descrivere cosa fa la mod, e la cartella che contiene i componenti di una mod deve avere
lo stesso nome del nome mod.
Per scoprire se un nome mod è disponibile, prova a cercarlo su
[content.minetest.net](https://content.minetest.net).
lamiamod
mymod
├── init.lua (necessario) - Viene eseguito al lancio del gioco.
├── mod.conf (consigliato) - Contiene la descrizione e le dipendneze.
├── textures (opzionale)
@ -94,37 +112,53 @@ Per scoprire se un nome è disponibile, prova a cercarlo su
│   └── ... qualsiasi file audio
└── ... qualsiasi altro tipo di file o cartelle
Solo il file init.lua è necessario in una mod per eseguirla quando si avvia un gioco;
tuttavia è consigliato anche mod.conf, e altri componenti potrebbero essere richiesti a
seconda di quello che si vuole fare.
Solo il file init.lua è necessario in una mod per eseguirla al lanciare un gioco;
tuttavia, mod.conf è consigliato e altri componenti potrebbero essere richiesti a
seconda della funzione della mod.
## mod.conf
## Dipendenze
Questo file è utilizzato per i metadati della mod, che includono il suo nome, la descrizione e altre informazioni.
Per esempio:
name = lamiamod
description = Aggiunge X, Y, e Z
depends = mod1, mod2
### Dipendenze
Una dipendenza è quando (all'avvio) una o più mod vengono richieste da un'altra mod.
I motivi sono vari: potrebbe per esempio aver bisogno di parti del loro codice, degli oggetti, o in generale di risorse che queste forniscono.
Una dipendenza avviene quando una mod richiede che un'altra mod sia avviata prima di essa.
Una mod potrebbe infatti richiedere il codice di quest'ultima, i suoi oggetti o altre risorse.
Ci sono due tipi di dipendenze: forti e opzionali.
Entrambe richiedono che la mod richiesta venga caricata prima, con la differenza che se la dipendenza è forte e la mod non viene trovata, l'altra non verrà caricata, mentre se è opzionale, verranno semplicemente caricate meno funzionalità.
Entrambe richiedono che la mod richiesta venga caricata prima, con la differenza che se la
dipendenza è forte e la mod non viene trovata, l'altra fallirà nel caricare, mentre se è opzionale,
verranno semplicemente caricate meno feature.
Le dipendenze sono specificate in un elenco separato da virgole in mod.conf.
Una dipendenza opzionale è utile se si vuole integrare opzionalmente un'altra mod; può abilitare
contenuti extra se l'utente desidera usare entrambe le mod in contemporanea.
Le dipendenze vanno elencate in mod.conf.
### mod.conf
Questo file è utilizzato per i metadati della mod, che includono il suo nome, la descrizione e
altre informazioni. Per esempio:
name = lamiamod
description = Aggiunge X, Y, e Z.
depends = mod1, mod2
optional_depends = mod3
### depends.txt
Per questioni di compatibilità con le versioni 0.4.x di Minetest, al posto di specificare le
dipendenze solamente in mod.conf, c'è bisogno di fornire un file depends.txt nel quale vanno
elencate tutte le dipendenze:
mod1
mod2
mod3?
Ogni nome mod occupa una riga, e i nomi mod seguiti da un punto di domanda indicano una dipendenza
opzionale.
## Pacchetti mod (mod pack)
Le mod possono essere raggruppate in pacchetti che permettono di confezionarne e spostarne più alla volta.
Sono comodi se si vogliono fornire più mod a chi gioca, ma non si vuole al tempo stesso fargliele scaricare una per una.
Le mod possono essere raggruppate in pacchetti che permettono a più mod di essere confezionate
e spostate insieme. Sono comodi se si vogliono fornire più mod al giocatore, ma non si vuole al
tempo stesso fargliele scaricare una per una.
pacchettomod1
├── modpack.lua (necessario) - segnala che è un pacchetto mod
@ -133,7 +167,8 @@ Sono comodi se si vogliono fornire più mod a chi gioca, ma non si vuole al temp
└── mymod (opzionale)
   └── ... file mod
Attenzione che un pacchetto mod non equivale a un *gioco*. I giochi hanno una propria struttura organizzativa che verrà spiegata nel loro apposito capitolo.
Attenzione che un pacchetto mod non equivale a un *gioco*.
I giochi hanno una propria struttura organizzativa che verrà spiegata nel loro apposito capitolo.
## Esempio
@ -143,14 +178,18 @@ Segue un esempio che mette insieme tutto ciò discusso finora:
lamiamod
├── textures
│   └── lamiamod_nodo.png
├── depends.txt
├── init.lua
└── mod.conf
### depends.txt
default
### init.lua
```lua
print("Questo file parte all'avvio!")
print("Questo file parte al caricamento!")
core.register_node("lamiamod:nodo", {
minetest.register_node("lamiamod:nodo", {
description = "Questo è un nodo",
tiles = {"lamiamod_nodo.png"},
groups = {cracky = 1}
@ -162,8 +201,8 @@ core.register_node("lamiamod:nodo", {
descriptions = Aggiunge un nodo
depends = default
Questa mod ha come nome "lamiamod". Ha due file di testo: init.lua e mod.conf.\\
Questa mod ha il nome "lamiamod". Ha tre file di testo: init.lua, mod.conf e depends.txt.\\
Lo script stampa un messaggio e poi registra un nodo che sarà spiegato nel prossimo capitolo.\\
C'è una sola dipendenza, la [mod default](https://content.minetest.net/metapackages/default/), che
si trova solitamente in Minetest Game.\\
si trova solitamente nel Minetest Game.\\
C'è anche una texture in textures/ per il nodo.

View File

@ -9,40 +9,26 @@ redirect_from: /it/chapters/lua.html
## Introduzione <!-- omit in toc -->
In questo capitolo parleremo della programmazione in Lua, degli strumenti necessari, e tratteremo alcune tecniche che troverai probabilmente utili.
In questo capitolo parleremo della programmazione in Lua, degli strumenti necessari,
e tratteremo alcune tecniche che troverai probabilmente utili.
- [Programmare](#programmare)
- [Programmare in Lua](#programmare-in-lua)
- [Editor di codice](#editor-di-codice)
- [Programmare in Lua](#programmare-in-lua)
- [Flusso del programma](#flusso-del-programma)
- [Tipi di variabili](#tipi-di-variabili)
- [Operatori matematici](#operatori-matematici)
- [Selezione](#selezione)
- [Operatori logici](#operatori-logici)
- [Programmare](#programmare)
- [Portata locale e globale](#portata-locale-e-globale)
- [Precedenza alla portata locale](#precedenza-alla-portata-locale)
- [Local dovrebbe essere usato il più possibile](#local-dovrebbe-essere-usato-il-piu-possibile)
- [Inclusione di altri script Lua](#inclusione-di-altri-script-lua)
## Programmare
Programmare è l'azione di prendere un problema, come ordinare una lista di oggetti, e tramutarlo in dei passaggi che il computer può comprendere.
Insegnarti i processi logici della programmazione non rientra nell'ambito di questo libro; tuttavia, i seguenti siti sono alquanto utili per approfondire l'argomento:
* [Codecademy](http://www.codecademy.com/) è una delle migliori risorse per imparare come scrivere codice; offre un'esperienza guidata interattiva.
* [Scratch](https://scratch.mit.edu) è una buona risorsa quando si comincia dalle basi assolute, imparando le tecniche di problem solving necessarie per la programmazione.\\
Scratch è *ideato per insegnare ai bambini* e non è un linguaggio serio di programmazione.
* [Programming with Mosh](https://www.youtube.com/user/programmingwithmosh) is
a good YouTube series to learn programming.
### Programmare in Lua
Neanche insegnarti come programmare in lua rientra nell'ambito di questo libro.
Tuttavia, se mastichi l'inglese puoi rifarti a quest'altro libro, ["Programming in Lua"](https://www.lua.org/pil/contents.html), per un'eccellente infarinatura sull'argomento. Se invece l'inglese non è il tuo forte, troverai comunque svariate guide in italiano in giro per la rete.
## Editor di codice
Un editor di codice con evidenziamento delle parole chiave è sufficiente per scrivere script in Lua.
L'evidenziamento assegna colori diversi a parole e caratteri diversi, a seconda del loro significato, permettendo quindi di individuare più facilmente eventuali errori e inconsistenze.
Per esempio:
L'evidenziamento assegna colori diversi a parole e caratteri diversi a seconda del loro significato.
Questo ti permette di individuare più facilmente eventuali errori.
```lua
function ctf.post(team,msg)
@ -60,16 +46,146 @@ function ctf.post(team,msg)
end
```
Nel passaggio qui sopra, le parole chiave `if`, `then`, `end` e `return` sono evidenziate.
E Lo stesso vale per le funzioni interne di Lua come `table.insert`.
Per esempio, parole chiave come if, then, end e return sono evidenziate nel passaggio qui sopra.
table.insert è invece una funzione base che deriva direttamente da Lua.
Tra gli editor più famosi che ben si prestano a lavorare in Lua, troviamo:
Segue una lista di editor noti che si prestano bene per programmare in Lua.
Non sono, ovviamente, gli unici esisteneti.
* [VSCode](https://code.visualstudio.com/) - software libero (come Code-OSS e VSCodium), rinomato, e che dispone di [estensioni per il modding su Minetest](https://marketplace.visualstudio.com/items?itemName=GreenXenith.minetest-tools).
* [Notepad++](http://notepad-plus-plus.org/) - Solo per Windows
* 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/)
(ne esistono ovviamente anche altri)
## Programmare in Lua
### Flusso del programma
I programmi sono una serie di comandi che vengono eseguiti uno dopo l'altro.
Chiamiamo questi comandi "istruzioni".
Il flusso del programma è il come queste istruzioni vengono eseguite.
Differenti tipi di flusso ti permettono di saltare o meno serie di comandi.
Ci sono tre tipi di flusso:
* Sequenziale: esegue un'istruzione dopo l'altra, senza salti.
* Selettivo: salta alcune sequenze a seconda delle condizioni.
* Iterante: ripete ciclicamente. Continua a eseguire le stesse istruzioni
finché una condizione non è soddisfatta.
Quindi, come vengono rappresentate le istruzioni in Lua?
```lua
local a = 2 -- Imposta 'a' a 2
local b = 2 -- Imposta 'b' a 2
local risultato = a + b -- Imposta 'risultato' ad a + b, cioè 4
a = a + 10
print("La somma è ".. risultato)
```
Whoa, cos'è appena successo?
a, b, e risultato sono *variabili*. Le variabili locali si dichiarano
tramite l'uso della parola chiave local, e assegnando loro un valore iniziale.
Local sarà discussa in un attimo, in quanto parte di un concetto molto importante
chiamato *portata*.
Il simbolo `=` significa *assegnazione*, quindi `risultato = a + b` significa impostare "risultato" ad a + b.
I nomi delle variabili possono essere più lunghi di un carattere, al contrario che in matematica, come
visto nella variabile "risultato".
Vale anche la pena notare che Lua è *case-sensitive* (differenzia maiscuole da minuscole);
A è una variabile diversa da a.
### Tipi di variabili
Una variabile può equivalere solo a uno dei seguenti tipi e può cambiare tipo dopo l'assegnazione.
È buona pratica assicurarsi che una variabile sia sempre solo o nil o diversa da nil.
| Tipo | Descrizione | Esempio |
|----------|---------------------------------|----------------|
| Nil | Non inizializzata. La variabile è vuota, non ha valore | `local A`, `D = nil` |
| Numero | Un numero intero o decimale | `local A = 4` |
| Stringa | Una porzione di testo | `local D = "one two three"` |
| Booleano | Vero o falso (true, false) | `local is_true = false`, `local E = (1 == 1)` |
| Tabella | Liste | Spiegate sotto |
| Funzione | Può essere eseguita. Può richiedere input e ritornare un valore | `local result = func(1, 2, 3)` |
### Operatori matematici
Lista non esaustiva, ce ne sono altri
| Simbolo | Scopo | Esempio |
|---------|--------------------|---------------------------|
| A + B | Addizione | 2 + 2 = 4 |
| A - B | Sottrazione | 2 - 10 = -8 |
| A * B | Moltiplicazione | 2 * 2 = 4 |
| A / B | Divisione | 100 / 50 = 2 |
| A ^ B | Potenze | 2 ^ 2 = 2<sup>2</sup> = 4 |
| A .. B | Concatena stringhe | "foo" .. "bar" = "foobar" |
### Selezione
La selezione più basica è il costrutto if. Si presenta così:
```lua
local random_number = math.random(1, 100) -- Tra 1 e 100.
if random_number > 50 then
print("Woohoo!")
else
print("No!")
end
```
Questo esempio genera un numero casuale tra 1 e 100. Stampa poi
"Woohoo!" se il numero è superiore a 50, altrimenti stampa "No!".
Cos'altro puoi usare oltre a '>'?
### Operatori logici
| Simbolo | Scopo | Esempio |
|---------|--------------------------------------|-------------------------------------------------------------|
| A == B | Uguale a | 1 == 1 (true), 1 == 2 (false) |
| A ~= B | Non uguale a (diverso da) | 1 ~= 1 (false), 1 ~= 2 (true) |
| A > B | Maggiore di | 5 > 2 (true), 1 > 2 (false), 1 > 1 (false) |
| A < B | Minore di | 1 < 3 (true), 3 < 1 (false), 1 < 1 (false) |
| A >= B | Maggiore o uguale a | 5 >= 5 (true), 5 >= 3 (true), 5 >= 6 (false) |
| A <= B | Minore o uguale a | 3 <= 6 (true), 3 <= 3 (true) |
| A and B | E (entrambi devono essere veri) | (2 > 1) and (1 == 1) (true), (2 > 3) and (1 == 1) (false) |
| A or B | O (almeno uno dei due vero) | (2 > 1) or (1 == 2) (true), (2 > 4) or (1 == 3) (false) |
| not A | non vero | not (1 == 2) (true), not (1 == 1) (false) |
La lista non è esaustiva, e puoi inoltre combinare gli operatori in questo modo:
```lua
if not A and B then
print("Yay!")
end
```
Che stampa "Yay!" se A è falso e B vero.
Gli operatori logici e matematici funzionano esattamente allo stesso modo;
entrambi accettano input e ritornano un valore che può essere immagazzinato.
```lua
local A = 5
local is_equal = (A == 5)
if is_equal then
print("È equivalente!")
end
```
## Programmare
Programmare è l'azione di prendere un problema, come ordinare una lista di oggetti,
e tramutarlo in dei passaggi che il computer può comprendere.
Insegnarti i processi logici della programmazione non rientra nell'ambito di questo libro;
tuttavia, i seguenti siti sono alquanto utili per approfondire l'argomento:
* [Codecademy](http://www.codecademy.com/) è una delle migliori risorse per
imparare a 'programmare'; offre un'esperienza guidata interattiva.
* [Scratch](https://scratch.mit.edu) è una buona risorsa quando si comincia
dalle basi assolute, imparando le tecniche di problem solving necessarie per programmare.\\
Scratch è **ideato per insegnare ai bambini** e non è un linguaggio serio di programmazione.
## Portata locale e globale
@ -93,6 +209,24 @@ end
Mentre le variabili globali sono accessibili da qualsiasi script di qualsiasi mod.
```lua
my_global_variable = "ciao"
function one()
my_global_variable = "hey"
end
print(my_global_variable) -- Output: "ciao"
one()
print(my_global_variable) -- Output: "hey"
```
### Local dovrebbe essere usato il più possibile
Lua è globale di default (a differenza di molti altri linguaggi di programmazione).
Le variabili locali devono essere identificate come tali.
```lua
function one()
foo = "bar"
@ -106,15 +240,15 @@ one()
two()
```
dump() è una funzione che può trasformare qualsiasi variabile in una stringa, cosicché
il programmatore possa vedere cosa rappresenta. La variabile foo sarà stampata come
"bar", virgolette incluse (che dimostrano che è una stringa).
### Precedenza alla portata locale
Le variabili locali dovrebbero venire usate il più possibile, con le mod che creano al massimo una globale corrispondente al nome della mod.
Crearne di ulteriori è considerato cattiva programmazione, e Minetest ci avviserà di ciò:
L'esempio precedente non è buona programmazione e Minetest, infatti, avviserà di ciò:
Assignment to undeclared global 'foo' inside function at init.lua:2
Per ovviare, usa `local`:
Per ovviare, usa "local":
```lua
function one()
@ -129,10 +263,13 @@ one()
two()
```
Ricorda che `nil` significa **non inizializzato**.
Ovvero la variabile non è stata ancora assegnata a un valore, non esiste o è stata deinizializzata (cioè impostata a `nil`)
Ricorda che nil significa **non inizializzato**.
Ovvero la variabile non è stata ancora assegnata a un valore,
non esiste o è stata deinizializzata (cioè impostata a nil)
La stessa cosa vale per le funzioni: esse sono variabili di tipo speciale, e dovrebbero essere dichiarate locali, in quanto altre mod potrebbero sennò avere funzioni con lo stesso nome.
La stessa cosa vale per le funzioni. Le funzioni sono variabili di tipo speciale,
e dovrebbero essere dichiarate locali, in quanto altre mod potrebbero sennò avere funzioni
con lo stesso nome.
```lua
local function foo(bar)
@ -140,8 +277,7 @@ local function foo(bar)
end
```
Per permettere alle mod di richiamare le tue funzioni, dovresti creare una tabella con lo stesso nome della mod e aggiungercele all'interno.
Questa tabella è spesso chiamata una API.
Le tabelle API dovrebbero essere usate per permettere ad altre mod di chiamare le funzioni, come in:
```lua
mymod = {}
@ -159,7 +295,7 @@ mymod.foo("foobar")
Il metodo consigliato per includere in una mod altri script Lua è usare *dofile*.
```lua
dofile(core.get_modpath("modname") .. "/script.lua")
dofile(minetest.get_modpath("modname") .. "/script.lua")
```
Uno script può ritornare un valore, che è utile per condividere variabili locali private:
@ -169,8 +305,10 @@ Uno script può ritornare un valore, che è utile per condividere variabili loca
return "Hello world!"
-- init.lua
local ret = dofile(core.get_modpath("modname") .. "/script.lua")
local ret = dofile(minetest.get_modpath("modname") .. "/script.lua")
print(ret) -- Hello world!
```
Nei [capitoli seguenti](../quality/clean_arch.html) si parlerà nel dettaglio di come suddividere il codice di una mod.
Nei capitoli seguenti si parlerà nel dettaglio di come suddividere il codice di una mod.
Tuttavia, per ora l'approccio semplicistico è di avere file differenti per diversi tipi di cose
— nodi.lua, craft.lua, oggetti.lua ecc.

View File

@ -9,10 +9,10 @@ idx: 7.1
Il punto forte di Minetest è quello di poter sviluppare giochi con facilità senza il bisogno di costruirsi il proprio motore grafico voxel, i propri algoritmi voxel, o la propria parte network.
- [Cos'è un gioco?](#cosè-un-gioco)
- [Cos'è un gioco?](#cose-un-gioco)
- [Cartella di un gioco](#cartella-di-un-gioco)
- [Compatibilità tra giochi](#compatibilità-tra-giochi)
- [Compatibilità delle API](#compatibilità-delle-api)
- [Compatibilità tra giochi](#compatibilita-tra-giochi)
- [Compatibilità delle API](#compatibilita-delle-api)
- [Gruppi e alias](#gruppi-e-alias)
- [Il tuo turno](#il-tuo-turno)

View File

@ -1,6 +1,5 @@
---
title: Copertina
description: An easy guide to learn how to create mods for Minetest
title: Front Cover
layout: default
homepage: true
no_header: true
@ -9,7 +8,7 @@ idx: 0.1
---
<header>
<h1>Minetest: Libro del Moddaggio</h1>
<h1>Minetest: Libro del Modding</h1>
<span>di <a href="https://rubenwardy.com" rel="author">rubenwardy</a></span>
<span>con modifiche di <a href="http://rc.minetest.tv/">Shara</a></span>
@ -18,18 +17,20 @@ idx: 0.1
## Introduzione
Il moddaggio su Minetest è supportato grazie a script in Lua.
Questo libro mira a insegnarti come si crea una mod, iniziando dalle basi: ogni capitolo si concentra su un aspetto specifico dell'API, così da arrivare in breve tempo a farti creare i tuoi contenuti.
Il modding su Minetest è supportato grazie a script in Lua.
Questo libro mira a insegnarti come creare le tue mod, iniziando dalle basi.
Ogni capitolo si concentra su un punto specifico dell'API, e ti porterà presto
a fare le tue mod.
Oltre che [leggere questo libro su internet](https://rubenwardy.com/minetest_modding_book),
Oltre che [leggere questo libro online](https://rubenwardy.com/minetest_modding_book),
puoi anche [scaricarlo in HTML](https://github.com/rubenwardy/minetest_modding_book/releases).
### Riscontri e Contributi
### Feedback e Contributi
Hai notato un errore o vuoi dirmi la tua? Assicurati di farmelo presente.
Hai notato un errore o vuoi dare un feedback? Assicurati di farmelo presente.
* Apri una [Segnalazione su GitLab](https://gitlab.com/rubenwardy/minetest_modding_book/-/issues).
* Rispondi alla [Discussione sul Forum](https://forum.minetest.net/viewtopic.php?f=14&t=10729).
* Crea una [Issue su GitLab](https://gitlab.com/rubenwardy/minetest_modding_book/-/issues).
* Posta nel [Topic sul Forum](https://forum.minetest.net/viewtopic.php?f=14&t=10729).
* [Contattami (in inglese)](https://rubenwardy.com/contact/).
* Voglia di contribuire?
[Leggi il README](https://gitlab.com/rubenwardy/minetest_modding_book/-/blob/master/README.md).

View File

@ -1,184 +0,0 @@
---
title: Richiami dei nodi e degli oggetti
layout: default
root: ../..
idx: 2.15
description: Scopri i richiami, le azioni e gli eventi, come on_use, on_punch, on_place e on_rightclick
---
## Introduction <!-- omit in toc -->
Minetest usa una struttura di moddaggio estensivamente incentrata sui richiami. Un richiamo è una funzione che si dà a un'API e che viene chiamata quando l'evento registrato si verifica.
Per esempio, puoi aggiungere una funzione `on_punch` nella definizione di un nodo, che verrà chiamata quando questo viene colpito.
Ci sono poi anche dei richiami globali, come `core.register_on_punchnode`, che in questo caso verrà invocato al colpire qualsiasi nodo.
- [Richiami degli oggetti](#richiami-degli-oggetti)
- [on_use](#on_use)
- [on_place e on_secondary_use](#on_place-e-on_secondary_use)
- [on_drop](#on_drop)
- [after_use](#after_use)
- [item_place contro place_item](#item_place-contro-place_item)
- [Richiami dei nodi](#richiami-dei-nodi)
- [Tasto destro e nodi piazzati](#tasto-destro-e-nodi-piazzati)
- [Colpire e scavare](#colpire-e-scavare)
- [...e altro!](#e-altro)
## Richiami degli oggetti
Quando un giocatore ha un nodo, un oggetto fabbricabile o uno strumento nel proprio inventario, questi potrebbero innescare degli eventi:
| Richiamo | Assegnazione base | Valore base |
|------------------|---------------------------|----------------------------------------------|
| on_use | clic sinistro | nil |
| on_place | clic destro su un nodo | `core.item_place` |
| on_secondary_use | clic destro a vuoto | `core.item_secondary_use` (non fa nulla) |
| on_drop | Q | `core.item_drop` |
| after_use | allo scavare un nodo | nil |
### on_use
Sovrascrivere l'uso dell'oggetto impedisce che quest'ultimo possa essere usato per scavare nodi.
Un impiego comune di questo richiamo lo si trova nel cibo:
```lua
core.register_craftitem("miamod:fangotorta", {
description = "Torta aliena di fango",
inventory_image = "miamod_fangotorta.png",
on_use = core.item_eat(20),
})
```
Il numero fornito alla funzione core.item_eat è il numero di punti salute ripristinati al consumare il cibo.
In gioco ogni cuore equivale a due punti.
Un giocatore ha solitamente un massimo di 10 cuori, ovvero 20 punti salute, e quest'ultimi non devono per forza essere interi - bensì anche decimali.
`core.item_eat()` è una funzione che ritorna un'altra funzione, in questo caso quindi impostandola come richiamo di on_use.
Ciò significa che il codice in alto è alquanto simile al seguente:
```lua
core.register_craftitem("miamod:fangotorta", {
description = "Torta aliena di fango",
inventory_image = "miamod_fangotorta.png",
on_use = function(...)
return core.do_item_eat(20, nil, ...)
end,
})
```
Capendo come funziona item_eat, è possibile modificarlo per operazioni più complesse
come per esempio riprodurre un suono personalizzato.
### on_place e on_secondary_use
La differenza tra `on_place` e `on_secondary_use` consiste nel fatto che `on_place` viene chiamato quando il giocatore sta puntando un nodo, mentre `on_secondary_use` quando non ne punta uno.
Entrambi i richiami sono invocati per tutti i tipi di oggetti.
`on_place` risponde alla funzione `core.item_place`, la quale o gestisce la chiamata a `on_rightclick` del nodo puntato, o piazza l'oggetto in mano se questo è un nodo.
### on_drop
`on_drop` viene chiamato quando il giocatore fa richiesta per buttare un oggetto, per esempio usando il tasto apposito (Q) o trascinando l'oggetto fuori dall'inventario.
Risponde alla funzione `core.item_drop`, la quale gestisce il buttare l'oggetto.
### after_use
`after_use` viene chiamato quando si scava un nodo, e permette di personalizzare come viene applicata l'usura a uno strumento.
Se `after_use` non esiste, è come se ci fosse scritto:
```lua
after_use = function(itemstack, user, node, digparams)
itemstack:add_wear(digparams.wear)
return itemstack
end
```
## item_place contro place_item
L'API di Minetest include varie implementazioni già pronte di richiami.
Queste seguono la nomenclatura "tipodioggetto_azione", per esempio `core.item_place` e `core.node_dig`.
Alcune sono usate direttamente, mentre altre sono funzioni che ritornano il richiamo vero e proprio:
```lua
core.register_item("miamod:esempio", {
on_place = core.item_place,
on_use = core.item_eat(10),
})
```
Inoltre, l'API di Minetest include funzioni già pronte che _fanno_ qualcosa.
Queste sono spesso chiamate con nomi che rischiano di farle confondere con le implementazioni dei richiami, tuttavia hanno un verbo all'inizio (per esempio `core.place_item` e `core.dig_node`, che permettono rispettivamente di scavare e piazzare nodi come se lo stesse facendo un giocatore).
## Richiami dei nodi
Quando un nodo si trova in un inventario, vengono invocati i richiami degli oggetti discussi poc'anzi.
Al contrario, quando un nodo è situato nel mondo, vengono invocati i richiami dei nodi.
Ce ne sono di svariati tipi, troppi per essere discussi in questo libro, tuttavia alcuni di questi verranno trattati nei capitoli successivi.
Molti richiami dei nodi sono collegati alle operazioni effettuate - appunto - sui nodi, come piazzarli e rimuoverli dal mondo.
È importante però sottolineare che, per motivi di prestazioni, operazioni come queste non vengono chiamate da modifiche in blocco (quelle che cambiano un grande numero di nodi in un colpo solo).
È meglio quindi non fare affidamento su un'esecuzione sicura al 100%.
### Tasto destro e nodi piazzati
Quando un utente preme col tasto destro un nodo mentre ha un oggetto in mano, viene invocato il richiamo `on_place` dell'oggetto.
Di base, questo è impostato a `core.item_place`.
Se il nodo puntato ha un richiamo `on_rightclick` e il tasto accovacciati (shift) è tenuto premuto, allora verrà chiamato `on_rightclick`.
Diversamente, `core.item_place` piazzerà il nodo.
Piazzare un nodo invocherà simultaneamente `on_construct` e `after_place_node`: il primo è chiamato da ogni evento che cambia i singoli nodi (quindi non in blocco) e ritorna la posizione e il valore del nodo.
`after_place_node` viene invece chiamato solamente al piazzare un nodo, contenendo di conseguenza più informazioni - come chi l'ha piazzato e l'ItemStack.
È importante notare che i giocatori non sono le uniche realtà che possono piazzare nodi; anche le entità e le mod possono farlo.
Per via di ciò, `place` potrebbe essere un giocatore, ma anche un'entità o `nil`.
```lua
core.register_node("miamod:mionodo", {
on_rightclick = function(pos, node, clicker, itemstack, pointed_thing)
if clicker:is_player() then
core.chat_send_player(clicker:get_player_name(), "Ciao mondo!")
end
end,
on_construct = function(pos, node)
local meta = core.get_meta(pos)
meta:set_string("infotext", "Il mio nodo!")
end,
after_place_node = function(pos, placer, itemstack, pointed_thing)
-- controlla chi sta piazzando
if placer and placer:is_player() then
local meta = core.get_meta(pos)
meta:set_string("proprietario", placer:get_player_name())
end
end,
})
```
### Colpire e scavare
Si ha un colpo quando un giocatore preme col tasto sinistro per un breve periodo.
Se l'oggetto in mano possiede un richiamo `on_use`, questo verrà chiamato.
Diversamente, verrà chiamato il richiamo `on_punch` sul nodo selezionato.
Quando il giocatore tenta di scavare un nodo, viene eseguito il richiamo `on_dig` del nodo.
Di base, ciò equivale a `core.node_dig`, che controlla eventuali protezioni dell'area, usura l'oggetto, rimuove il nodo, e ne esegue il richiamo `after_dig_node`.
```lua
core.register_node("miamod:mionodo", {
on_punch = function(pos, node, puncher, pointed_thing)
if puncher:is_player() then
core.chat_send_player(puncher:get_player_name(), "Ahia!")
end
end,
})
```
### ...e altro!
Dài un occhio alla API Lua di Minetest per una lista di tutti i richiami, e per avere più informazioni riguardo quelli vista qui sopra.

View File

@ -9,10 +9,13 @@ redirect_from: /it/chapters/creating_textures.html
## Introduzione <!-- omit in toc -->
Essere in grado di creare e ottimizare le texture è un'abilità alquanto utile quando si sviluppa per Minetest.
Ci sono molti approcci sul come creare texture in pixel art, e capire questi approcci migliorerà nettamente la qualità dei tuoi lavori.
Essere in grado di creare e ottimizare le texture è un'abilità alquanto utile quando si sviluppa
per Minetest.
Ci sono molti approcci sul come creare texture in pixel art, e capire questi approcci
migliorerà nettamente la qualità dei tuoi lavori.
Fornire spiegazioni dettagliate non rientra tuttavia nell'ambito di questo libro: verranno quindi trattate solo le tecniche più semplici.
Fornire spiegazioni dettagliate non rientra tuttavia nell'ambito di questo libro:
verranno quindi trattate solo le tecniche più semplici.
Se si vuole approfondire, ci sono comunque molti [buoni tutorial online](http://www.photonstorm.com/art/tutorials-art/16x16-pixel-art-tutorial) disponibili, che si occupano di pixel art in modo molto più dettagliato.
- [Tecniche](#tecniche)
@ -28,14 +31,17 @@ Se si vuole approfondire, ci sono comunque molti [buoni tutorial online](http://
### Usare la matita
Lo strumento matita è disponibile nella maggior parte dei programmi di disegno.
Quando viene impostato alla dimensione minima, permette di disegnare un pixel alla volta senza alterare le atre parti dell'immagine.
Manipolando i singoli pixel si possono creare texture chiare e nette senza alcuna sfocatura non voluta, dando inoltre un alto livello di precisione e controllo.
Quando viene impostato alla dimensione minima, ti permette di disegnare un pixel alla volta
senza alterare le atre parti dell'immagine.
Manipolando i singoli pixel si possono creare texture chiare e nette senza alcuna
sfocatura non voluta, dando inoltre un alto livello di precisione e controllo.
### Piastrellatura (tiling)
Le texture usate per i nodi dovrebbero generalmente essere progettate per ripetersi come
delle piastrelle.
Questo significa che quando piazzi più nodi con la stessa texture vicini, i bordi dovranno allinearsi correttamente creando un effetto di continuità.
Questo significa che quando piazzi più nodi con la stessa texture vicini, i bordi dovranno
allinearsi correttamente creando un effetto di continuità.
<!-- IMAGE NEEDED - cobblestone that tiles correctly -->
@ -46,8 +52,10 @@ gradevole da vedere.
### Trasparenza
La trasparenza è importante quando si creano texture per pressoché tutti gli oggetti fabbricabili e per alcuni nodi, come il vetro.
Non tutti i programmi supportano la trasparenza, perciò assicurati di sceglierne uno adatto ai tipi di texture che vuoi creare.
La trasparenza è importante quando si creano texture per pressoché tutti gli
oggetti fabbricabili e per alcuni nodi, come il vetro.
Non tutti i programmi supportano la trasparenza, perciò assicurati di sceglierne
uno adatto ai tipi di texture che vuoi creare.
## Programmi
@ -61,7 +69,8 @@ tuttavia se la trasparenza è un requisito nelle tue texture dovresti guardare o
### GIMP
GIMP viene impiegato spesso nella comunità di Minetest.
Ha una curva di apprendimento alquanto alta, dato che molte delle sue funzioni non risultano ovvie nell'immediato.
Ha una curva di apprendimento alquanto alta, dato che molte delle sue funzioni
non risultano ovvie nell'immediato.
Quando usi GIMP, puoi selezionare la matita dalla Barra degli Strumenti:

View File

@ -1,5 +1,5 @@
---
title: ItemStack e inventari
title: ItemStack e Inventari
layout: default
root: ../..
idx: 2.4
@ -13,7 +13,8 @@ redirect_from:
## Introduzione <!-- omit in toc -->
In questo capitolo, imparerai come usare e manipolare gli inventari, siano essi quelli di un giocatore, di un nodo o a sé stanti.
In questo capitolo, imparerai come usare e manipolare gli inventari, siano essi quelli
di un giocatore, di un nodo o a sé stanti.
- [Cosa sono gli ItemStack e gli inventari?](#cosa-sono-gli-itemstack-e-gli-inventari)
- [ItemStack](#itemstack)
@ -30,17 +31,21 @@ In questo capitolo, imparerai come usare e manipolare gli inventari, siano essi
## Cosa sono gli ItemStack e gli inventari?
Un ItemStack ( lett. "pila di oggetti") è il dato dietro una singola cella di un inventario.
Un ItemStack (*pila di oggetti*) è il dato dietro una singola cella di un inventario.
Un *inventario* è una collezione di *liste* apposite, ognuna delle quali è una griglia 2D di ItemStack.
Lo scopo di un inventario è quello di raggruppare più liste in un singolo oggetto (l'inventario appunto), in quanto a ogni giocatore e a ogni nodo ne può essere associato massimo uno.
Un *inventario* è una collezione di *liste* apposite, ognuna delle quali è una griglia
2D di ItemStack.
Lo scopo di un inventario è quello di raggruppare più liste in un singolo oggetto (l'inventario appunto),
in quanto a ogni giocatore e a ogni nodo può essere associato massimo un inventario.
## ItemStack
Gli ItemStack sono composti da quattro parametri: nome, quantità, durabilità e metadati.
Il nome dell'oggetto può essere il nome di un oggetto registrato, di uno sconosciuto (non registrato) o un alias.
Gli oggetti sconosciuti sono tipici di quando si disinstallano le mod, o quando le mod rimuovono degli oggetti senza nessun accorgimento, tipo senza registrarne un alias.
Il nome dell'oggetto può essere il nome di un oggetto registrato, di uno sconosciuto (non registrato)
o un alias.
Gli oggetti sconosciuti sono tipici di quando si disinstallano le mod, o quando le mod rimuovono
degli oggetti senza nessun accorgimento, tipo senza registrarne un alias.
```lua
print(stack:get_name())
@ -52,13 +57,18 @@ end
```
La quantità sarà sempre 0 o maggiore.
Durante una normale sessione di gioco, la quantità non dovrebbe mai essere maggiore della dimensione massima della pila dell'oggetto - `stack_max`.
Tuttavia, comandi da amministratore e mod fallate potrebbero portare a oggetti impilati che superano la grandezza massima.
Durante una normale sessione di gioco, la quantità non dovrebbe mai essere maggiore della dimensione
massima della pila dell'oggetto - `stack_max`.
Tuttavia, comandi da amministratore e mod fallate potrebbero portare a oggetti impilati che superano
la grandezza massima.
```lua
print(stack:get_stack_max())
```
Un ItemStack può essere vuoto, nel qual caso avrà come quantità 0.
```lua
@ -66,7 +76,7 @@ print(stack:get_count())
stack:set_count(10)
```
Gli ItemStack possono poi essere creati in diversi modi usando l'omonima funzione.
Gli ItemStack possono essere creati in diversi modi usando la funzione ItemStack.
```lua
ItemStack() -- name="", count=0
@ -77,7 +87,8 @@ ItemStack({ name = "default:wood", count = 10 })
I metadati di un oggetto sono una o più coppie chiave-valore custodite in esso.
Chiave-valore significa che si usa un nome (la chiave) per accedere al dato corrispettivo (il valore).
Alcune chiavi hanno significati predefiniti, come `description` che è usato per specificare la descrizione di una pila di oggetti.
Alcune chiavi hanno significati predefiniti, come `description` che è usato per specificare la descrizione
di una pila di oggetti.
Questo sarà trattato più in dettaglio nel capitolo Storaggio e Metadati.
## Collocazione inventari
@ -90,11 +101,12 @@ Gli inventari collocati nei nodi sono associati alle coordinate di un nodo speci
Il nodo deve essere stato caricato perché viene salvato [nei suoi metadati](../map/storage.html#metadata).
```lua
local inv = core.get_inventory({ type="node", pos={x=1, y=2, z=3} })
local inv = minetest.get_inventory({ type="node", pos={x=1, y=2, z=3} })
```
L'esempio in alto ottiene il *riferimento a un inventario*, comunemente definito *InvRef*.
Questi riferimenti sono usati per manipolare l'inventario, e son chiamati così perché i dati non sono davvero salvati dentro all'oggetto (in questo caso "inv"), bensì *puntano* a quei dati.
Questi riferimenti sono usati per manipolare l'inventario, e son chiamati così perché i dati
non sono davvero salvati dentro all'oggetto (in questo caso "inv"), bensì *puntano* a quei dati.
In questo modo, modificando "inv", stiamo in verità modificando l'inventario.
La collocazione di tali riferimenti può essere ottenuta nel seguente modo:
@ -103,34 +115,36 @@ La collocazione di tali riferimenti può essere ottenuta nel seguente modo:
local location = inv:get_location()
```
Gli inventari dei giocatori si ottengono in maniera simile, oppure usando il riferimento a un giocatore (*PlayerRef*).
In entrambi casi, il giocatore deve essere connesso.
Gli inventari dei giocatori si ottengono in maniera simile, oppure usando il riferimento
a un giocatore (*PlayerRef*). In entrambi casi, il giocatore deve essere connesso.
```lua
local inv = core.get_inventory({ type="player", name="player1" })
local inv = minetest.get_inventory({ type="player", name="player1" })
-- oppure
local inv = player:get_inventory()
```
Gli inventari separati, infine, sono quelli non collegati né a nodi né a giocatori, e al contrario degli altri, vengono persi dopo un riavvio.
Gli inventari separati, infine, sono quelli non collegati né a nodi né a giocatori,
e al contrario degli altri, vengono persi dopo un riavvio.
```lua
local inv = core.get_inventory({
local inv = minetest.get_inventory({
type="detached", name="nome_inventario" })
```
Un'ulteriore differenza, è che gli inventari separati devono essere creati prima di poterci accedere:
```lua
core.create_detached_inventory("inventory_name")
minetest.create_detached_inventory("inventory_name")
```
La funzione `create_detached_inventory` accetta 3 parametri, di cui solo il primo - il nome - è necessario.
Il secondo parametro prende una tabella di callback, che possono essere utilizzati per controllare come i giocatori interagiscono con l'inventario:
La funzione create_detached_inventory accetta 3 parametri, di cui solo il primo - il nome - è necessario.
Il secondo parametro prende una tabella di callback, che possono essere utilizzati
per controllare come i giocatori interagiscono con l'inventario:
```lua
-- Input only detached inventory
core.create_detached_inventory("inventory_name", {
minetest.create_detached_inventory("inventory_name", {
allow_move = function(inv, from_list, from_index, to_list, to_index, count, player)
return count -- permette di spostare gli oggetti
end,
@ -140,29 +154,33 @@ core.create_detached_inventory("inventory_name", {
end,
allow_take = function(inv, listname, index, stack, player)
return 0 -- non permette di rimuoverli
return -1 -- non permette di rimuoverli
end,
on_put = function(inv, listname, index, stack, player)
core.chat_send_all(player:get_player_name() ..
minetest.chat_send_all(player:get_player_name() ..
" ha messo " .. stack:to_string() ..
" nella cassa delle donazioni da " .. core.pos_to_string(player:get_pos()))
" nella cassa delle donazioni da " .. minetest.pos_to_string(player:get_pos()))
end,
})
```
I callback dei permessi - quelle che iniziano con `allow_` - ritornano il numero degli oggetti da trasferire, e si usa 0 per impedirne del tutto l'azione.
I callback dei permessi - quelle che iniziano con `allow_` - ritornano il numero
degli oggetti da trasferire, e si usa -1 per impedirne del tutto l'azione.
I callback delle azioni - quelle che iniziano con `on_` - non ritornano invece alcun valore.
## Liste
Le liste negli inventari permettono di disporre più griglie nello stesso luogo (l'inventario).
Esse sono particolarmente utili per il giocatore, e infatti di base ogni gioco possiede già delle liste come *main* per il corpo principale dell'inventario e *craft* per l'area di fabbricazione.
Esse sono particolarmente utili per il giocatore, e infatti di base ogni gioco possiede già
delle liste come *main* per il corpo principale dell'inventario e *craft* per l'area di fabbricazione.
### Dimensione e ampiezza
Le liste hanno una dimensione, equivalente al numero totale di celle nella griglia, e un'ampiezza, che è usata esclusivamente dentro il motore di gioco: quando viene disegnato un inventario in una finestra, infatti, il codice dietro di essa già determina che ampiezza usare.
Le liste hanno una dimensione, equivalente al numero totale di celle nella griglia, e un'ampiezza,
che è usata esclusivamente dentro il motore di gioco: quando viene disegnato un inventario in una finestra,
infatti, il codice dietro di essa già determina che ampiezza usare.
```lua
if inv:set_size("main", 32) then
@ -174,8 +192,10 @@ else
end
```
`set_size` non andrà in porto e ritornerà "false" se il nome della lista o la dimensione dichiarata non risultano valide.
Per esempio, la nuova dimensione potrebbe essere troppo piccola per contenere gli oggetti attualmente presenti nell'inventario.
`set_size` non andrà in porto e ritornerà "false" se il nome della lista o la dimensione dichiarata
non risultano valide.
Per esempio, la nuova dimensione potrebbe essere troppo piccola per contenere gli oggetti attualmente
presenti nell'inventario.
### Controllare il contenuto
@ -199,8 +219,8 @@ end
### Aggiungere a una lista
Per aggiungere degli oggetti a una lista (in questo caso "main") usiamo `add_item`.
Nell'esempio sottostante ci accertiamo anche di rispettare la dimensione:
Per aggiungere degli oggetti a una lista (in questo caso "main") usiamo `add_item`. Nell'esempio
sottostante ci accertiamo anche di rispettare la dimensione:
```lua
local stack = ItemStack("default:stone 99")
@ -228,7 +248,8 @@ Puoi modificare le singole pile prima ottenendole:
local stack = inv:get_stack(listname, 0)
```
E poi modificandole impostando le nuove proprietà o usando i metodi che rispettano `stack_size`:
E poi modificandole impostando le nuove proprietà o usando i metodi che
rispettano `stack_size`:
```lua
local pila = ItemStack("default:stone 50")
@ -246,7 +267,8 @@ print("Hai " .. pila:get_count() .. " oggetti")
```
`add_item` aggiungerà gli oggetti all'ItemStack e ritornerà quelli in eccesso.
`take_item` rimuoverà gli oggetti indicati (o meno se ce ne sono meno), e ritornerà l'ammontare rimosso.
`take_item` rimuoverà gli oggetti indicati (o meno se ce ne sono meno), e ritornerà
l'ammontare rimosso.
Infine, si imposta la pila modificata:
@ -256,8 +278,9 @@ inv:set_stack(listname, 0, pila)
## Usura
Gli strumenti possono avere un livello di usura; essa è rappresentata da un barra progressiva e fa rompere lo strumento quando completamente logorato.
Nello specifico, l'usura è un numero da 0 a 65535: più è alto, più è consumato l'oggetto.
Gli strumenti possono avere un livello di usura; l'usura è rappresentata da un barra progressiva
e fa rompere lo strumento quando completamente logorato.
L'usura è un numero da 0 a 65535; più è alto, più è consumato l'oggetto.
Il livello di usura può essere manipolato usando `add_wear()`, `get_wear()`, e `set_wear(wear)`.
@ -270,7 +293,7 @@ local usi_massimi = 10
pila:add_wear(65535 / (usi_massimi - 1))
```
Quando si scava un nodo, l'incremento di usura di uno strumento dipende da che tipo di nodo è.
Quando scavi un nodo, l'incremento di usura di uno strumento dipende da che tipo di nodo è.
Di conseguenza, `usi_massimi` varia a seconda di cos'è stato scavato.
## Tabelle Lua
@ -311,8 +334,10 @@ La tabella di liste ritornata da `get_lists()` sarà nel seguente formato:
`get_list()` ritornerà una lista singola fatta di ItemStack.
Una cosa importante da sottolineare è che i metodi `set` qui in alto non cambiano la dimensione delle liste.
Questo significa che si può svuotare una lista dichiarandola uguale a una tabella vuota, e la sua dimensione tuttavia non cambierà:
Una cosa importante da sottolineare è che i metodi `set` qui in alto non cambiano
la dimensione delle liste.
Questo significa che si può svuotare una lista dichiarandola uguale a una tabella vuota,
e la sua dimensione tuttavia non cambierà:
```lua
inv:set_list("main", {})

View File

@ -10,14 +10,21 @@ redirect_from: /it/chapters/node_drawtypes.html
## Introduzione <!-- omit in toc -->
Il metodo col quale un nodo viene disegnato in gioco è chiamato *drawtype*.
Ci sono diversi tipi di drawtype: il loro comportamento è determinato dalle proprietà impostate durante la definizione del tipo di nodo.
Queste proprietà sono fisse, uguali per tutte le istanze, tuttavia è possibile manipolarne alcune per singolo nodo usando una cosa chiamata `param2`.
Ci sono diversi tipi di drawtype: il loro comportamento è determinato dalle proprietà
impostate durante la definizione del tipo di nodo.
Queste proprietà sono fisse, uguali per tutte le istanze, tuttavia
è possibile manipolarne alcune per singolo nodo usando una cosa chiamata `param2`.
Il concetto di nodo è stato introdotto nello scorso capitolo, ma non è mai stata data una definizione completa.
Il mondo di Minetest è una griglia 3D: un nodo è un punto di quella griglia ed è composto da un tipo (`name`) e due parametri (`param1` e `param2`).
Non farti inoltre ingannare dalla funzione `core.register_node`, in quanto è un po' fuorviante: essa non registra infatti un nuovo nodo (c'è solo una definizione di nodo), bensì un nuovo *tipo* di nodo.
Il concetto di nodo è stato introdotto nello scorso capitolo, ma non è mai stata
data una definizione completa.
Il mondo di Minetest è una griglia 3D: un nodo è un punto di quella griglia ed è composto
da un tipo (name) e due parametri (param1 e param2).
Non farti inoltre ingannare dalla funzione `minetest.register_node`, in quanto è un po' fuorviante:
essa non registra infatti un nuovo nodo (c'è solo una definizione di nodo), bensì un nuovo *tipo* di nodo.
I parametri sono infine usati per controllare come un nodo viene renderizzato individualmente: `param1` immagazzina le proprietà di luce, mentre il ruolo di `param2` dipende dalla proprietà `paramtype2`, la quale è situata nella definizione dei singoli tipi.
I parametri sono infine usati per controllare come un nodo viene renderizzato individualmente:
`param1` immagazzina le proprietà di luce, mentre il ruolo di `param2` dipende dalla
proprietà `paramtype2`, la quale è situata nella definizione dei singoli tipi di nodi.
- [Nodi cubici: normali e a facciate piene](#nodi-cubici-normali-e-a-facciate-piene)
- [Nodi vitrei](#nodi-vitrei)
@ -43,21 +50,23 @@ I parametri sono infine usati per controllare come un nodo viene renderizzato in
</figcaption>
</figure>
Il *drawtype* normale è tipicamente usato per renderizzare un nodo cubico.
Se il lato di uno di questi nodi tocca un nodo solido, allora quel lato non sarà renderizzato, risultando in un grande guadagno sulle prestazioni.
Il drawtype normale è tipicamente usato per renderizzare un nodo cubico.
Se il lato di uno di questi nodi tocca un nodo solido, allora quel lato non sarà renderizzato,
risultando in un grande guadagno sulle prestazioni.
Al contrario, i *drawtype* a facciate piene (*allfaces*) renderizzeranno comunque il lato interno quando è contro un nodo solido.
Ciò è buono per quei nodi con facce in parte trasparenti come le foglie.
Puoi inoltre usare il drawtype `allfaces_optional` per permettere agli utenti di fare opt-out dal rendering più pesante, facendo comportare il nodo come se fosse di tipo normale.
Al contrario, i drawtype a facciate piene (allfaces) renderizzeranno comunque il lato interno quando
è contro un nodo solido. Ciò è buono per quei nodi con facce in parte trasparenti come
le foglie. Puoi inoltre usare il drawtype `allfaces_optional` per permettere agli utenti
di fare opt-out dal rendering più pesante, facendo comportare il nodo come se fosse di tipo normale.
```lua
core.register_node("miamod:diamante", {
minetest.register_node("miamod:diamante", {
description = "Diamante alieno",
tiles = {"miamod_diamante.png"},
groups = {cracky = 3},
})
core.register_node("default:foglie", {
minetest.register_node("default:foglie", {
description = "Foglie",
drawtype = "allfaces_optional",
tiles = {"default_foglie.png"}
@ -68,8 +77,10 @@ Attenzione: il drawtype normale è quello predefinito, quindi non c'è bisogno d
## Nodi vitrei
La differenza tra i nodi vitrei (*glasslike*) e quelli normali è che piazzando i primi vicino a un nodo normale, non nasconderanno il lato di quest'ultimo.
Questo è utile in quanto i nodi vitrei tendono a essere trasparenti, perciò permettono di vedere attraverso.
La differenza tra i nodi vitrei (glasslike) e quelli normali è che piazzando i primi vicino a un
nodo normale, non nasconderanno il lato di quest'ultimo.
Questo è utile in quanto i nodi vitrei tendono a essere trasparenti, perciò permettono
di vedere attraverso.
<figure>
<img src="{{ page.root }}//static/drawtype_glasslike_edges.png" alt="Bordi vitrei">
@ -79,7 +90,7 @@ Questo è utile in quanto i nodi vitrei tendono a essere trasparenti, perciò pe
</figure>
```lua
core.register_node("default:obsidian_glass", {
minetest.register_node("default:obsidian_glass", {
description = "Vetro d'ossidiana",
drawtype = "glasslike",
tiles = {"default_obsidian_glass.png"},
@ -93,7 +104,8 @@ core.register_node("default:obsidian_glass", {
### Vitreo incorniciato
Questa opzione crea un solo bordo lungo tutto l'insieme di nodi, al posto di crearne più per singolo nodo.
Questa opzione crea un solo bordo lungo tutto l'insieme di nodi, al posto di
crearne più per singolo nodo.
<figure>
<img src="{{ page.root }}//static/drawtype_glasslike_framed.png" alt="Bordi vitrei incorniciati">
@ -103,11 +115,11 @@ Questa opzione crea un solo bordo lungo tutto l'insieme di nodi, al posto di cre
</figure>
```lua
core.register_node("default:glass", {
minetest.register_node("default:glass", {
description = "Vetro",
drawtype = "glasslike_framed",
tiles = {"default_glass.png", "default_glass_detail.png"},
inventory_image = core.inventorycube("default_glass.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},
@ -115,14 +127,14 @@ core.register_node("default:glass", {
})
```
Puoi inoltre usare il *drawtype* `glasslike_framed_optional` per permettere un opt-in all'utente.
Puoi inoltre usare il drawtype glasslike_framed_optional per permettere un opt-in all'utente.
## Nodi d'aria
I nodi d'aria (*airlike*) non sono renderizzati e perciò non hanno texture.
I nodi d'aria (airlike) non sono renderizzati e perciò non hanno texture.
```lua
core.register_node("miaaria:aria", {
minetest.register_node("miaaria:aria", {
description = "Mia Aria",
drawtype = "airlike",
paramtype = "light",
@ -143,20 +155,21 @@ core.register_node("miaaria:aria", {
## Luce e propagazione solare
La luce di un nodo è salvata in `param1`.
Per capire come ombreggiare il lato di un nodo, viene utilizzato il valore di luminosità dei nodi adiacenti.
La luce di un nodo è salvata in param1. Per capire come ombreggiare
il lato di un nodo, viene utilizzato il valore di luminosità dei nodi adiacenti.
Questo comporta un blocco della luce da parte dei nodi solidi.
Di base, non viene salvata la luce in nessun nodo né nelle sue istanze.
È invece solitamente preferibile farla passare in tipi quali quelli d'aria e vitrei.
Per fare ciò, ci sono due proprietà che devono essere definite:
È invece solitamente preferibile farla passare in tipi quali quelli d'aria e
vitrei. Per fare ciò, ci sono due proprietà che devono essere definite:
```lua
paramtype = "light",
sunlight_propagates = true,
```
La prima riga dice a `param1` di immagazzinare l'indice di luminosità, mentre la seconda permette alla luce del sole di propagarsi attraverso il nodo senza diminuire il proprio valore.
La prima riga dice a param1 di immagazzinare l'indice di luminosità.
La seconda riga invece permette alla luce del sole di propagarsi attraverso il nodo senza diminuire il suo valore.
## Nodi liquidi
@ -167,16 +180,17 @@ La prima riga dice a `param1` di immagazzinare l'indice di luminosità, mentre l
</figcaption>
</figure>
Ogni tipo di liquido richiede due definizioni di nodi: una per la sorgente e l'altra per il liquido che scorre.
Ogni tipo di liquido richiede due definizioni di nodi: una per la sorgente e l'altra
per il liquido che scorre.
```lua
-- Alcune proprietà sono state rimosse perché non
-- rilevanti per questo capitolo
core.register_node("default:water_source", {
minetest.register_node("default:water_source", {
drawtype = "liquid",
paramtype = "light",
inventory_image = core.inventorycube("default_water.png"),
inventory_image = minetest.inventorycube("default_water.png"),
-- ^ questo è necessario per impedire che l'immagine nell'inventario sia animata
tiles = {
@ -247,10 +261,11 @@ Guarda default:water_flowing nella mod default di minetest_game per un esempio c
</figcaption>
</figure>
I nodi complessi (*nodebox*) ti permettono di creare un nodo che non è cubico, bensì un insieme di più cuboidi.
I nodi complessi (nodebox) ti permettono di creare un nodo che non è cubico, bensì un insieme
di più cuboidi.
```lua
core.register_node("stairs:stair_stone", {
minetest.register_node("stairs:stair_stone", {
drawtype = "nodebox",
paramtype = "light",
node_box = {
@ -263,25 +278,30 @@ core.register_node("stairs:stair_stone", {
})
```
La parte più importante è la tabella `node_box`:
La parte più importable è la tabella node_box:
```lua
{-0.5, -0.5, -0.5, 0.5, 0, 0.5},
{-0.5, 0, 0, 0.5, 0.5, 0.5}
```
Ogni riga corrisponde a un cuboide e l'insieme delle righe forma il nodo complesso: i primi tre numeri sono le coordinate (da -0.5 a 0.5) dell'angolo davanti in basso a sinistra, mentre gli altri tre equivalgono all'angolo opposto.
Ogni riga corrisponde a un cuboide e l'insieme delle righe forma il nodo complesso:
i primi tre numeri sono le coordinate (da -0.5 a 0.5) dell'angolo davanti in basso a sinistra,
mentre gli altri tre equivalgono all'angolo opposto.
Essi sono in formato X, Y, Z, dove Y indica il sopra.
Puoi usare [NodeBoxEditor](https://forum.minetest.net/viewtopic.php?f=14&t=2840) per creare nodi complessi più facilmente, in quanto permette di vedere in tempo reale le modifiche sul nodo che si sta modellando.
Puoi usare [NodeBoxEditor](https://forum.minetest.net/viewtopic.php?f=14&t=2840) per
creare nodi complessi più facilmente, in quanto permette di vedere in tempo reale le modifiche
sul nodo che si sta modellando.
### Nodi complessi a muro
Certe volte si vogliono avere nodi complessi che cambiano a seconda della loro posizione sul pavimento, sul muro e sul soffitto, come le torce.
Certe volte si vogliono avere nodi complessi che cambiano a seconda della loro posizione
sul pavimento, sul muro e sul soffitto, come le torce.
```lua
core.register_node("default:sign_wall", {
minetest.register_node("default:sign_wall", {
drawtype = "nodebox",
node_box = {
type = "wallmounted",
@ -306,16 +326,18 @@ core.register_node("default:sign_wall", {
## Nodi mesh
Mentre i nodi complessi sono generalmente più semplici da fare, essi sono limitati in quanto possono essere composti solo da cuboidi.
I nodi complessi sono anche non ottimizzati: le facce interne, infatti, saranno comunque renderizzate, anche quando completamente nascoste.
Mentre i nodi complessi sono generalmente più semplici da fare, essi sono limitati
in quanto possono essere composti solo da cuboidi. I nodi complessi sono anche non ottimizzati:
le facce interne, infatti, saranno comunque renderizzate, anche quando completamente nascoste.
Una faccia è una superficie piatta di una mesh.
Una faccia interna appare quando le facce di due nodi complessi si sovrappongono, rendendo invisibili parti del modello ma renderizzandole comunque.
Una faccia è una superficie piatta di una mesh. Una faccia interna appare quando le
facce di due nodi complessi si sovrappongono, rendendo invisibili parti del modello
ma renderizzandole comunque.
Puoi registrare un nodo mesh come segue:
```lua
core.register_node("miamod:meshy", {
minetest.register_node("miamod:meshy", {
drawtype = "mesh",
-- Contiene le texture di ogni materiale
@ -329,18 +351,20 @@ core.register_node("miamod:meshy", {
```
Assicurati che la mesh sia presente nella cartella `models`.
La maggior parte delle volte la mesh dovrebbe essere nella cartella della tua mod, tuttavia è ok condividere una mesh fornita da un'altra mod dalla quale dipendi.
Per esempio, una mod che aggiunge più tipi di mobili potrebbe usfruire di un modello fornito da una mod di mobili base.
La maggior parte delle volte la mesh dovrebbe essere nella cartella della tua mod, tuttavia
è ok condividere una mesh fornita da un'altra mod dalla quale dipendi.
Per esempio, una mod che aggiunge più tipi di mobili potrebbe usfruire di un modello
fornito da una mod di mobili base.
## Nodi insegna
I nodi insegna (*signlike*) sono nodi piatti che possono essere affissi sulle facce di altri nodi.
I nodi insegna (signlike) sono nodi piatti che possono essere affissi sulle facce di altri nodi.
Al contrario del loro nome, i cartelli non rientrano nei nodi insegna bensì in quelli complessi, per fornire un effetto 3D.
I tipi insegna tuttavia, sono comunemente usati dalle scale a pioli.
Al contrario del loro nome, i cartelli non rientrano nei nodi insegna bensì nei nodi complessi,
per fornire un effetto 3D. I tipi insegna tuttavia, sono comunemente usati dalle scale a pioli.
```lua
core.register_node("default:ladder_wood", {
minetest.register_node("default:ladder_wood", {
drawtype = "signlike",
tiles = {"default_ladder_wood.png"},
@ -363,10 +387,10 @@ core.register_node("default:ladder_wood", {
</figcaption>
</figure>
I nodi pianta (*plantlike*) raffigurano la loro texture in un pattern a forma di X.
I nodi pianta raffigurano la loro texture in un pattern a forma di X.
```lua
core.register_node("default:papyrus", {
minetest.register_node("default:papyrus", {
drawtype = "plantlike",
-- Viene usata solo una texture
@ -381,7 +405,8 @@ core.register_node("default:papyrus", {
## Nodi fiamma
I nodi fiamma (*firelike*) sono simili ai pianta, ad eccezione del fatto che sono ideati per avvinghiarsi ai muri e ai soffitti.
I nodi fiamma (firelike) sono simili ai pianta, ad eccezione del fatto che sono
ideati per "avvinghiarsi" ai muri e ai soffitti.
<figure>
<img src="{{ page.root }}//static/drawtype_firelike.png" alt="Drawtype fiamma">
@ -391,7 +416,7 @@ I nodi fiamma (*firelike*) sono simili ai pianta, ad eccezione del fatto che son
</figure>
```lua
core.register_node("miamod:avvinghiatutto", {
minetest.register_node("miamod:avvinghiatutto", {
drawtype = "firelike",
-- Viene usata solo una texture
@ -401,7 +426,7 @@ core.register_node("miamod:avvinghiatutto", {
## Altri drawtype
Questa non è una lista esaustiva, in quanto ci sono infatti altri tipi di nodi come:
Questa non è una lista definitiva, ci sono infatti altri tipi di nodi come:
* Nodi staccionata
* Nodi pianta radicata - per quelle acquatiche
@ -410,4 +435,5 @@ Questa non è una lista esaustiva, in quanto ci sono infatti altri tipi di nodi
Le torce in Minetest Game usano in verità due diverse definizioni dei
nodi mesh (default:torch e default:torch_wall).
Come al solito, consulta la [documentazione sull'API Lua](https://minetest.gitlab.io/minetest/nodes/#node-drawtypes) per l'elenco completo.
Come al solito, consulta la [documentazione sull'API Lua](../../lua_api.html#node-drawtypes)
per l'elenco completo.

View File

@ -13,29 +13,34 @@ Saper registrare nuovi nodi, oggetti fabbricabili e conseguenti ricette, è un r
- [Cosa sono i nodi e gli oggetti?](#cosa-sono-i-nodi-e-gli-oggetti)
- [Registrare gli oggetti](#registrare-gli-oggetti)
- [Nomi oggetto](#nomi-oggetto)
- [Alias](#alias)
- [Nomi oggetto e alias](#nomi-oggetto-e-alias)
- [Texture](#texture)
- [Registrare un nodo base](#registrare-un-nodo-base)
- [Azioni e callback](#azioni-e-callback)
- [on_use](#onuse)
- [Fabbricazione](#fabbricazione)
- [Fisse (shaped)](#fisse-shaped)
- [Informi (shapeless)](#informi-shapeless)
- [Cottura (cooking) e Carburante (fuel)](#cottura-cooking-e-carburante-fuel)
- [Gruppi](#gruppi)
- [Strumenti, Capacità e Friabilità](#strumenti-capacità-e-friabilità)
- [Strumenti, Capacità e Friabilità](#strumenti-capacita-e-friabilita)
## Cosa sono i nodi e gli oggetti?
Nodi, oggetti fabbricabili e strumenti sono tutti oggetti.
Un oggetto è qualcosa che può essere trovato in un inventario — anche se potrebbe non risultare possibile durante una normale sessione di gioco.
Un oggetto è qualcosa che può essere trovato in un inventario —
anche se potrebbe non risultare possibile durante una normale sessione di gioco.
Un nodo è un oggetto che può essere piazzato o trovato nel mondo.
Ogni coordinata nel mondo deve essere occupata da un unico nodo — ciò che appare vuoto è solitamente un nodo d'aria.
Ogni coordinata nel mondo deve essere occupata da un unico nodo —
ciò che appare vuoto è solitamente un nodo d'aria.
Un oggetto fabbricabile (*craftitem*) non può essere invece piazzato, potendo apparire solo negli inventari o come oggetto rilasciato nel mondo.
Un oggetto fabbricabile (*craftitem*) non può essere invece piazzato, potendo apparire solo negli inventari
o come oggetto rilasciato nel mondo.
Uno strumento (*tool*) può usurarsi e solitamente non possiede la capacità di scavare.
In futuro, è probabile che gli oggetti fabbricabili e gli strumenti verranno fusi in un unico tipo, in quanto la distinzione fra di essi è alquanto artificiosa.
In futuro, è probabile che gli oggetti fabbricabili e gli strumenti verranno fusi in un unico tipo,
in quanto la distinzione fra di essi è alquanto artificiosa.
## Registrare gli oggetti
@ -43,62 +48,67 @@ Le definizioni degli oggetti consistono in un *nome oggetto* e una *tabella di d
La tabella di definizioni contiene attributi che influenzano il comportamento dell'oggetto.
```lua
core.register_craftitem("nomemod:nomeoggetto", {
minetest.register_craftitem("nomemod:nomeoggetto", {
description = "Il Mio Super Oggetto",
inventory_image = "nomemod_nomeoggetto.png"
})
```
### Nomi oggetto
### Nomi oggetto e alias
Ogni oggetto ha un nome usato per riferirsi a esso, che dovrebbe seguire la seguente struttura:
nomemod:nomeoggetto
`nomemod` equivale appunto al nome della mod che registra l'oggetto, e `nomeoggetto` è il nome che si vuole assegnare a quest'ultimo.
Esso dovrebbe essere inerente a quello che rappresenta e deve essere unico nella mod.
### Alias
Il nomemod equivale appunto al nome della mod che registra l'oggetto, e nomeoggetto è
il nome che si vuole assegnare a quest'ultimo.
Il nome dell'oggetto dovrebbe essere inerente a quello che rappresenta e deve essere unico nella mod.
Gli oggetti possono anche avere degli *alias* che puntano al loro nome.
Un *alias* è uno pseudonimo che dice al motore di gioco di trattarlo come se fosse il nome a cui punta.
Ciò è comunemente usato in due casi:
* Rinominare gli oggetti rimossi in qualcos'altro.
Ci potrebbero essere nodi sconosciuti nel mondo e negli inventari se un oggetto viene rimosso da una mod senza nessun codice per gestirlo.
* Aggiungere una scorciatoia.
`/giveme dirt` è più semplice di `/giveme default:dirt`.
Ci potrebbero essere nodi sconosciuti nel mondo e negli inventari se un oggetto
viene rimosso da una mod senza nessun codice per gestirlo.
È importante evitare di assegnare come alias nomi di nodi inottenibili già esistenti
al nodo rimosso, se quest'ultimo poteva essere ottenuto.
* Aggiungere una scorciatoia. `/giveme dirt` è più semplice di `/giveme default:dirt`.
Registrare un alias è alquanto semplice.
```lua
core.register_alias("dirt", "default:dirt")
```
Un buon modo per ricordarne il funzionamento è `da → a`, dove *da*
è l'alias e *a* è il nome dell'oggetto a cui punta.
Le mod devono inoltre assicurarsi di elaborare gli alias prima di occuparsi direttamente del nome dell'oggeto, in quanto l'engine non lo fa di suo.
Anche in questo caso non è difficile:
```lua
minetest.register_alias("dirt", "default:dirt")
```
Le mod devono assicurarsi di elaborare gli alias prima di occuparsi direttamente
del nome dell'oggeto, in quanto l'engine non lo fa di suo.
Ciò è comunque molto semplice:
```lua
itemname = core.registered_aliases[itemname] or itemname
itemname = minetest.registered_aliases[itemname] or itemname
```
### Texture
Per convenzione le texture andrebbero messe nella cartella textures/ con nomi che seguono la struttura `nomemod_nomeoggetto.png`.\\
Le immagini in JPEG sono supportate, ma non supportano la trasparenza e sono generalmente di cattiva qualità nelle basse risoluzioni.
Per convenzione le texture andrebbero messe nella cartella textures/ con nomi che seguono la struttura
`nomemod_nomeoggetto.png`.\\
Le immagini in JPEG sono supportate, ma non supportano la trasparenza e sono generalmente
di cattiva qualità nelle basse risoluzioni.
Si consiglia quindi il formato PNG.
Le texture su Minetest sono generalmente 16x16 pixel.
Possono essere di qualsiasi dimensione, ma è buona norma che rientrino nelle potenze di 2, per esempio 16, 32, 64 o 128.
Questo perché dimensioni differenti potrebbero non essere supportate dai vecchi dispositivi, comportando una diminuzione delle performance.
Possono essere di qualsiasi dimensione, ma è buona norma che rientrino nelle potenze di 2,
per esempio 16, 32, 64 o 128.
Questo perché dimensioni differenti potrebbero non essere supportate dai vecchi dispositivi,
comportando una diminuzione delle performance.
## Registrare un nodo base
```lua
core.register_node("miamod:diamante", {
minetest.register_node("miamod:diamante", {
description = "Diamante alieno",
tiles = {"miamod_diamante.png"},
is_ground_content = true,
@ -116,7 +126,7 @@ Per assegnarne invece di diverse, bisogna fornire il nome di 6 texture in quest'
Ricorda che su Minetest, come nella convenzione della computer grafica 3D, +Y punta verso l'alto.
```lua
core.register_node("miamod:diamante", {
minetest.register_node("miamod:diamante", {
description = "Diamante alieno",
tiles = {
"miamod_diamante_up.png", -- y+
@ -129,13 +139,57 @@ core.register_node("miamod:diamante", {
is_ground_content = true,
groups = {cracky = 3},
drop = "miamod:diamante_frammenti"
-- ^ Al posto di far cadere diamanti, fa cadere miamod:diamante_frammenti
-- ^ Al posto di rilasciare diamanti, rilascia miamod:diamante_frammenti
})
```
L'attributo is_ground_content è essenziale per ogni nodo che si vuole far apparire sottoterra durante la generazione della mappa.
L'attributo is_ground_content è essenziale per ogni nodo che si vuole far apparire sottoterra
durante la generazione della mappa.
Le caverne vengono scavate nel mondo dopo che tutti gli altri nodi nell'area sono stati generati.
## Azioni e callback
Minetest usa ampiamente una struttura per il modding incentrata sui callback (richiami).
I callback possono essere inseriti nella tabella di definizioni dell'oggetto per permettere una
risposta a vari tipi di eventi generati dall'utente.
### on_use
Di base, il callback on_use scatta quando un giocatore clicca col tasto sinistro con l'oggetto in mano.
Avere un callback sull'uso previene che l'oggetto venga utilizzato per scavare nodi.
Un utilizzo comune di questo callback è per il cibo:
```lua
minetest.register_craftitem("miamod:fangotorta", {
description = "Torta aliena di fango",
inventory_image = "miamod_fangotorta.png",
on_use = minetest.item_eat(20),
})
```
Il numero fornito alla funzione minetest.item_eat è il numero di punti salute ripristinati al
consumare il cibo.
In gioco ogni cuore equivale a due punti.
Un giocatore ha solitamente un massimo di 10 cuori, ovvero 20 punti salute.
Quest'ultimi non devono per forza essere interi, bensì possono anche essere decimali.
minetest.item_eat() è una funzione che ritorna un'altra funzione, in questo caso
quindi impostandola come callback di on_use.
Ciò significa che il codice in alto è alquanto simile al seguente:
```lua
minetest.register_craftitem("miamod:fangotorta", {
description = "Torta aliena di fango",
inventory_image = "miamod_fangotorta.png",
on_use = function(...)
return minetest.do_item_eat(20, nil, ...)
end,
})
```
Capendo come funziona item_eat, è possibile modificarlo per operazioni più complesse
come per esempio riprodurre un suono personalizzato.
## Fabbricazione
Ci sono diversi tipi di ricette di fabbricazione disponibili, indicate dalla proprietà `type`.
@ -146,15 +200,17 @@ Ci sono diversi tipi di ricette di fabbricazione disponibili, indicate dalla pro
* fuel - Definisce gli oggetti che possono alimentare il fuoco nella fornace.
* tool_repair - Definisce gli oggetti che possono essere riparati.
Le ricette di fabbricazione non sono oggetti, perciò non usano nomi oggetto per identificare in maniera univoca se stesse.
Le ricette di fabbricazione non sono oggetti, perciò non usano nomi oggetto per
identificare in maniera univoca se stesse
### Fisse (shaped)
Le ricette fisse avvengono quando gli ingredienti devono essere nella forma o sequenza corretta per funzionare.
Nell'esempio sotto, i frammenti necessitano di essere in una figura a forma di sedia per poter fabbricare appunto 99 sedie.
Le ricette fisse avvengono quando gli ingredienti devono essere nella forma o sequenza
corretta per funzionare. Nell'esempio sotto, i frammenti necessitano di essere in
in una figura a forma di sedia per poter fabbricare appunto 99 sedie.
```lua
core.register_craft({
minetest.register_craft({
type = "shaped",
output = "miamod:diamante_sedia 99",
recipe = {
@ -166,11 +222,12 @@ core.register_craft({
```
Una cosa da tener presente è la colonna vuota sulla parte destra.
Questo significa che ci *deve* essere una colonna vuota a destra della forma, altrimenti ciò non funzionerà.
Questo significa che ci *deve* essere una colonna vuota a destra della forma, altrimenti
ciò non funzionerà.
Se invece la colonna non dovesse servire, basta ometterla in questo modo:
```lua
core.register_craft({
minetest.register_craft({
output = "miamod:diamante_sedia 99",
recipe = {
{"miamod:diamante_frammenti", "" },
@ -180,14 +237,16 @@ core.register_craft({
})
```
Il campo type non è davvero necessario per le ricette fisse, in quanto sono il tipo di base.
Il campo type non è davvero necessario per le ricette fisse, in quanto sono
il tipo di base.
### Informi (shapeless)
Le ricette informi sono ricette che vengono usate quando non importa dove sono posizionati gli ingredienti, ma solo che ci siano.
Le ricette informi sono ricette che vengono usate quando non importa
dove sono posizionati gli ingredienti, ma solo che ci siano.
```lua
core.register_craft({
minetest.register_craft({
type = "shapeless",
output = "miamod:diamante 3",
recipe = {
@ -200,10 +259,12 @@ core.register_craft({
### Cottura (cooking) e carburante (fuel)
Le ricette di tipo "cottura" non vengono elaborate nella griglia di fabbricazione, bensì nelle fornaci o in qualsivoglia altro strumento di cottura che può essere trovato nelle mod.
Le ricette di tipo "cottura" non vengono elaborate nella griglia di fabbricazione,
bensì nelle fornaci o in qualsivoglia altro strumento di cottura che può essere trovato
nelle mod.
```lua
core.register_craft({
minetest.register_craft({
type = "cooking",
output = "miamod_diamante_frammenti",
recipe = "default:coalblock",
@ -211,29 +272,34 @@ core.register_craft({
})
```
L'unica vera differenza nel codice è che in questo la ricetta non è una tabella (tra parentesi graffe), bensì un singolo oggetto.
Le ricette di cottura dispongono anche di un parametro aggiuntivo "cooktime" che indica in secondi quanto tempo ci impiega l'oggetto a cuocersi.
L'unica vera differenza nel codice è che in questo la ricetta non è una tabella (tra parentesi graffe),
bensì un singolo oggetto.
Le ricette di cottura dispongono anche di un parametro aggiuntivo "cooktime"
che indica in secondi quanto tempo ci impiega l'oggetto a cuocersi.
Se non è impostato, di base è 3.
La ricetta qui sopra genera un'unità di frammenti di diamante dopo 10 secondi quando il blocco di carbone (`coalblock`) è nello slot di input, con un qualche tipo di carburante sotto di esso.
La ricetta qui sopra genera un'unità di frammenti di diamante dopo 10 secondi quando
il blocco di carbone (coalblock) è nello slot di input, con un qualche tipo di carburante sotto di esso.
Il tipo "carburante" invece funge da accompagnamento alle ricette di cottura, in quanto definisce cosa può alimentare il fuoco.
Il tipo "carburante" invece funge da accompagnamento alle ricette di cottura,
in quanto definisce cosa può alimentare il fuoco.
```lua
core.register_craft({
minetest.register_craft({
type = "fuel",
recipe = "miamod:diamante",
burntime = 300,
})
```
Esso non ha un output come le altre ricette, e possiede un tempo di arsura (`burntime`) che definisce in secondi per quanto alimenterà la fiamma.
In questo caso, 300 secondi!
Esso non ha un output come le altre ricette, e possiede un tempo di arsura (burntime)
che definisce in secondi per quanto alimenterà la fiamma. In questo caso, 300 secondi!
## Gruppi
Gli oggetti possono essere membri di più gruppi, e i gruppi possono avere più membri.
Essi sono definiti usando la proprietà `groups` nella tabella di definizione, e possiedono un valore associato.
Essi sono definiti usando la proprietà `groups` nella tabella di definizione,
e possiedono un valore associato.
```lua
groups = {cracky = 3, wood = 1}
@ -241,10 +307,11 @@ groups = {cracky = 3, wood = 1}
Ci sono diverse ragioni per cui usare i gruppi.
In primis, vengono utilizzati per descrivere proprietà come friabilità e infiammabilità.
In secundis, possono essere usati in una ricetta al posto di un nome oggetto per permettere a qualsiasi oggetto nel gruppo di essere utilizzato.
In secundis, possono essere usati in una ricetta al posto di un nome oggetto per permettere
a qualsiasi oggetto nel gruppo di essere utilizzato.
```lua
core.register_craft({
minetest.register_craft({
type = "shapeless",
output = "miamod:diamante_qualcosa 3",
recipe = {"group:wood", "miamod:diamante"}
@ -253,9 +320,11 @@ core.register_craft({
## Strumenti, Capacità e Friabilità
Le friabilità sono dei gruppi particolari utilizzati per definire la resistenza di un nodo quando scavato con un determinato strumento.
Le friabilità sono dei gruppi particolari utilizzati per definire la resistenza di un nodo
quando scavato con un determinato strumento.
Una friabilità elevata equivale a una maggior facilità e velocità nel romperlo.
È possibile combinarne di più tipi per permettere al nodo di essere distrutto da più tipi di strumento, mentre un nodo senza friabilità non può essere distrutto da nessuno strumento.
È possibile combinarne di più tipi per permettere al nodo di essere distrutto da più tipi di strumento.
Un nodo senza friabilità non può essere distrutto da nessuno strumento.
| Gruppo | Miglior strumento | Descrizione |
|---------|-------------------|-------------|
@ -268,14 +337,18 @@ Una friabilità elevata equivale a una maggior facilità e velocità nel romperl
| oddly_breakable_by_hand | *qualsiasi* | Torce e simili — molto veloci da rompere |
Ogni strumento possiede poi delle capacità (*capability*).
Una capacità include una lista di friabilità supportate, e proprietà associate per ognuna di esse come la velocità di scavata e il livello di usura.
Gli strumenti possono anche avere una durezza massima supportata per ogni tipo; ciò serve a prevenire che strumenti più deboli possano rompere nodi meno friabili.
È poi molto comune che uno strumento includa tutte le friabilità nelle sue capacità, con quelle meno adatte equivalenti a proprietà inefficienti.
Se l'oggetto impugnato dal giocatore non ha una capacità esplicitata, verrà allora usata quella della mano.
Ogni strumento possiede poi delle capacità (capability).
Una capacità include una lista di friabilità supportate, e proprietà associate
per ognuna di esse come la velocità di scavata e il livello di usura.
Gli strumenti possono anche avere una durezza massima supportata per ogni tipo;
ciò serve a prevenire che strumenti più deboli possano rompere nodi meno friabili.
È poi molto comune che uno strumento includa tutte le friabilità nelle sue capacità,
con quelle meno adatte equivalenti a proprietà inefficienti.
Se l'oggetto impugnato dal giocatore non ha una capacità esplicitata,
verrà allora usata quella della mano.
```lua
core.register_tool("miamod:strumento", {
minetest.register_tool("miamod:strumento", {
description = "Il mio strumento",
inventory_image = "miamod_strumento.png",
tool_capabilities = {
@ -292,5 +365,6 @@ core.register_tool("miamod:strumento", {
},
})
```
I gruppi limite (`groupcaps`) sono una lista delle friabilità supportate dallo strumento.
I gruppi di danno invece (`damage_groups`) servono a controllare come uno strumento (esterno) danneggia quell'oggetto. Quest'ultimi verranno discussi in seguito nel capitolo Oggetti, Giocatori e Entità.
I gruppi limite (groupcaps) sono una lista delle friabilità supportate dallo strumento.
I gruppi di danno invece (damage_groups) servono a controllare come uno strumento (esterno) danneggia
quell'oggetto. Quest'ultimi verranno discussi in seguito nel capitolo Oggetti, Giocatori e Entità.

View File

@ -7,7 +7,7 @@ description: Operazioni base come set_node e get_node
redirect_from: /it/chapters/environment.html
---
## Introduzione <!-- omit in toc -->
## Introduction <!-- omit in toc -->
In questo capitolo imparerai come eseguire semplici azioni sulla mappa.
@ -44,7 +44,7 @@ I Blocchi Mappa esistenti, tuttavia, ignorano questo limite quando caricati dal
Un nodo può essere letto da un mondo fornendone la posizione:
```lua
local nodo = core.get_node({ x = 1, y = 3, z = 4 })
local nodo = minetest.get_node({ x = 1, y = 3, z = 4 })
print(dump(nodo)) --> { name=.., param1=.., param2=.. }
```
@ -55,7 +55,7 @@ Se la posizione è un decimale, verrà arrotondata alle coordinate del nodo.
* `param1` - Guarda la definizione dei nodi. È solitamente associato alla luce.
* `param2` - Guarda la definizione dei nodi.
Per vedere se un nodo è caricato si può utilizzare `core.get_node_or_nil`, che ritornerà `nil` se il nome del nodo risulta `ignore`
Per vedere se un nodo è caricato si può utilizzare `minetest.get_node_or_nil`, che ritornerà `nil` se il nome del nodo risulta `ignore`
(la funzione non caricherà comunque il nodo).
Potrebbe comunque ritornare `ignore` se un blocco contiene effettivamente `ignore`: questo succede ai limiti della mappa.
@ -67,14 +67,11 @@ Le più frequenti sono quelle per trovare i nodi.
Per esempio, mettiamo che si voglia creare un certo tipo di pianta che cresce più velocemente vicino alla pietra;
si dovrebbe controllare che ogni nodo nei pressi della pianta sia pietra, e modificarne il suo indice di crescita di conseguenza.
`core.find_node_near` ritornerà il primo nodo trovato in un dato raggio, combaciante con le informazioni passategli (nomi di nodi o gruppi).
Nell'esempio che segue, andiamo alla ricerca di un nodo di mese nel raggio di 5 nodi:
```lua
local vel_crescita = 1
local pos_nodo = core.find_node_near(pos, 5, { "default:stone" })
local pos_nodo = minetest.find_node_near(pos, 5, { "default:stone" })
if pos_nodo then
core.chat_send_all("Nodo trovato a: " .. dump(pos_nodo))
minetest.chat_send_all("Nodo trovato a: " .. dump(pos_nodo))
vel_crescita = 2
end
```
@ -86,32 +83,30 @@ Si dovrebbe quindi usare una funzione in grado di trovare più nodi in un'area:
local pos1 = vector.subtract(pos, { x = 5, y = 5, z = 5 })
local pos2 = vector.add(pos, { x = 5, y = 5, z = 5 })
local lista_pos =
core.find_nodes_in_area(pos1, pos2, { "default:stone" })
minetest.find_nodes_in_area(pos1, pos2, { "default:stone" })
local vel_crescita = 1 + #lista_pos
```
Il codice qui in alto ritorna il numero di nodi in un *volume cuboidale*.
Il che è diverso da usare `find_node_near`, il quale usa la distanza dalla posizione data (cioé una *sfera*).
Per ovviare a ciò, bisogna controllare l'intervallo manualmente.
Il codice qui in alto, tuttavia, non fa proprio quello che ci serve, in quanto fa controlli basati su un'area, mentre `find_node_near` li fa su un intervallo.
Per ovviare a ciò, bisogna purtroppo controllare l'intervallo manualmente.
```lua
local pos1 = vector.subtract(pos, { x = 5, y = 5, z = 5 })
local pos2 = vector.add(pos, { x = 5, y = 5, z = 5 })
local lista_pos =
core.find_nodes_in_area(pos1, pos2, { "default:stone" })
minetest.find_nodes_in_area(pos1, pos2, { "default:stone" })
local vel_crescita = 1
for i=1, #lista_pos do
local delta = vector.subtract(lista_pos[i], pos)
if delta.x*delta.x + delta.y*delta.y + delta.z*delta.z <= 5*5 then
if delta.x*delta.x + delta.y*delta.y <= 5*5 then
vel_crescita = vel_crescita + 1
end
end
```
Ora il codice aumenterà correttamente `vel_crescita` basandosi su quanti nodi di pietra ci sono in un intervallo.
Notare come si sia comparata la distanza al quadrato dalla posizione, invece che calcolarne la radice quadrata per ottenerne la distanza vera e propria.
Questo perché i computer trovano le radici quadrate computazionalmente pesanti, quindi dovrebbero essere evitate il più possibile.
Questo perché i computer trovano le radici quadrate computazionalmente pesanti, quindi dovresti evitare di usarle il più possibile.
Ci sono altre variazioni delle due funzioni sopracitate, come `find_nodes_with_meta` e `find_nodes_in_area_under_air`, che si comportano in modo simile e sono utili in altre circostanze.
@ -120,23 +115,23 @@ Ci sono altre variazioni delle due funzioni sopracitate, come `find_nodes_with_m
### Scrittura dei nodi
Puoi usare `set_node` per sovrascrivere nodi nella mappa.
Ogni chiamata a `set_node` ricalcolerà la luce e richiamerà i suoi callback, il che significa che `set_node` è alquanto lento quando usato su un elevato numero di nodi.
Ogni chiamata a set_node ricalcolerà la luce, il ché significa che set_node è alquanto lento quando usato su un elevato numero di nodi.
```lua
core.set_node({ x = 1, y = 3, z = 4 }, { name = "default:stone" })
minetest.set_node({ x = 1, y = 3, z = 4 }, { name = "default:stone" })
local nodo = core.get_node({ x = 1, y = 3, z = 4 })
local nodo = minetest.get_node({ x = 1, y = 3, z = 4 })
print(nodo.name) --> default:stone
```
`set_node` rimuoverà ogni metadato e inventario associato a quel nodo: ciò non è sempre desiderabile, specialmente se si stanno usando
set_node rimuoverà ogni metadato e inventario associato a quel nodo: ciò non è sempre desiderabile, specialmente se si stanno usando
più definizioni di nodi per rappresentarne concettualmente uno. Un esempio è il nodo fornace: per quanto lo si immagini come un nodo unico,
sono in verità due.
Si può impostare un nuovo nodo senza rimuoverne metadati e inventario con `swap_node`:
```lua
core.swap_node({ x = 1, y = 3, z = 4 }, { name = "default:stone" })
minetest.swap_node({ x = 1, y = 3, z = 4 }, { name = "default:stone" })
```
### Rimozione dei nodi
@ -146,15 +141,15 @@ Un nodo deve sempre essere presente. Per rimuoverlo, basta impostarlo uguale a `
Le seguenti due linee di codice sono equivalenti, rimuovendo in entrambi i casi il nodo:
```lua
core.remove_node(pos)
core.set_node(pos, { name = "air" })
minetest.remove_node(pos)
minetest.set_node(pos, { name = "air" })
```
Infatti, `remove_node` non fa altro che richiamare `set_node` con nome `air`.
## Caricamento blocchi
Puoi usare `core.emerge_area` per caricare i blocchi mappa.
Puoi usare `minetest.emerge_area` per caricare i blocchi mappa.
Questo comando è asincrono, ovvero i blocchi non saranno caricati istantaneamente; al contrario, verranno caricati man mano e il callback associato sarà richiamato a ogni passaggio.
```lua
@ -164,7 +159,7 @@ local pos1 = vector.subtract(pos, mezza_dimensione)
local pos2 = vector.add (pos, mezza_dimensione)
local param = {} -- dati persistenti tra un callback e l'altro
core.emerge_area(pos1, pos2, mio_callback, param)
minetest.emerge_area(pos1, pos2, mio_callback, param)
```
Minetest chiamerà la funzione locale definita qua sotto `mio_callback` ogni volta che carica un blocco, con delle informazioni sul progresso.
@ -183,12 +178,12 @@ local function mio_callback(pos, action,
-- Invia messaggio indicante il progresso
if param.blocchi_totali == param.blocchi_caricati then
core.chat_send_all("Ho finito di caricare blocchi!")
else
minetest.chat_send_all("Ho finito di caricare blocchi!")
end
local percentuale = 100 * param.blocchi_caricati / param.blocchi_totali
local msg = string.format("Caricamento blocchi %d/%d (%.2f%%)",
param.blocchi_caricati, param.blocchi_totali, percentuale)
core.chat_send_all(msg)
minetest.chat_send_all(msg)
end
end
```
@ -205,7 +200,7 @@ local mezza_dimensione = { x = 10, y = 10, z = 10 }
local pos1 = vector.subtract(pos, mezza_dimensione)
local pos2 = vector.add (pos, mezza_dimensione)
core.delete_area(pos1, pos2)
minetest.delete_area(pos1, pos2)
```
Questo cancellerà tutti i blocchi mappa in quell'area, anche quelli solo parzialmente selezionati.

View File

@ -1,5 +1,5 @@
---
title: Oggetti, giocatori ed entità
title: Oggetti, giocatori e entità
layout: default
root: ../..
idx: 3.4
@ -15,14 +15,10 @@ degrad:
In questo capitolo imparerai come manipolare gli oggetti e come definirne di tuoi.
- [Cosa sono gli oggetti, i giocatori e le entità?](#cosa-sono-gli-oggetti-i-giocatori-e-le-entità)
- [Posizione e velocità](#posizione-e-velocità)
- [Proprietà degli oggetti](#proprietà-degli-oggetti)
- [Entità](#entità)
- [Salute e danno](#salute-e-danno)
- [Punti vita (HP)](#punti-vita-hp)
- [Pugni, Gruppi Danno e Gruppi Armatura](#pugni-gruppi-danno-e-gruppi-armatura)
- [Esempi di calcolo del danno](#esempi-di-calcolo-del-danno)
- [Cosa sono gli oggetti, i giocatori e le entità?](#cosa-sono-gli-oggetti-i-giocatori-e-le-entita)
- [Posizione e velocità](#posizione-e-velocita)
- [Proprietà degli oggetti](#proprieta-degli-oggetti)
- [Entità](#entita)
- [Oggetti figli](#oggetti-figli)
- [Il tuo turno](#il-tuo-turno)
@ -43,7 +39,7 @@ Questa distinzione è resa meno chiara dal fatto che le entità sono controllate
`get_pos` e `set_pos` permettono di ottenere e impostare la posizione di un oggetto.
```lua
local giocatore = core.get_player_by_name("bob")
local giocatore = minetest.get_player_by_name("bob")
local pos = giocatore:get_pos()
giocatore:set_pos({ x = pos.x, y = pos.y + 1, z = pos.z })
```
@ -90,6 +86,18 @@ Come mostrato nella prossima sezione, le entità possono avere delle proprietà
Un'entità ha una tabella di definizione che ricorda quella degli oggetti (intesi come *items*).
Questa tabella può contenere metodi di callback, proprietà iniziali e membri personalizzati.
Tuttavia, c'è una differenza sostanziale tra le due; perché quando un'entità appare (come quando viene creata o caricata) una nuova tabella viene generata per quell'entità, *ereditando* le proprietà dalla tabella originaria tramite una metatabella.
Ci si riferisce solitamente a questa nuova tabella con Tabella di Entità Lua e può essere usata per immagazzinare variabili per quella specifica entità.
Le metatabelle rappresentano un aspetto importante di Lua, che bisogna tenere bene a mente in quanto sono una parte essenziale del linguaggio.
In parole povere, le metatabelle permettono di controllare come si comporta una tabella quando viene usata una certa sintassi in Lua.
Vengono usate soprattutto per la loro abilità di usare un'altra tabella come prototipo, fungendo da valori di base di quest'ultima quando essa non contiene le proprietà e i metodi richiesti.
Mettiamo che si voglia accedere al campo X della tabella A.
Se la tabella A ha quel campo, allora ritornerà normalmente.
Tuttavia, se X non esiste ma esiste una metatabella B associata ad A, B verrà ispezionata alla ricerca di un eventuale X prima di ritornare (eventualmente) nil.
```lua
local MiaEntita = {
initial_properties = {
@ -112,29 +120,16 @@ function MiaEntita:imposta_messaggio(msg)
end
```
Tuttavia, c'è una differenza sostanziale tra entità e oggetti; perché quando un'entità appare (come quando viene creata o caricata) una nuova tabella viene generata per quell'entità, *ereditando* le proprietà dalla tabella originaria tramite una metatabella.
<!--
Questa eredità avviene usando una metatabella. Le metatabelle rappresentano un aspetto importante di Lua, che bisogna tenere bene a mente in quanto sono una parte essenziale del linguaggio.
In parole povere, le metatabelle permettono di controllare come si comporta una tabella quando viene usata una certa sintassi in Lua.
Vengono usate soprattutto per la loro abilità di usare un'altra tabella come prototipo, fungendo da valori di base di quest'ultima quando essa non contiene le proprietà e i metodi richiesti.
Mettiamo che si voglia accedere al campo `x` della tabella `a` (`a.x`).
Se la tabella `a` ha quel campo, allora ritornerà normalmente.
Tuttavia, se `a.x` non esiste ma esiste una metatabella `b` associata ad `a`, `b` verrà ispezionata alla ricerca di un eventuale `b.x` da ritornare al posto di `nil`.
-->
Sia la tabella di un ObjectRef che quella di un'entità forniscono modi per ottenerne la controparte:
```lua
local entita = oggetto:get_luaentity()
local oggetto = entita.object
print("L'entità si trova a " .. core.pos_to_string(oggetto:get_pos()))
print("L'entità si trova a " .. minetest.pos_to_string(oggetto:get_pos()))
```
Ci sono diversi callback disponibili da usare per le entità.
Una lista completa può essere trovata in [lua_api.md](https://minetest.gitlab.io/minetest/minetest-namespace-reference/#registered-definition-tables).
Una lista completa può essere trovata in [lua_api.txt]({{ page.root }}/lua_api.html#registered-entities).
```lua
function MiaEntita:on_step(dtime)
@ -142,9 +137,9 @@ function MiaEntita:on_step(dtime)
local pos_giu = vector.subtract(pos, vector.new(0, 1, 0))
local delta
if core.get_node(pos_giu).name == "air" then
if minetest.get_node(pos_giu).name == "air" then
delta = vector.new(0, -1, 0)
elseif core.get_node(pos).name == "air" then
elseif minetest.get_node(pos).name == "air" then
delta = vector.new(0, 0, 1)
else
delta = vector.new(0, 1, 0)
@ -156,7 +151,7 @@ function MiaEntita:on_step(dtime)
end
function MiaEntita:on_punch(hitter)
core.chat_send_player(hitter:get_player_name(), self.message)
minetest.chat_send_player(hitter:get_player_name(), self.message)
end
```
@ -167,14 +162,14 @@ Questo succede nella *Staticdata*, una stringa che contiene tutte le informazion
```lua
function MiaEntita:get_staticdata()
return core.write_json({
return minetest.write_json({
messaggio = self.messaggio,
})
end
function MiaEntita:on_activate(staticdata, dtime_s)
if staticdata ~= "" and staticdata ~= nil then
local data = core.parse_json(staticdata) or {}
local data = minetest.parse_json(staticdata) or {}
self:imposta_messaggio(data.messaggio)
end
end
@ -190,14 +185,14 @@ Questo significa che il suo staticdata inizialmente potrebbe essere vuoto (dato
Infine, c'è bisogno di registrare la tabella usando `register_entity`.
```lua
core.register_entity("miamod:entita", MiaEntita)
minetest.register_entity("miamod:entita", MiaEntita)
```
L'entità può essere spawnata da una mod nel seguente modo:
```lua
local pos = { x = 1, y = 2, z = 3 }
local oggetto = core.add_entity(pos, "miamod:entita", nil)
local oggetto = minetest.add_entity(pos, "miamod:entita", nil)
```
Il terzo parametro è lo staticdata inziale.
@ -207,82 +202,6 @@ Per impostare il messaggio, puoi usare la Tabella di Entità Lua:
oggetto:get_luaentity():imposta_messaggio("ciao!")
```
## Salute e danno
### Punti vita (HP)
Ogni oggetto ha un valore Punti Vita (HP), che rappresenta la salute attuale.
Nei giocatori è inoltre possibile impostare il valore di salute massima tramite la proprietà `hp_max`.
Al raggiungere gli 0 HP, un oggetto muore.
```lua
local hp = oggetto:get_hp()
oggetto:set_hp(hp + 3)
```
### Pugni, Gruppi Danno e Gruppi Armatura
Il danno è la riduzione degli HP di un oggetto.
Quest'ultimo può prendere "a pugni" un altro oggetto per infliggere danno.
"A pugni" perché non si parla necessariamente di un pugno vero e proprio: può essere infatti un'esplosione, un fendente, e via dicendo.
Il danno complessivo è calcolato moltiplicando i Gruppi Danno del pugno con le vulnerabilità dell'obiettivo.
Questo è poi eventualmente ridotto a seconda di quanto recente è stato il colpo precedente.
Vedremo tra poco nel dettaglio quest'aspetto.
Proprio come i [Gruppi Danno dei nodi](../items/nodes_items_crafting.html#strumenti-capacità-e-friabilità), questi gruppi possono prendere qualsiasi nome e non necessitano di essere registrati.
Tuttavia, si è soliti usare gli stessi nomi di quelli dei nodi.
La vulnerabilità di un oggetto a un certo tipo di danno dipende dalla sua [proprietà](#proprietà-degli-oggetti) `armor_groups`.
Al contrario di quello che potrebbe far intendere il nome, `armor_groups` specifica la percentuale di danno subita da specifici Gruppi Danno, e non la resistenza.
Se un Gruppo Danno non è elencato nei Gruppi Armatura di un oggetto, quest'ultimo ne sarà completamente immune.
```lua
obiettivo:set_armor_groups({
fleshy = 90,
crumbly = 50,
})
```
Nell'esempio qui sopra, l'oggetto subirà il 90% di danno `fleshy` e 50% di quello `crumbly`.
Quando un giocatore prende "a pugni" un oggetto, i Gruppi Danno vengono estrapolati dall'oggetto che ha attualmente il mano.
Negli altri casi, saranno le mod a decidere quali Gruppi Danno usare.
### Esempi di calcolo del danno
Prendiamo a pugni l'oggetto `target`:
```lua
local capacita_oggetto = {
full_punch_interval = 0.8,
damage_groups = { fleshy = 5, choppy = 10 },
-- Questo è usato solo per scavare nodi, ma è comunque richiesto
max_drop_level=1,
groupcaps={
fleshy={times={[1]=2.5, [2]=1.20, [3]=0.35}, uses=30, maxlevel=2},
},
}
local tempo_da_ultimo_pugno = capacita_oggetto.full_punch_interval
obiettivo:punch(oggetto, tempo_da_ultimo_pugno, capacita_oggetto)
```
Ora, calcoliamo a quanto ammonterà il danno.
I Gruppi Danno del pugno sono `fleshy=5` e `choppy=10`, con l'obiettivo che prenderà 90% di danno da fleshy e 0% da choppy.
Per prima cosa, moltiplichiamo i Gruppi Danno per le vulnerabilità, e ne sommiamo il risultato.
Poi, moltiplichiamo per un numero tra 0 e 1 a seconda di `tempo_da_ultimo_pugno`.
```lua
= (5*90/100 + 10*0/100) * limit(tempo_da_ultimo_pugno / full_punch_interval, 0, 1)
= (5*90/100 + 10*0/100) * 1
= 4.5
```
Dato che HP è un intero, il danno è arrotondato a 5 punti.
## Oggetti figli
Gli oggetti figli (*attachments*) si muovono quando il genitore - l'oggetto al quale sono legati - viene mosso.

View File

@ -9,19 +9,19 @@ redirect_from:
- /it/map/node_metadata.html
---
## Introduzione <!-- omit in toc -->
## Introduction <!-- omit in toc -->
In questo capitolo imparerai i vari modi per immagazzinare dati.
- [Metadati](#metadati)
- [Cos'è un metadato?](#cosè-un-metadato)
- [Cos'è un metadato?](#cos-e-un-metadato)
- [Ottenere i metadati di un oggetto](#ottenere-i-metadati-di-un-oggetto)
- [Lettura e scrittura](#lettura-e-scrittura)
- [Chiavi speciali](#chiavi-speciali)
- [Immagazzinare tabelle](#immagazzinare-tabelle)
- [Metadati privati](#metadati-privati)
- [Tabelle Lua](#tabelle-lua)
- [Storaggio Mod](#storaggio-mod)
- [Storaggio mod](#storaggio-mod)
- [Database](#database)
- [Decidere quale usare](#decidere-quale-usare)
- [Il tuo turno](#il-tuo-turno)
@ -47,7 +47,7 @@ Il dato in sé, come il tipo di un nodo o la quantità di un ItemStack, non rien
Se si conosce la posizione di un nodo, si possono ottenere i suoi metadati:
```lua
local meta = core.get_meta({ x = 1, y = 2, z = 3 })
local meta = minetest.get_meta({ x = 1, y = 2, z = 3 })
```
Quelli dei giocatori e degli ItemStack invece sono ottenuti tramite `get_meta()`:
@ -87,7 +87,7 @@ print(meta:get_string("count")) --> "3"
Questo è utile, per esempio, per mostrare lo stato o il proprietario di un nodo.
`description` è usato negli ItemStack per sovrascrivere la descrizione al passare il mouse sopra l'oggetto in un formspec (come l'inventario, li vedremo più avanti).
È possibile utilizzare `core.colorize()` per cambiarne il colore.
È possibile utilizzare `minetest.colorize()` per cambiarne il colore.
`owner` è una chiave comune, usata per immagazzinare il nome del giocatore a cui appartiene l'oggetto o il nodo.
@ -100,14 +100,14 @@ Quello in Lua tende a essere molto più veloce e corrisponde al formato usato da
```lua
local data = { username = "utente1", score = 1234 }
meta:set_string("foo", core.serialize(data))
meta:set_string("foo", minetest.serialize(data))
data = core.deserialize(meta:get_string("foo"))
data = minetest.deserialize(minetest:get_string("foo"))
```
### Metadati privati
Di base, tutti i metadati dei nodi sono inviati al client. Rendendo le loro chiavi private, questo invece non succede.
Le voci nei metadati di un nodo possono essere segnate come private, senza venire quindi inviate al client (al contrario delle normali).
```lua
meta:set_string("segreto", "asd34dn")
@ -130,7 +130,7 @@ Lo spazio d'archiviazione della mod (*storage*) usa la stessa identica API dei m
Il primo infatti è per mod, e può essere ottenuto solo durante l'inizializzazione - appunto - della mod.
```lua
local memoria = core.get_mod_storage()
local memoria = minetest.get_mod_storage()
```
Nell'esempio è ora possibile manipolare lo spazio d'archiviazione come se fosse un metadato:
@ -148,10 +148,10 @@ Dovresti rendere ciò opzionale, separando il come i dati vengono salvati e il d
local backend
if use_database then
backend =
dofile(core.get_modpath("miamod") .. "/backend_sqlite.lua")
dofile(minetest.get_modpath("miamod") .. "/backend_sqlite.lua")
else
backend =
dofile(core.get_modpath("miamod") .. "/backend_storage.lua")
dofile(minetest.get_modpath("miamod") .. "/backend_storage.lua")
end
backend.get_foo("a")
@ -161,15 +161,15 @@ backend.set_foo("a", { score = 3 })
Il file `backend_storage.lua` dell'esempio (puoi nominarlo come vuoi) dovrebbe includere l'implementazione del metodo di storaggio:
```lua
local memoria = core.get_mod_storage()
local memoria = minetest.get_mod_storage()
local backend = {}
function backend.set_foo(key, value)
memoria:set_string(key, core.serialize(value))
memoria:set_string(key, minetest.serialize(value))
end
function backend.get_foo(key)
return core.deserialize(memoria:get_string(key))
function backend.get_foo(key, value)
return minetest.deserialize(memoria:get_string(key))
end
return backend
@ -182,7 +182,7 @@ Un ambiente non sicuro è una tabella disponibile solamente alle mod con accesso
Gli ambienti non sicuri saranno trattati più nel dettaglio nel capitolo sulla [Sicurezza](../quality/security.html).
```lua
local amb_nonsicuro = core.request_insecure_environment()
local amb_nonsicuro = minetest.request_insecure_environment()
assert(amb_nonsicuro, "Per favore aggiungi miamod a secure.trusted_mods nelle impostazioni")
local _sql = amb_nonsicuro.require("lsqlite3")
@ -214,4 +214,4 @@ Si prestano bene per i grandi ammontare di dati.
## Il tuo turno
* Crea un nodo che sparisce dopo essere stato colpito cinque volte.
(Usa `on_punch` nella definizione del nodo e `core.set_node`)
(Usa `on_punch` nella definizione del nodo e `minetest.set_node`)

View File

@ -32,25 +32,34 @@ A ogni nodo è associato un timer.
Questi timer possono essere gestiti ottenendo un oggetto NodeTimerRef (quindi un riferimento, come già visto per gli inventari).
```lua
local timer = core.get_node_timer(pos)
local timer = minetest.get_node_timer(pos)
timer:start(10.5) -- in secondi
```
Nell'esempio sottostante controlliamo che un timer sia attivo (`is_started()`), da quanto (`get_elapsed()`), quanto manca (`get_timeout()`) e infine lo fermiamo (`stop()`)
```lua
if timer:is_started() then
print("Il timer sta andando, e gli rimangono " .. timer:get_timeout() .. " secondi!")
print("Sono passati " .. timer:get_elapsed() .. " secondi")
end
timer:stop()
```
Quando un timer raggiunge lo zero, viene eseguito il metodo `on_timer`, che va dichiarato dentro la tabella di definizione del nodo.
`on_timer` richiede un solo parametro, ovvero la posizione del nodo.
```lua
core.register_node("porteautomatiche:porta_aperta", {
minetest.register_node("porteautomatiche:porta_aperta", {
on_timer = function(pos)
core.set_node(pos, { name = "porteautomatiche:porta_chiusa" })
minetest.set_node(pos, { name = "porteautomatiche:porta_chiusa" })
return false
end
})
```
Ritornando `true`, il timer ripartirà (con la stessa durata di prima).
È inoltre possibile usare `get_node_timer(pos)` all'interno di `on_timer`, basta assicurarsi di ritornare `false` per evitare conflitti.
Ritornando true, il timer ripartirà (con la stessa durata di prima).
Potresti aver tuttavia notato una limitazione: per questioni di ottimizzazione, infatti, è possibile avere uno e un solo timer per tipo di nodo, e solo un timer attivo per nodo.
@ -60,15 +69,15 @@ Potresti aver tuttavia notato una limitazione: per questioni di ottimizzazione,
Erba aliena, a scopo illustrativo del capitolo, è un tipo d'erba che ha una probabilità di apparire vicino all'acqua.
```lua
core.register_node("alieni:erba", {
minetest.register_node("alieni:erba", {
description = "Erba Aliena",
light_source = 3, -- Il nodo irradia luce. Min 0, max 14
tiles = {"alieni_erba.png"},
groups = {choppy=1},
on_use = core.item_eat(20)
on_use = minetest.item_eat(20)
})
core.register_abm({
minetest.register_abm({
nodenames = {"default:dirt_with_grass"}, -- nodo sul quale applicare l'ABM
neighbors = {"default:water_source", "default:water_flowing"}, -- nodi che devono essere nei suoi dintorni (almeno uno)
interval = 10.0, -- viene eseguito ogni 10 secondi
@ -76,7 +85,7 @@ core.register_abm({
action = function(pos, node, active_object_count,
active_object_count_wider)
local pos = {x = pos.x, y = pos.y + 1, z = pos.z}
core.set_node(pos, {name = "alieni:erba"})
minetest.set_node(pos, {name = "alieni:erba"})
end
})
```

View File

@ -8,18 +8,16 @@ redirect_from: /it/chapters/chat.html
cmd_online:
level: warning
title: I giocatori offline possono eseguire comandi
message: |
Viene passato il nome del giocatore al posto del giocatore in sé perché le mod possono eseguire comandi in vece di un giocatore offline.
Per esempio, il ponte IRC permette ai giocatori di eseguire comandi senza dover entrare in gioco.
message: <p>Viene passato il nome del giocatore al posto del giocatore in sé perché le mod possono eseguire comandi in vece di un giocatore offline.
Per esempio, il bridge IRC permette ai giocatori di eseguire comandi senza dover entrare in gioco.</p>
Assicurati quindi di non dar per scontato che un giocatore sia connesso.
Puoi controllare ciò tramite `core.get_player_by_name`, per vedere se ritorna qualcosa o meno.
<p>Assicurati quindi di non dar per scontato che un giocatore sia connesso.
Puoi controllare ciò tramite <pre>minetest.get_player_by_name</pre>, per vedere se ritorna qualcosa o meno.</p>
cb_cmdsprivs:
level: warning
title: Privilegi e comandi
message: |
Il privilegio shout non è necessario per far sì che un giocatore attivi questo richiamo.
message: Il privilegio shout non è necessario per far sì che un giocatore attivi questo callback.
Questo perché i comandi sono implementati in Lua, e sono semplicemente dei messaggi in chat che iniziano con /.
---
@ -28,23 +26,18 @@ cb_cmdsprivs:
Le mod possono interagire con la chat del giocatore, tra l'inviare messaggi, intercettarli e registrare dei comandi.
- [Inviare messaggi](#inviare-messaggi)
- [A tutti i giocatori](#a-tutti-i-giocatori)
- [A giocatori specifici](#a-giocatori-specifici)
- [Inviare messaggi a tutti i giocatori](#inviare-messaggi-a-tutti-i-giocatori)
- [Inviare messaggi a giocatori specifici](#inviare-messaggi-a-giocatori-specifici)
- [Comandi](#comandi)
- [Accettare più argomenti](#accettare-più-argomenti)
- [Usare string.split](#usare-stringsplit)
- [Usare i pattern Lua](#usare-i-pattern-lua)
- [Intercettare i messaggi](#intercettare-i-messaggi)
- [Complex Subcommands](#complex-subcommands)
- [Intercettare i messaggi](#interecettare-i-messaggi)
## Inviare messaggi
### A tutti i giocatori
## Inviare messaggi a tutti i giocatori
Per inviare un messaggio a tutti i giocatori connessi in gioco, si usa la funzione `chat_send_all`:
```lua
core.chat_send_all("Questo è un messaggio visualizzabile da tutti")
minetest.chat_send_all("Questo è un messaggio visualizzabile da tutti")
```
Segue un esempio di come apparirerebbe in gioco:
@ -55,12 +48,12 @@ Segue un esempio di come apparirerebbe in gioco:
Il messaggio appare su una nuova riga, per distinguerlo dai messaggi dei giocatori.
### A giocatori specifici
## Inviare messaggi a giocatori specifici
Per inviare un messaggio a un giocatore in particolare, si usa invece la funzione `chat_send_player`:
```lua
core.chat_send_player("Tizio", "Questo è un messaggio per Tizio")
minetest.chat_send_player("Tizio", "Questo è un messaggio per Tizio")
```
Questo messaggio viene mostrato esattamente come il precedente, ma solo, in questo caso, a Tizio.
@ -70,7 +63,7 @@ Questo messaggio viene mostrato esattamente come il precedente, ma solo, in ques
Per registrare un comando, per esempio `/foo`, si usa `register_chatcommand`:
```lua
core.register_chatcommand("foo", {
minetest.register_chatcommand("foo", {
privs = {
interact = true,
},
@ -82,53 +75,24 @@ core.register_chatcommand("foo", {
Nel codice qui in alto, `interact` è elencato come [privilegio](privileges.html) necessario; in altre parole, solo i giocatori che hanno quel privilegio possono usare il comando.
`param` è una stringa contenente tutto ciò che un giocatore scrive dopo il nome del comando.
Per esempio, in `/grantme uno,due,tre`, `param` equivarrà a `uno,due,tre`.
I comandi ritornano un massimo di due valori, dove il primo è un booleano che indica l'eventuale successo, mentre il secondo è un messaggio da inviare all'utente.
{% include notice.html notice=page.cmd_online %}
## Sottocomandi complessi
### Accettare più argomenti
È spesso necessario creare dei comandi complessi, come per esempio:
Non è raro che i comandi richiedano più argomenti, come per esempio `/squadra entra <nome_squadra>`.
Ci sono due modi per implementare ciò: usare `string.split` o i pattern Lua.
* `/msg <a> <messaggio>`
* `/team entra <nometeam>`
* `/team esci <nometeam>`
* `/team elenco`
#### Usare string.split
Una stringa può essere spezzettata in più parti tramite `string.split(" ")`:
Questo viene solitamente reso possibile dai [pattern di Lua](https://www.lua.org/pil/20.2.html).
I pattern sono un modo di estrapolare "cose" dal testo usando delle regole ben precise.
```lua
local parti = param:split(" ")
local cmd = parti[1]
if cmd == "entra" then
local nome_squadra = parti[2]
squadra.entra(name, nome_squadra)
return true, "Sei dentro la squadra!"
elseif cmd == "gioc_max" then
local nome_squadra = parti[2]
local gioc_max = tonumber(parti[3])
if nome_squadra and gioc_max then
return true, "Giocatori massimi della squadra " .. nome_squadra .. " impostati a " .. gioc_max
else
return false, "Uso: /squadra gioc_max <nome_squadra> <numero>"
end
else
return false, "È necessario un comando"
end
```
#### Usare i pattern Lua
[I pattern Lua](https://www.lua.org/pil/20.2.html) sono un modo per estrapolare pezzi di testo seguendo delle regole.
Sono perfetti in caso di argomenti che contengono spazi, o comunque quando è richiesto un controllo più meticoloso dei parametri catturati.
```lua
local a, msg = string.match(param, "^([%a%d_-]+) (.+)$")
local a, msg = string.match(param, "^([%a%d_-]+) (*+)$")
```
Il codice sovrastante implementa `/msg <a> <messaggio>`. Vediamo cos'è successo partendo da sinistra:
@ -137,9 +101,9 @@ Il codice sovrastante implementa `/msg <a> <messaggio>`. Vediamo cos'è successo
* `()` è un gruppo - qualsiasi cosa che combaci con ciò che è contenuto al suo interno verrà ritornato da string.match;
* `[]` significa che i caratteri al suo interno sono accettati;
* `%a` significa che accetta ogni lettera e `%d` ogni cifra.
* `[%a%d_-]` significa che accetta ogni lettera, cifra, `_` e `-`.
* `[%d%a_-]` significa che accetta ogni lettera, cifra, `_` e `-`.
* `+` dice di combaciare ciò che lo precede una o più volte.
* `.` dice di combaciare qualsiasi tipo di carattere.
* `*` dice di combaciare qualsiasi tipo di carattere.
* `$` dice di combaciare la fine della stringa.
Detto semplicemente, il pattern cerca un nome (una parola fatta di lettere, numeri, trattini o trattini bassi), poi uno spazio e poi il messaggio (uno o più caratteri, qualsiasi essi siano).
@ -149,9 +113,7 @@ Questo è come molte mod implementano comandi complessi.
Una guida più completa ai pattern è probabilmente quella su [lua-users.org](http://lua-users.org/wiki/PatternsTutorial) o la [documentazione PIL](https://www.lua.org/pil/20.2.html).
<p class="book_hide">
C'è anche una libreria scritta dall'autore di questo libro che può essere usata
per creare comandi complessi senza l'utilizzo di pattern:
<a href="https://gitlab.com/rubenwardy/ChatCmdBuilder">Chat Command Builder</a>.
C'è anche una libreria scritta dall'autore di questo libro che può essere usata per creare comandi complessi senza l'utilizzo di pattern: <a href="chat_complex.html">Chat Command Builder</a>.
</p>
@ -160,13 +122,13 @@ Una guida più completa ai pattern è probabilmente quella su [lua-users.org](ht
Per intercettare un messaggio, si usa `register_on_chat_message`:
```lua
core.register_on_chat_message(function(name, message)
minetest.register_on_chat_message(function(name, message)
print(name .. " ha detto " .. message)
return false
end)
```
Ritornando `false`, si permette al messaggio di essere inviato.
Ritornando false, si permette al messaggio di essere inviato.
In verità `return false` può anche essere omesso in quanto `nil` verrebbe ritornato implicitamente, e nil è trattato come false.
{% include notice.html notice=page.cb_cmdsprivs %}
@ -175,10 +137,10 @@ Dovresti assicurarti, poi, che il messaggio potrebbe essere un comando che invia
o che l'utente potrebbere non avere `shout`.
```lua
core.register_on_chat_message(function(name, message)
minetest.register_on_chat_message(function(name, message)
if message:sub(1, 1) == "/" then
print(name .. " ha eseguito un comando")
elseif core.check_player_privs(name, { shout = true }) then
elseif minetest.check_player_privs(name, { shout = true }) then
print(name .. " ha detto " .. message)
else
print(name .. " ha provato a dire " .. message ..

173
_it/players/chat_complex.md Normal file
View File

@ -0,0 +1,173 @@
---
title: Chat Command Builder
layout: default
root: ../..
idx: 4.3
description: Creazione di comandi complessi semplificandosi la vita
redirect_from: /it/chapters/chat_complex.html
---
## Introduzione <!-- omit in toc -->
Questo capitolo ti mostrerà come creare comandi complessi con ChatCmdBuilder, come `/msg <nome> <messaggio>`, `/team entra <nometeam>` or `/team esci <nometeam>`.
Tieni conto che ChatCmdBuilder è una libreria creata dall'autore di questo libro, e che molti modder tendono a usare il metodo illustrato nel capitolo [Chat e comandi](chat.html#complex-subcommands).
- [Perché ChatCmdBuilder?](#perche-chatcmdbuilder)
- [Tratte](#tratte)
- [Funzioni nei sottocomandi](#funzioni-nei-sottocomandi)
- [Installare ChatCmdBuilder](#installare-chatcmdbuilder)
- [Esempio: comando complesso /admin](#esempio-comando-complesso-admin)
## Perché ChatCmdBuilder?
Le mod tradizionali implementano questi comandi complessi usando i pattern Lua.
```lua
local nome = string.match(param, "^join ([%a%d_-]+)")
```
Io, tuttavia, trovo i pattern Lua illeggibili e scomodi.
Per via di ciò, ho creato una libreria che ti semplifichi la vita.
```lua
ChatCmdBuilder.new("sethp", function(cmd)
cmd:sub(":target :hp:int", function(name, target, hp)
local giocatore = minetest.get_player_by_name(target)
if giocatore then
giocatore:set_hp(hp)
return true, "Gli hp di " .. target .. " sono ora " .. hp
else
return false, "Giocatore " .. target .. " non trovato"
end
end)
end, {
description = "Imposta gli hp del giocatore",
privs = {
kick = true
-- ^ è probabilmente meglio impostare un nuovo privilegio
}
})
```
`ChatCmdBuilder.new(name, setup_func, def)` crea un nuovo comando chiamato `name`.
Poi, chiama la funzione passatagli (`setup_func`), che crea a sua volta i sottocomandi.
Ogni `cmd:sub(route, func)` è un sottocomando.
Un sottocomando è una particolare risposta a un parametro di input.
Quando un giocatore esegue il comando, il primo sottocomando che combacia con l'input verrà eseguito.
Se non ne viene trovato nessuno, il giocatore verrà avvisato della sintassi non valida.
Nel codice qui in alto, per esempio, se qualcuno scrive qualcosa come `/sethp nickname 12`, la funzione corrispondente verrà chiamata.
Tuttavia, qualcosa come `/sethp 12 bleh` genererà un messaggio d'errore.
`:name :hp:int` è una tratta.
Descrive il formato del parametro passato a /teleport.
## Tratte
Una tratta è composta di fermate e variabili, dove le prime sono obbligatorie.
Una fermata è per esempio `crea` in `/team crea :nometeam :giocatorimassimi:int`, ma anche gli spazi contano come tali.
Le variabili possono cambiare valore a seconda di cosa scrive l'utente. Per esempio `:nometeam` e `:giocatorimassimi:int`.
Le variabili sono definite con `:nome:tipo`: il nome è usato nella documentazione, mentre il tipo è usato per far combaciare l'input.
Se il tipo non è specificato, allora sarà di base `word`.
I tipi consentiti sono:
* `word` - Predefinito. Qualsiasi stringa senza spazi;
* `int` - Qualsiasi numero intero;
* `number` - Qualsiasi numero, decimali inclusi;
* `pos` - Coordinate. Il formato può essere 1,2,3, o 1.1,2,3.4567, o (1,2,3), o ancora 1.2, 2 ,3.2;
* `text` - Qualsiasi stringa, spazi inclusi. Può esserci solo un `text` e non può essere seguito da nient'altro.
In `:nome :hp:int`, ci sono due variabili:
* `name` - di tipo `word` in quanto non è stato specificato
* `hp` - di tipo `int`, quindi un numero intero
## Funzioni nei sottocomandi
Il primo parametro è il nome di chi invia il comando. Le variabili sono poi passate alla funzione nell'ordine in cui sono state dichiarate.
```lua
cmd:sub(":target :hp:int", function(name, target, hp)
-- funzione del sottocomando
end)
```
## Installare ChatCmdBuilder
Il codice sorgente può essere trovato e scaricato su
[Github](https://github.com/rubenwardy/ChatCmdBuilder/).
Ci sono due modi per installarlo:
1. Installarlo come una mod a sé stante;
2. Includere nella tua mod l'init.lua di ChatCmdBuilder rinominandolo chatcmdbuilder.lua, e integrarlo tramite `dofile`.
## Esempio: comando complesso /admin
Segue un esempio che crea un comando che aggiunge le seguenti funzioni per chi ha il permesso `kick` e `ban` (quindi, in teoria, un admin):
* `/admin uccidi <nome>` - uccide un utente;
* `/admin sposta <nome> a <pos>` - teletrasporta un utente;
* `/admin log <nome>` - mostra il log di un utente;
* `/admin log <nome> <messaggio>` - aggiunge un messaggio al log di un utente.
```lua
local admin_log
local function carica()
admin_log = {}
end
local function salva()
-- todo
end
carica()
ChatCmdBuilder.new("admin", function(cmd)
cmd:sub("uccidi :nome", function(name, target)
local giocatore = minetest.get_player_by_name(target)
if giocatore then
giocatore:set_hp(0)
return true, "Hai ucciso " .. target
else
return false, "Unable to find " .. target
end
end)
cmd:sub("sposta :nome to :pos:pos", function(nome, target, pos)
local giocatore = minetest.get_player_by_name(target)
if giocatore then
giocatore:setpos(pos)
return true, "Giocatore " .. target .. " teletrasportato a " ..
minetest.pos_to_string(pos)
else
return false, "Giocatore " .. target .. " non trovato"
end
end)
cmd:sub("log :nome", function(name, target)
local log = admin_log[target]
if log then
return true, table.concat(log, "\n")
else
return false, "Nessuna voce per " .. target
end
end)
cmd:sub("log :nome :messaggio", function(name, target, messaggio)
local log = admin_log[target] or {}
table.insert(log, messaggio)
admin_log[target] = log
salva()
return true, "Aggiunto"
end)
end, {
description = "Strumenti per gli admin",
privs = {
kick = true,
ban = true
}
})
```

View File

@ -48,8 +48,6 @@ Il modo in cui elementi diversi venivano posizionati nel formspec variava in man
Da Minetest 5.1.0, tuttavia, è stata introdotta una funzione chiamata Coordinate Reali (*real coordinates*), la quale punta a correggere questo comportamento tramite l'introduzione di un sistema di coordinate coerente.
L'uso delle coordinate reali è caldamente consigliato, onde per cui questo capitolo non tratterà di quelle vecchie.
Usando una versione del formspec maggiore o uguale a 2, esse saranno abilitate di base.
## Anatomia di un formspec
### Elementi
@ -66,7 +64,7 @@ Si possono concatenare più elementi, piazzandoli eventualmente su più linee:
bo[param1]
Gli elementi sono o oggetti come i campi di testo e i pulsanti, o dei metadati come la grandezza e lo sfondo.
Per una lista esaustiva di tutti i possibili elementi, si rimanda a [lua_api.md](https://minetest.gitlab.io/minetest/formspec/).
Per una lista esaustiva di tutti i possibili elementi, si rimanda a [lua_api.txt](../../lua_api.html#elements).
### Intestazione
@ -74,12 +72,12 @@ L'intestazione di un formspec contiene informazioni che devono apparire prima di
Questo include la grandezza del formspec, la posizione, l'ancoraggio, e se il tema specifico del gioco debba venir applicato.
Gli elementi nell'intestazione devono essere definiti in un ordine preciso, altrimenti ritorneranno un errore.
L'ordine è dato nel paragrafo qui in alto e, come sempre, documentato in lua_api.md.
L'ordine è dato nel paragrafo qui in alto e, come sempre, documentato in [lua_api.txt](../../lua_api.html#sizewhfixed_size).
La grandezza è in caselle formspec - un'unità di misura che è circa 64 pixel, ma varia a seconda della densità dello schermo e delle impostazioni del client.
Ecco un formspec di 2x2:
formspec_version[4]
formspec_version[3]
size[2,2]
Notare come è stata esplicitamente definita la versione del linguaggio: senza di essa, il sistema datato sarebbe stato usato di base - che avrebbe impossibilitato il posizionamento coerente degli elementi e altre nuove funzioni.
@ -88,8 +86,9 @@ La posizione e l'ancoraggio degli elementi sono usati per collocare il formspec
La posizione imposta dove si troverà (con valore predefinito al centro, `0.5,0.5`), mentre l'ancoraggio da dove partire, permettendo di allineare il formspec con i bordi dello schermo.
Per esempio, lo si può posizionare ancorato a sinistra in questo modo:
formspec_version[4]
formspec_version[3]
size[2,2]
real_coordinates[true]
position[0,0.5]
anchor[0,0.5]
@ -121,9 +120,8 @@ function indovina.prendi_formspec(nome)
local testo = "Sto pensando a un numero... Prova a indovinare!"
local formspec = {
"formspec_version[4]",
"size[6,3.476]",
"label[0.375,0.5;", core.formspec_escape(testo), "]",
"label[0.375,0.5;", minetest.formspec_escape(testo), "]",
"field[0.375,1.25;5.25,0.8;numero;Numero;]",
"button[1.5,2.3;3,0.8;indovina;Indovina]"
}
@ -143,10 +141,10 @@ Il metodo principale per farlo è usare `show_formspec`:
```lua
function indovina.mostra_a(nome)
core.show_formspec(nome, "indovina:gioco", indovina.prendi_formspec(nome))
minetest.show_formspec(nome, "indovina:gioco", indovina.prendi_formspec(nome))
end
core.register_chatcommand("gioco", {
minetest.register_chatcommand("gioco", {
func = function(name)
indovina.mostra_a(name)
end,
@ -180,19 +178,19 @@ Per far sì che i formspec siano utili, le informazioni devono essere ritornate
Il metodo per fare ciò è chiamato Campo di Compilazione (*formspec field submission*), e per `show_formspec` quel campo viene ottenuto usando un callback globale:
```lua
core.register_on_player_receive_fields(function(player, formname, fields)
minetest.register_on_player_receive_fields(function(player, formname, fields)
if formname ~= "indovina:gioco" then
return
end
if fields.indovina then
local p_name = player:get_player_name()
core.chat_send_all(p_name .. " ha tentato di indovinare con il numero " .. fields.numero)
minetest.chat_send_all(p_name .. " ha tentato di indovinare con il numero " .. fields.numero)
end
end)
```
La funzione data in `core.register_on_player_receive_fields` è chiamata ogni volta che un utente invia un modulo.
La funzione data in `minetest.register_on_player_receive_fields` è chiamata ogni volta che un utente invia un modulo.
La maggior parte dei callback necessiteranno di controllare il nome fornito alla funzione, e uscire se non è quello esatto; tuttavia, alcuni potrebbero necessitare di operare su più moduli, se non addirittura su tutti.
Il parametro `fields` è una tabella di tutti i valori inviati dall'utente, indicizzati per stringhe.
@ -223,7 +221,7 @@ local function prendi_contesto(nome)
return contesto
end
core.register_on_leaveplayer(function(player)
minetest.register_on_leaveplayer(function(player)
_contexts[player:get_player_name()] = nil
end)
```
@ -236,7 +234,7 @@ function indovina.mostra_a(nome)
contesto.soluzione = contesto.soluzione or math.random(1, 10)
local formspec = indovina.prendi_formspec(nome, contesto)
core.show_formspec(nome, "indovina:gioco", formspec)
minetest.show_formspec(nome, "indovina:gioco", formspec)
end
```
@ -282,11 +280,11 @@ Ci sono tre diversi modi per far sì che un formspec sia consegnato al client:
### Formspec nei nodi
`core.show_formspec` non è l'unico modo per mostrare un formspec; essi possono infatti essere aggiunti anche ai [metadati di un nodo](../map/storage.html).
`minetest.show_formspec` non è l'unico modo per mostrare un formspec; essi possono infatti essere aggiunti anche ai [metadati di un nodo](node_metadata.html).
Per esempio, questo è usato con le casse per permettere tempi più veloci d'apertura - non si ha bisogno di aspettare che il server invii il formspec della cassa al giocatore.
```lua
core.register_node("miamod:tastodestro", {
minetest.register_node("miamod:tastodestro", {
description = "Premimi col tasto destro del mouse!",
tiles = {"miamod_tastodestro.png"},
groups = {cracky = 1},
@ -295,9 +293,9 @@ core.register_node("miamod:tastodestro", {
-- Il codice che segue imposta il formspec della cassa.
-- I metadati sono un modo per immagazzinare dati nel nodo.
local meta = core.get_meta(pos)
local meta = minetest.get_meta(pos)
meta:set_string("formspec",
"formspec_version[4]" ..
"formspec_version[3]" ..
"size[5,5]"..
"label[1,1;Questo è mostrato al premere col destro]"..
"field[1,2;2,1;x;x;]")
@ -312,7 +310,7 @@ core.register_node("miamod:tastodestro", {
I formspec impostati in questo modo non innescano lo stesso callback.
Per far in modo di ricevere il modulo di input per i formspec nei nodi, bisogna includere una voce `on_receive_fields` al registrare il nodo.
Questo stile di callback viene innescato al premere invio in un campo, che è possibile grazie a `core.show_formspec`; tuttavia, questi tipi di moduli possono essere mostrati solo
Questo stile di callback viene innescato al premere invio in un campo, che è possibile grazie a `minetest.show_formspec`; tuttavia, questi tipi di moduli possono essere mostrati solo
tramite il premere col tasto destro del mouse su un nodo. Non è possibile farlo programmaticamente.
### Inventario del giocatore
@ -321,7 +319,7 @@ L'inventario del giocatore è un formspec, che viene mostrato al premere "I".
Il callback globale viene usato per ricevere eventi dall'inventario, e il suo nome è `""`.
Ci sono svariate mod che permettono ad altrettante mod di personalizzare l'inventario del giocatore.
La mod ufficialmente raccomandata è [SFINV](https://github.com/rubenwardy/sfinv/blob/master/Tutorial.md), ed è inclusa in Minetest Game.
La mod ufficialmente raccomandata è [Simple Fast Inventory (sfinv)](sfinv.html), ed è inclusa in Minetest Game.
### Il tuo turno

View File

@ -18,10 +18,10 @@ Le HUD, infatti, non accettano input dall'utente, lasciando quel ruolo ai [forms
- [Esempio: tabellone segnapunti](#esempio-tabellone-segnapunti)
- [Elementi di testo](#elementi-di-testo)
- [Parametri](#parametri)
- [Tornando all'esempio](#tornando-allesempio)
- [Tornando all'esempio](#tornando-all-esempio)
- [Elementi immagine](#elementi-immagine)
- [Parametri](#parametri-1)
- [Tornando all'esempio](#tornando-allesempio-1)
- [Tornando all'esempio](#tornando-all-esempio-1)
- [Cambiare un elemento](#cambiare-un-elemento)
- [Salvare gli ID](#salvare-gli-id)
- [Altri elementi](#altri-elementi)
@ -79,7 +79,7 @@ Questo permette all'intero pannello di essere ancorato sulla destra della finest
Puoi creare un elemento HUD una volta ottenuto il riferimento al giocatore al quale assegnarla:
```lua
local giocatore = core.get_player_by_name("tizio")
local giocatore = minetest.get_player_by_name("tizio")
local idx = giocatore:hud_add({
hud_elem_type = "text",
position = {x = 0.5, y = 0.5},
@ -268,9 +268,9 @@ function punteggio.aggiorna_hud(giocatore)
end
end
core.register_on_joinplayer(punteggio.aggiorna_hud)
minetest.register_on_joinplayer(punteggio.aggiorna_hud)
core.register_on_leaveplayer(function(player)
minetest.register_on_leaveplayer(function(player)
hud_salvate[player:get_player_name()] = nil
end)
```
@ -278,4 +278,4 @@ end)
## Altri elementi
Dai un occhio a [lua_api.md](https://minetest.gitlab.io/minetest/hud/) per una lista completa degli elementi HUD.
Dai un occhio a [lua_api.txt]({{ page.root }}/lua_api.html#hud-element-types) per una lista completa degli elementi HUD.

View File

@ -15,7 +15,7 @@ Per esempio, un valore di 2 sulla gravità, renderà la gravità di un utente du
- [Esempio base](#esempio-base)
- [Sovrascritture disponibili](#sovrascritture-disponibili)
- [Vecchio sistema di movimento](#vecchio-sistema-di-movimento)
- [Incompatibilità tra mod](#incompatibilità-tra-mod)
- [Incompatibilità tra mod](#incompatibilita-tra-mod)
- [Il tuo turno](#il-tuo-turno)
## Esempio base
@ -23,9 +23,9 @@ Per esempio, un valore di 2 sulla gravità, renderà la gravità di un utente du
Segue l'esempio di un comando di antigravità:
```lua
core.register_chatcommand("antigrav", {
minetest.register_chatcommand("antigrav", {
func = function(name, param)
local giocatore = core.get_player_by_name(name)
local giocatore = minetest.get_player_by_name(name)
giocatore:set_physics_override({
gravity = 0.1, -- imposta la gravità al 10% del suo valore originale
-- (0.1 * 9.81)
@ -36,7 +36,7 @@ core.register_chatcommand("antigrav", {
## Sovrascritture disponibili
`set_physics_override()` è una tabella. Stando a [lua_api.md](https://minetest.gitlab.io/minetest/class-reference/#player-only-no-op-for-other-objects), le chiavi possono essere:
`set_physics_override()` è una tabella. Stando a [lua_api.txt]({{ page.root }}/lua_api.html#player-only-no-op-for-other-objects), le chiavi possono essere:
* `speed`: moltiplicatore della velocità di movimento (predefinito: 1)
* `jump`: moltiplicatore del salto (predefinito: 1)
@ -61,4 +61,4 @@ Quando si imposta una sovrascrittura, sovrascriverà qualsiasi altro suo simile
* **Sonic**: Imposta il moltiplicatore di velocità a un valore elevato (almeno 6) quando un giocatore entra in gioco;
* **Super rimbalzo**: Aumenta il valore del salto in modo che il giocatore possa saltare 20 metri (1 cubo = 1 metro);
* **Spazio**: Fai in modo che la gravità diminuisca man mano che si sale di altitudine.
* **Space**: Fai in modo che la gravità diminuisca man mano che si sale di altitudine.

View File

@ -32,7 +32,7 @@ I privilegi non sono fatti per indicare classi o status.
* ban
* vote
* worldedit
* area_admin - se si tratta di un privilegio per gli amministratori è ok
* area_admin - admin functions of one mod is ok
**Privilegi sbagliati:**
@ -46,7 +46,7 @@ I privilegi non sono fatti per indicare classi o status.
Usa `register_privilege` per dichiarare un nuovo privilegio:
```lua
core.register_privilege("voto", {
minetest.register_privilege("voto", {
description = "Può votare nei sondaggi",
give_to_singleplayer = false
})
@ -59,7 +59,7 @@ core.register_privilege("voto", {
Per controllare velocemente se un giocatore ha tutti i privilegi necessari o meno:
```lua
local celo, manca = core.check_player_privs(player_or_name, {
local celo, manca = minetest.check_player_privs(player_or_name, {
interact = true,
voto = true })
```
@ -68,7 +68,7 @@ In quest'esempio, `celo` è true se il giocatore ha sia `interact` che `voto`.
Se `celo` è false, allora `manca` conterrà una tabella con i privilegi mancanti.
```lua
local celo, manca = core.check_player_privs(name, {
local celo, manca = minetest.check_player_privs(name, {
interact = true,
voto = true })
@ -82,7 +82,7 @@ end
Se non hai bisogno di controllare i privilegi mancanti, puoi inserire `check_player_privs` direttamente nel costrutto if:
```lua
if not core.check_player_privs(name, { interact=true }) then
if not minetest.check_player_privs(name, { interact=true }) then
return false, "Hai bisogno del privilegio 'interact' per eseguire quest'azione!"
end
```
@ -92,11 +92,11 @@ end
Si può accedere o modificare i privilegi di un giocatore anche se quest'ultimo non risulta online.
```lua
local privs = core.get_player_privs(name)
local privs = minetest.get_player_privs(name)
print(dump(privs))
privs.voto = true
core.set_player_privs(name, privs)
minetest.set_player_privs(name, privs)
```
I privilegi sono sempre specificati come una tabella chiave-valore, con il loro nome come chiave e true/false come valore.

View File

@ -1,4 +1,223 @@
---
sitemap: false
redirect_to: "https://github.com/rubenwardy/sfinv/blob/master/Tutorial.md"
title: "SFINV"
layout: default
root: ../..
idx: 4.7
description: una mod per rendere più semplice la creazione di un inventario complesso
redirect_from: /it/chapters/sfinv.html
---
## Introduzione <!-- omit in toc -->
Simple Fast Inventory (SFINV) è una mod presente in Minetest Game, usata per creare il [formspec](formspecs.html) del giocatore.
SFINV ha un'API che permette di aggiungere e altresì gestire le pagine mostrate.
Mentre SFINV di base mostra le pagine come finestre, le pagine sono chiamate tali in quanto è assolutamente possibile che una mod o un gioco decidano di mostrarle in un altro formato.
Per esempio, più pagine possono essere mostrate nel medesimo formspec.
- [Registrare una pagina](#registrare-una-pagina)
- [Ricevere eventi](#ricevere-eventi)
- [Condizioni per la visualizzazione](#condizioni-per-la-visualizzazione)
- [Callback on_enter e on_leave](#callback-onenter-e-onleave)
- [Aggiungere a una pagina esistente](#aggiungere-a-una-pagina-esistente)
## Registrare una pagina
SFINV fornisce la funzione chiamata `sfinv.register_page` per creare nuove pagine.
Basta chiamare la funzione con il nome che si vuole assegnare alla pagina e la sua definizione:
```lua
sfinv.register_page("miamod:ciao", {
title = "Ciao!",
get = function(self, player, context)
return sfinv.make_formspec(player, context,
"label[0.1,0.1;Ciao mondo!]", true)
end
})
```
La funzione `make_formspec` circonda il formspec con il codice di SFINV.
Il quarto parametro, attualmente impostato a `true`, determina se l'inventario del giocatore è mostrato.
Rendiamo le cose più eccitanti; segue il codice della generazione di un formspec per gli admin.
Questa finestra permetterà agli admin di cacciare o bannare i giocatori selezionandoli da una lista e premendo un pulsante.
```lua
sfinv.register_page("mioadmin:mioadmin", {
title = "Finestra",
get = function(self, player, context)
local giocatori = {}
context.mioadmin_giocatori = giocatori
-- Usare un array per costruire un formspec è decisamente più veloce
local formspec = {
"textlist[0.1,0.1;7.8,3;lista_giocatori;"
}
-- Aggiunge tutti i giocatori sia alla lista testuale che a quella - appunto - dei giocatori
local primo = true
for _ , giocatore in pairs(minetest.get_connected_players()) do
local nome_giocatore = giocatore:get_player_name()
giocatori[#giocatori + 1] = nome_giocatore
if not primo then
formspec[#formspec + 1] = ","
end
formspec[#formspec + 1] =
minetest.formspec_escape(nome_giocatore)
primo = false
end
formspec[#formspec + 1] = "]"
-- Aggiunge i pulsanti
formspec[#formspec + 1] = "button[0.1,3.3;2,1;caccia;Caccia]"
formspec[#formspec + 1] = "button[2.1,3.3;2,1;banna;Caccia e Banna]"
-- Avvolge il formspec nella disposizione di SFINV
-- (es: aggiunge le linguette delle finestre e lo sfondo)
return sfinv.make_formspec(player, context,
table.concat(formspec, ""), false)
end,
})
```
Non c'è niente di nuovo in questa parte di codice; tutti i concetti sono già stati trattati o qui in alto o nei precedenti capitoli.
<figure>
<img src="{{ page.root }}//static/sfinv_admin_fs.png" alt="Pagina per gli amministratori">
</figure>
## Ricevere eventi
Puoi ricevere eventi formspec tramite l'aggiunta della funzione `on_player_receive_fields` nella definizione SFINV.
```lua
on_player_receive_fields = function(self, player, context, fields)
-- TODO: implementarlo
end,
```
`on_player_receive_fields` funziona alla stessa maniera di `minetest.register_on_player_receive_fields`, con la differenza che viene richiesto il contesto al posto del nome del form.
Tieni a mente che gli eventi interni di SFINV, come la navigazione tra le varie finestre, vengono gestiti dentro la mod stessa, e che quindi non verranno ricevuti in questo callback.
Implementiamo ora `on_player_receive_fields` nella mod:
```lua
on_player_receive_fields = function(self, player, context, fields)
-- evento della lista testuale: controlla il tipo di evento e imposta il nuovo indice se è cambiata la selezione
if fields.lista_giocatori then
local event = minetest.explode_textlist_event(fields.lista_giocatori)
if event.type == "CHG" then
context.mioadmin_sel_id = event.index
end
-- Al premere "Caccia"
elseif fields.caccia then
local nome_giocatore =
context.myadmin_players[context.mioadmin_sel_id]
if player_name then
minetest.chat_send_player(player:get_player_name(),
"Cacciato " .. nome_giocatore)
minetest.kick_player(nome_giocatore)
end
-- Al premere "Caccia e Banna"
elseif fields.banna then
local nome_giocatore =
context.myadmin_players[context.mioadmin_sel_id]
if player_name then
minetest.chat_send_player(player:get_player_name(),
"Banned " .. player_name)
minetest.ban_player(nome_giocatore)
minetest.kick_player(nome_giocatore, "Banned")
end
end
end,
```
C'è, tuttavia, un problema abbastanza grande a riguardo: chiunque può cacciare o bannare i giocatori!
C'è bisogno di un modo per mostrare questa finestra solo a chi ha i privilegi `kick` e `ban`.
Fortunatamente, SFINV ci permette di farlo!
## Condizioni per la visualizzazione
Si può aggiungere una funzione `is_in_nav` nella definizione della pagina se si desidera gestire quando la pagina deve essere mostrata:
```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,
```
Se si ha bisogno di controllare un solo privilegio o si vuole eseguire un `and`, si bisognerebbe usare `minetest.check_player_privs()` al posto di `get_player_privs`.
Tieni a mente che `is_in_nav` viene chiamato soltanto alla generazione dell'inventario del giocatore.
Questo succede quando un giocatore entra in gioco, si muove tra le finestre, o una mod richiede a SFINV di rigenerare l'inventario.
Ciò significa che hai bisogno di richiedere manualmente la rigenerazione del formspec dell'inventario per ogni evento che potrebbe cambiare il risultato ti `is_in_nav`.
Nel nostro caso, abbiamo bisogno di farlo ogni volta che i permessi `kick` o `ban` vengono assegnati/revocati a un giocatore:
```lua
local function al_cambio_privilegi(nome_target, nome_garante, priv)
if priv ~= "kick" and priv ~= "ban" then
return
end
local giocatore = minetest.get_player_by_name(nome_target)
if not giocatore then
return
end
local contesto = sfinv.get_or_create_context(giocatore)
if contesto.page ~= "mioadmin:mioadmin" then
return
end
sfinv.set_player_inventory_formspec(giocatore, contesto)
end
minetest.register_on_priv_grant(al_cambio_privilegi)
minetest.register_on_priv_revoke(al_cambio_privilegi)
```
## Callback on_enter e on_leave
Un giocatore *entra* in una finestra quando la finestra è selezionata e *esce* dalla finestra quando un'altra finestra è prossima a essere selezionata.
Attenzione che è possibile selezionare più pagine alla volta se viene usata un tema personalizzato.
Si tenga conto, poi, che questi eventi potrebbero non essere innescati dal giocatore, in quanto potrebbe addirittura non avere un formspec aperto in quel momento.
Per esempio, `on_enter` viene chiamato dalla pagina principale anche quando un giocatore entra in gioco, ancor prima che apri l'inventario.
Infine, non è possibile annullare il cambio pagina, in quanto potrebbe potenzialmente confondere il giocatore.
```lua
on_enter = function(self, player, context)
end,
on_leave = function(self, player, context)
end,
```
## Aggiungere a una pagina esistente
Per aggiungere contenuti a una pagina che già esiste, avrai bisogno di sovrascrivere la pagina e modificare il formspec che viene ritornato:
```lua
local vecchia_funzione = sfinv.registered_pages["sfinv:crafting"].get
sfinv.override_page("sfinv:crafting", {
get = function(self, player, context, ...)
local ret = vecchia_funzione(self, player, context, ...)
if type(ret) == "table" then
ret.formspec = ret.formspec .. "label[0,0;Ciao]"
else
-- Retrocompatibilità
ret = ret .. "label[0,0;Ciao]"
end
return ret
end
})
```

View File

@ -82,7 +82,7 @@ E hai ragione! L'API di Minetest è molto incentrata sull'Osservatore, per far i
## Modello-Vista-Controllo
Nel prossimo capitolo discuteremo di come testare automaticamente il codice, e uno dei problemi che riscontreremo sarà come separare il più possibile la logica (calcoli, cosa bisognerebbe fare) dalle chiamate alle API (`core.*`, altre mod).
Nel prossimo capitolo discuteremo di come testare automaticamente il codice, e uno dei problemi che riscontreremo sarà come separare il più possibile la logica (calcoli, cosa bisognerebbe fare) dalle chiamate alle API (`minetest.*`, altre mod).
Un modo per fare ciò è pensare a:
@ -148,14 +148,14 @@ function terreno.mostra_formspec_crea(nome)
]]
end
core.register_chatcommand("/land", {
minetest.register_chatcommand("/land", {
privs = { terreno = true },
func = function(name)
land.gestore_richiesta_crea(name)
end,
})
core.register_on_player_receive_fields(function(player,
minetest.register_on_player_receive_fields(function(player,
formname, fields)
terreno.gestore_invio_crea(player:get_player_name(),
fields.nome_area)
@ -191,7 +191,7 @@ Al contrario, un approccio più comune e leggermente meno rigido è quello API-V
In un mondo ideale, si avrebbero le 3 aree MVC perfettamente separate... ma siamo nel mondo reale.
Un buon compromesso è ridurre la mod in due parti:
* **API** - modello + controllo. Non ci dovrebbe essere nessun uso di `core.` nella API.
* **API** - modello + controllo. Non ci dovrebbe essere nessun uso di `minetest.` nella API.
* **Vista** - la vista, esattamente come quella spiegata sopra.
È buona norma strutturare questa parte in file separati per ogni tipo di evento.

View File

@ -10,26 +10,51 @@ redirect_from: /it/chapters/common_mistakes.html
Questo capitolo illustra gli errori più comuni e come evitarli.
- [Fai attenzione quando salvi gli ObjectRef in delle variabili (giocatori ed entità)](#fai-attenzione-quando-salvi-gli-objectref-in-delle-variabili-giocatori-ed-entità)
- [Non salvare mai ObjectRef (giocatori ed entità)](#non-salvare-mai-objectref-giocatori-ed-entita)
- [Non fidarti dei campi dei formspec](#non-fidarti-dei-campi-dei-formspec)
- [Imposta gli ItemStack dopo averli modificati](#imposta-gli-itemstack-dopo-averli-modificati)
## Fai attenzione quando salvi gli ObjectRef in delle variabili (giocatori ed entità)
## Non salvare mai ObjectRef (giocatori ed entità)
Un ObjectRef viene invalidato quando il giocatore o l'entità che esso rappresenta abbandona il gioco.
Ciò si verifica quando un giocatore si disconnette, o quando un'entità viene rimossa dalla memoria.
Se l'oggetto rappresentato da un ObjectRef viene rimosso - per esempio quando il giocatore si disconnette o un'entità viene rimossa dalla memoria - chiamare metodi su quell'oggetto causerà la chiusura improvvisa del server (*crash*).
Da Minetest 5.2, i metodi degli ObjectRef ritorneranno sempre `nil` quando non validi.
In altre parole, ogni chiamata verrà ignorata.
Si dovrebbe evitare quanto possibile di immagazzinare gli ObjectRef in delle variabili.
Se ciò tuttavia accade, assicurati di controllare se esiste ancora, come illustrato qui di seguito:
Sbagliato:
```lua
-- questo codice funziona solo da Minetest 5.2 in poi
if obj:get_pos() then
-- è valido!
minetest.register_on_joinplayer(function(player)
local function func()
local pos = player:get_pos() -- MALE!
-- `player` viene salvato per essere utilizzato dopo.
-- Se il giocatore si disconnette, il server crasha
end
minetest.after(1, func)
foobar[player:get_player_name()] = player
-- RISCHIOSO
-- Non è consigliato fare così.
-- Usa invece minetest.get_connected_players() e minetest.get_player_by_name().
end)
```
Giusto:
```lua
minetest.register_on_joinplayer(function(player)
local function func(name)
-- Tenta di ottenere il riferimento
local player = minetest.get_player_by_name(name)
-- Controlla che il giocatore sia online
if player then
-- è online, procedo
local pos = player:get_pos()
end
end
-- Passa il nome nella funzione
minetest.after(1, func, player:get_player_name())
end)
```
## Non fidarti dei campi dei formspec
@ -40,11 +65,11 @@ Per esempio, il seguente codice presenta una vulnerabilità che permette ai gioc
```lua
local function show_formspec(name)
if not core.check_player_privs(name, { privs = true }) then
if not minetest.check_player_privs(name, { privs = true }) then
return false
end
core.show_formspec(name, "modman:modman", [[
minetest.show_formspec(name, "modman:modman", [[
size[3,2]
field[0,0;3,1;target;Nome;]
button_exit[0,1;3,1;sub;Promuovi]
@ -52,14 +77,14 @@ local function show_formspec(name)
return true
})
core.register_on_player_receive_fields(function(player,
minetest.register_on_player_receive_fields(function(player,
formname, fields)
-- MALE! Manca il controllo dei privilegi!
local privs = core.get_player_privs(fields.target)
local privs = minetest.get_player_privs(fields.target)
privs.kick = true
privs.ban = true
core.set_player_privs(fields.target, privs)
minetest.set_player_privs(fields.target, privs)
return true
end)
```
@ -67,9 +92,9 @@ end)
Aggiungi un controllo dei privilegi per ovviare:
```lua
core.register_on_player_receive_fields(function(player,
minetest.register_on_player_receive_fields(function(player,
formname, fields)
if not core.check_player_privs(name, { privs = true }) then
if not minetest.check_player_privs(name, { privs = true }) then
return false
end
@ -105,7 +130,7 @@ inv:set_stack("main", 1, pila)
Il comportamento dei callback è leggermente più complicato.
```lua
core.register_on_item_eat(function(hp_change, replace_with_item,
minetest.register_on_item_eat(function(hp_change, replace_with_item,
itemstack, user, pointed_thing)
itemstack:get_meta():set_string("description", "Un po' smangiucchiato")
-- Quasi corretto! I dati saranno persi se un altro callback annulla questa chiamata
@ -117,7 +142,7 @@ Se nessun callback cancella l'operazione, la pila sarà impostata e la descrizio
È meglio quindi fare così:
```lua
core.register_on_item_eat(function(hp_change, replace_with_item,
minetest.register_on_item_eat(function(hp_change, replace_with_item,
itemstack, user, pointed_thing)
itemstack:get_meta():set_string("description", "Un po' smangiucchiato")
user:get_inventory():set_stack("main", user:get_wield_index(),

View File

@ -19,12 +19,13 @@ LuaCheck può essere usato in combinazione con l'editor per fornire avvertimenti
- [Configurare LuaCheck](#configurare-luacheck)
- [Risoluzione problemi](#risoluzione-problemi)
- [Uso nell'editor](#uso-nelleditor)
- [Controllare i commit con Travis](#controllare-i-commit-con-travis)
## Installare LuaCheck
### Windows
Basta scaricare luacheck.exe dall'apposita [pagina delle versioni su Github](https://github.com/mpeterv/luacheck/releases).
Basta scaricare luacheck.exe dall'apposita [pagina delle release su Github](https://github.com/mpeterv/luacheck/releases).
### Linux
@ -52,7 +53,7 @@ Su Linux, esegui `luacheck .` nella cartella principale del progetto.
## Configurare LuaCheck
Crea un file chiamato .luacheckrc nella cartella principale del tuo progetto.
Questa può essere quella di un gioco, di un pacchetto mod o di una mod singola.
Questa può essere quella di un gioco, di una modpack o di una mod.
Inserisci il seguente codice all'interno:
@ -79,7 +80,7 @@ read_globals = {
Poi, avrai bisogno di assicurarti che funzioni eseguendo LuaCheck: dovresti ottenere molti meno errori questa volta.
Partendo dal primo errore, modifica il codice per risolvere il problema, o modifica la configurazione di LuaCheck se il codice è corretto.
Dài un occhio alla lista sottostante.
Dai un occhio alla lista sottostante.
### Risoluzione problemi
@ -95,7 +96,46 @@ Dài un occhio alla lista sottostante.
È caldamente consigliato installare un'estensione per il tuo editor di fiducia che ti mostri gli errori senza eseguire alcun comando.
Queste sono disponibili nella maggior parte degli editor, come:
* **Atom** - `linter-luacheck`;
* **VSCode** - Ctrl+P, poi incolla: `ext install dwenegar.vscode-luacheck`;
* **Sublime** - Installala usando package-control:
[SublimeLinter](https://github.com/SublimeLinter/SublimeLinter),
[SublimeLinter-luacheck](https://github.com/SublimeLinter/SublimeLinter-luacheck).
## Controllare i commit con Travis
Se il tuo progetto è pubblico ed è su Github, puoi usare TravisCI - un servizio gratuito per eseguire controlli sui commit.
Questo significa che ogni commit pushato verrà controllato secondo le impostazioni di LuaCheck, e una spunta verde o una X rossa appariranno al suo fianco per segnalare se sono stati trovati errori o meno.
Ciò è utile soprattutto per quando il tuo progetto riceve una richiesta di modifica (*pull request*) per verificare se il codice è scritto bene senza doverlo scaricare.
Prima di tutto, vai su [travis-ci.org](https://travis-ci.org/) ed esegui l'accesso con il tuo account Github.
Dopodiché cerca la repo del tuo progetto nel tuo profilo Travis, e abilita Travis cliccando sull'apposito bottone.
Poi, crea un file chiamato `.travis.yml` con il seguente contenuto:
```yml
language: generic
sudo: false
addons:
apt:
packages:
- luarocks
before_install:
- luarocks install --local luacheck
script:
- $HOME/.luarocks/bin/luacheck .
notifications:
email: false
```
Se il tuo progetto è un gioco piuttosto che una mod o un pacchetto di mod, cambia la riga dopo `script:` con:
```yml
- $HOME/.luarocks/bin/luacheck mods/
```
Ora esegui il commit e il push su Github.
Vai alla pagina del tuo progetto e clicca su "commits".
Dovresti vedere un cerchietto arancione di fianco al commit che hai appena fatto.
Dopo un po' di tempo il cerchietto dovrebbe cambiare in una spunta verde o in una X rossa (a seconda dell'esito, come detto prima).
In entrambi i casi, puoi cliccare l'icona per vedere il resoconto dell'operazione e l'output di LuaCheck.

View File

@ -10,15 +10,17 @@ redirect_from: /it/chapters/readmore.html
Dopo aver letto questo libro, se mastichi l'inglese dai un occhio a ciò che segue:
### Moddaggio di Minetest
### Modding di Minetest
* Riferimento alla API Lua di Minetest - [versione interattiva](https://minetest.gitlab.io/minetest/) |
[versione su pagina singola](https://github.com/minetest/minetest/blob/master/doc/lua_api.md).
* Riferimento alla API Lua di Minetest - [versione HTML]({{ page.root }}/lua_api.html) |
[versione solo testo](https://github.com/minetest/minetest/blob/master/doc/lua_api.txt).
* Esplora la [Wiki Sviluppatore](http://dev.minetest.net/Main_Page).
* Spulcia le [mod esistenti](https://forum.minetest.net/viewforum.php?f=11).
### Programmazione in Lua
* [Programmazione in Lua (PIL)](http://www.lua.org/pil/).
* [Corso accelerato su Lua](http://luatut.com/crash_course.html).
### Modellazione 3D

View File

@ -11,20 +11,21 @@ redirect_from: /it/chapters/releasing.html
Rilasciare (o pubblicare) una mod permette ad altre persone di poterne usufruire.
Una volta che una mod è stata rilasciata potrebbe venir usata nelle partite locali (a giocatore singolo) o nei server, inclusi quelli pubblici.
- [Scegliere una licenza](#scegliere-una-licenza)
- [Scegliere la licenza](#scegliere-la-licenza)
- [LGPL e CC-BY-SA](#lgpl-e-cc-by-sa)
- [CC0](#cc0)
- [MIT](#mit)
- [Impacchettare](#impacchettare)
- [README.txt](#readmetxt)
- [mod.conf / game.conf](#modconf--gameconf)
- [description.txt](#descriptiontxt)
- [screenshot.png](#screenshotpng)
- [Caricare](#caricare)
- [Sistemi di Controllo Versione](#sistemi-di-controllo-versione)
- [Rilasciare su ContentDB](#rilasciare-su-contentdb)
- [Allegati sul forum](#allegati-sul-forum)
- [Creare la discussione sul forum](#creare-la-discussione-sul-forum)
- [Titolo](#titolo)
## Scegliere una licenza
## Scegliere la licenza
Avrai bisogno di specificare una licenza per la tua mod.
Questo è importante perché dice alle altre persone cosa possono e non possono fare col tuo lavoro.
@ -36,9 +37,6 @@ Puoi adottare la licenza che preferisci; tuttavia, sappi che le mod con licenze
Tieni anche a mente che **la licenza di pubblico dominio non è una licenza valida**, perché la sua definizione varia da stato a stato.
È importante sottolineare che la WTFPL (*do What The Fuck you want to Public License*, la "facci il cazzo che ti pare")
[è caldamente *s*consigliata](https://content.minetest.net/help/wtfpl/), e alcune persone potrebbero decidere di non usare la tua mod se ha questa licenza.
### LGPL e CC-BY-SA
Questa è la combinazione più comune nella comunità di Minetest, nonché quella usata sia da Minetest che da Minetest Game.
@ -51,7 +49,9 @@ Ciò significa che:
### CC0
Queste licenze possono essere usate sia per il codice che per contenuti artistici, permettendo a chiunque di fare quello che gli va - incluso il non citare l'autore.
Queste licenze permettono a chiunque di fare quello che gli va - incluso il non citare l'autore - e possono essere usate sia per il codice che per i contenuti artistici.
È importante sottolineare che la WTFPL (*do What The Fuck you want to Public License*, la "facci il cazzo che ti pare") è caldamente *s*consigliata, e alcune persone potrebbero decidere di non usare la tua mod se ha questa licenza.
### MIT
@ -60,80 +60,119 @@ La differenza con la LGPL è che le copie derivate in questo caso non devono per
## Impacchettare
Ci sono alcuni file che è consigliato includere nelle proprie mod e nei propri giochi prima di rilasciarli.
Ci sono alcuni file che è consigliato includere nella propria mod prima di rilasciarla.
### README.txt
Il README dovrebbe dichiarare:
* Cosa fa la mod/gioco;
* Come si usa;
* Cosa fa la mod;
* Che licenza ha;
* Quali dipendenze richiede;
* Come installare la mod;
* Versione corrente della mod;
* Eventualmente, dove segnalare i problemi o comunque richiedere aiuto.
### mod.conf / game.conf
### description.txt
Assicurati di aggiungere una descrizione che spieghi cosa fa la mod o il gioco, usando la chiave `description`.
Questo file spiega cosa fa la mod.
Cerca di essere preciso e coinciso: dovrebbe essere breve perché il contenuto verrà mostrato nell'installer del motore di gioco, che ha uno spazio limitato.
Per esempio, consigliato:
description = Aggiunge zuppa, torte, pane e succhi
Aggiunge zuppa, torte, pane e succhi.
Sconsigliato:
description = Cibo per Minetest
Cibo per Minetest.
### screenshot.png
Gli screenshot dovrebbero essere in proporzione 3:2 e avere una grandezza minima di 300x200px.
Lo screen verrà mostrato all'interno di Minetest come anteprima del contenuto.
Lo screen verrà mostrato nel bazar delle mod (sono tutte gratuite).
## Caricare
Per far sì che un potenziale utente possa scaricare la tua mod, c'è bisogno di caricarla in uno spazio pubblico.
Ci sono svariati modi per fare ciò quindi usa l'approccio che ritieni più opportuno; l'importante è che esso rispetti i requisiti qui elencati, ed eventuale richieste aggiuntive dei moderatori del forum:
Ci sono svariati modi per fare ciò quindi usa l'approccio che ritieni più opportuno; l'importante è che esso rispetti i requisiti qui elencati, ed eventuale richieste aggiuntive aggiunta dai moderatori del forum:
* **Stabile** - Il sito che conterrà il file non dovrebbe essere propenso a chiudere i battenti da un momento all'altro senza preavviso;
* **Link diretto** - Dovresti essere in grado di cliccare su un link e scaricare il file senza il bisogno di dover passare per altre pagine;
* **Senza virus** - Caricamenti su siti sospetti potrebbero contenere materiali non sicuri.
ContentDB soddisfa questi requisiti, richiedendo giusto un file .zip.
* **Stabile** - Il sito che conterrà il file non dovrebbe essere propenso a morire da un momento all'altro senza preavviso;
* **Link diretto** - Dovresti essere in grado di cliccare su un link sul forum e scaricare il file senza il bisogno di dover passare per altre pagine;
* **Senza virus** - Le mod con contenuti malevoli saranno rimosse dal forum.
### Sistemi di Controllo Versione
Un Sistema di Controllo Versione (VCS, *Version Control System*) è un programma che gestisce i cambiamenti di altri programmi, spesso facilitandone la distribuzione e la collaborazione.
È consigliato usare un sistema di controllo versione che:
La maggior parte dei creatori di mod su Minetest usa Git e un sito per ospitare il loro codice come GitHub o GitLab.
* Permetta agli altri sviluppatori di inviare le loro modifiche facilmente;
* Permetta al codice di essere visualizzato prima di essere scaricato;
* Permetta agli utenti di fare segnalazioni (bug, domande ecc).
Usare git può essere difficile all'inizio.
La maggior parte dei creatori di mod su Minetest usa GitHub o GitLab come sito per ospitare il loro codice, ma esistono anche altre opzioni.
Usare siti come GitHub e GitLab può essere difficile all'inizio.
Se hai bisogno di una mano e mastichi l'inglese, prova a dare un occhio a [Pro Git book](http://git-scm.com/book/en/v1/Getting-Started) - gratis da leggere online.
## Rilasciare su ContentDB
### Allegati sul forum
ContentDB è il luogo ufficiale dove trovare e distribuire mod, giochi e pacchetti texture.
Gli utenti possono manualmente andare alla ricerca di contenuti tramite il sito, o scaricarli e installarli direttamente dall'integrazione presente nel menù principale di Minetest.
Un'alternativa all'usare un sistema di controllo versione è il caricare le mod come allegati sul forum.
Questo può essere fatto alla creazione della discussione nella sezione delle mod (spiegato sotto).
Iscriviti su [ContentDB](https://content.minetest.net) e aggiungi il tuo lavoro.
Assicurati di leggere le linee guida (in inglese) nella sezione d'aiuto (*Help*).
Prima di tutto, avrai bisogno di creare uno zip con i file della mod (il procedimento varia da sistema operativo a sistema operativo, ma solitamente si parla di premere il tasto destro su uno dei file dopo averli selezionati tutti).
Quando crei una discussione sul forum - nella pagina "Create a Topic" illustrata sotto - vai alla "Upload Attachment" situata in basso.
Clicca poi su "Browse", selezionando il file zip.
È inoltre consigliato specificare la versione della mod nel campo dei commenti ("File comment").
<figure>
<img src="{{ page.root }}/static/releasing_attachments.png" alt="Upload Attachment">
<figcaption>
La scheda Upload Attachment.
</figcaption>
</figure>
## Creare la discussione sul forum
Puoi anche creare una discussione sul forum per far in modo che gli utenti possano discutere ciò che hai fatto.
Per le mod usa la sezione ["WIP Mods"](https://forum.minetest.net/viewforum.php?f=9) (*Work In Progress*), per i giochi invece ["WIP Games"](https://forum.minetest.net/viewforum.php?f=50).
Puoi ora creare la discussione nella sezione ["WIP Mods"](https://forum.minetest.net/viewforum.php?f=9) (WIP sta per *Work In Progress*, lavori in corso).\\
Quando ritieni che la tua mod abbia raggiunto la sua prima versione ufficiale, puoi [richiedere (in inglese) che venga spostata](https://forum.minetest.net/viewtopic.php?f=11&t=10418) in "Mod Releases".
La discussione dovrebbe contenere contenuti simili al README, con giusto un po' più di coinvolgimento e il link per scaricare la mod.
È buona cosa aggiungere anche degli eventuali screenshot per far capire al volo cosa fa la mod, se possibile.
La formattazione del forum di Minetest è in bbcode.
Segue un esempio di una mod chiamata "superspecial" (si è tenuto l'esempio in inglese dato che bisogna scrivere appunto in inglese sul forum, NdT):
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]
Se hai intenzione di usare questo esempio per la tua mod, ricordati ovviamente di cambiare "superspecial" con il nome vero e proprio.
### Titolo
Il titolo della discussione deve seguire uno dei seguenti formati:
* [Mod] Nome mod [nomemod]

View File

@ -12,7 +12,7 @@ La sicurezza è molto importante per evitare che una mod permetta di far perdere
- [Concetti fondamentali](#concetti-fondamentali)
- [Formspec](#formspec)
- [Non fidarsi mai dei campi dei formspec](#non-fidarsi-mai-dei-campi-dei-formspec)
- [Il momento per controllare non è il momento dell'uso (Time of Check is not Time of Use)](#il-momento-per-controllare-non-è-il-momento-delluso-time-of-check-is-not-time-of-use)
- [Il momento per controllare non è il momento dell'uso (Time of Check is not Time of Use)](#il-momento-per-controllare-non-e-il-momento-delluso-time-of-check-is-not-time-of-use)
- [Ambienti (non sicuri)](#ambienti-non-sicuri)
## Concetti fondamentali
@ -33,7 +33,7 @@ Qualsiasi utente può inviare qualsiasi formspec con i valori che preferisce qua
Segue del codice trovato realmente in una mod:
```lua
core.register_on_player_receive_fields(function(player,
minetest.register_on_player_receive_fields(function(player,
formname, fields)
for key, field in pairs(fields) do
local x,y,z = string.match(key,
@ -71,7 +71,7 @@ Minetest permette alle mod di richiedere ambienti senza limiti, dando loro acces
Riesci a individuare la vulnerabilità in questo pezzo di codice??
```lua
local ie = core.request_insecure_environment()
local ie = minetest.request_insecure_environment()
ie.os.execute(("path/to/prog %d"):format(3))
```

View File

@ -1,153 +0,0 @@
---
title: Traduzione
layout: default
root: ../..
idx: 8.05
marked_text_encoding:
level: info
title: Marked Text Encoding
message: |
Non hai davvero bisogno di capire come funziona il testo formattato, ma potrebbe aiutarti a capire meglio.
```
"\27(T@miamod)Hello everyone!\27E"
```
* `\27` è il carattere di escape - è usato per dire a Minetest di far attenzione, in quanto sta per seguire qualcosa di speciale. È usato sia per le traduzioni che per la colorazione del testo.
* `(T@miamod)` dice che il testo a seguire è traducibile usando il dominio testuale di `miamod`.
* `Hello everyone!` è il testo in inglese da tradurre, passato alla funzione di traduzione.
* `\27E` è di nuovo il carattere di escape, dove `E` è usato per segnalare che si è arrivati alla fine.
---
## Introduzione <!-- omit in toc -->
Aggiungere il supporto per le traduzioni nelle tue mod e giochi dà la possibilità a più persone di gustarsele.
Stando a Google Play, il 64% dei giocatori di Minetest Android non usano l'inglese come prima lingua.
Per quanto Minetest non tenga traccia di questo parametro nelle altre piattaforme, vien comunque da sé pensare che una buona parte di giocatrici e giocatori non siano madrelingua inglesi.
Minetest ti permette di localizzare i tuoi contenuti in tante lingue diverse, chiedendoti il testo base in inglese, seguito da dei file di traduzione che mappano le parole/frasi equivalenti nelle altre lingue. Questo processo di traduzione è fatto a lato client, cosicché ogni giocatore possa vedere il contenuto nella propria lingua (se disponibile).
- [Come funziona la traduzione lato client?](#come-funziona-la-traduzione-lato-client)
- [Testo formattato](#testo-formattato)
- [File di traduzione](#file-di-traduzione)
- [Formattare una stringa](#formattare-una-stringa)
- [Buona prassi per una buona traduzione](#buona-prassi-per-una-buona-traduzione)
- [Traduzioni lato server](#traduzioni-lato-server)
- [Per concludere](#per-concludere)
## Come funziona la traduzione lato client?
### Testo formattato
Il server ha bisogno di dire ai client come tradurre il testo.
Questo accade grazie alla funzione `core.get_translator(dominiotestuale)`, che abbrevieremo con `S()`:
```lua
local S = core.get_translator("miamod")
core.register_craftitem("miamod:oggetto", {
description = S("My Item"),
})
```
Il primo parametro di `get_translator` è il dominio testuale, che funge da [spazio dei nomi](https://it.wikipedia.org/wiki/Namespace).
Piuttosto che avere tutte le traduzioni di una lingua salvate nello stesso file, queste possono essere suddivise in domini testuali (un file per dominio per lingua).
È buona norma assegnare al dominio testuale lo stesso nome della mod, onde evitare conflitti tra mod diverse.
Il testo formattato può essere usato nella maggior parte dei casi dove è richiesto un testo fatto per gli esseri umani - come i formspec, i campi di definizioni di un oggetto, `infotext` ecc.
Nel caso dei formspec, tieni presente che dovrai usare la funzione di escape `core.formspec_escape` per una corretta visualizzazione.
Quando il client incontra del testo formattato, come quello passato in `description`, ne andrà a cercare il corrispettivo nel file di traduzione della lingua del giocatore. Se la ricerca non avrà avuto esito positivo, ritornerà quello in inglese.
Nel testo formattato sono presenti il testo sorgente in inglese, il dominio testuale, e qualsivoglia altro parametro passato a `S()`.
Essenzialmente, è una codifica testuale della chiamata a `S` che contiene tutte le informazioni necessarie.
{% include notice.html notice=page.marked_text_encoding %}
### File di traduzione
I file di traduzione sono file che possono essere trovati nella cartella `locale` di ogni mod.
Al momento, l'unico formato supportato è `.tr`, ma è probabile che altri formati più tipici saranno aggiunti in futuro.
I file di traduzione devono essere nominati nel seguente modo: `[dominiotestuale].[codicelingua].tr`.
I file `.tr` iniziano con un commento che ne specifica il dominio, seguito poi da righe che mappano il testo originale in inglese nella lingua del file.
Per esempio, `miamod.it.tr`:
```
# textdomain: miamod
Hello everyone!=Ciao a tutti!
I like grapefruit=Mi piace il pompelmo
```
Dovresti creare dei file di traduzione basati sul codice sorgente delle tue mod/giochi, usando uno strumento come [update_translations](https://github.com/minetest-tools/update_translations).
Questo cercherà tutte le occorrenze di `S(` nel tuo codice Lua, creando in automatico un modello che traduttrici e traduttori potranno usare per tradurre nella loro lingua.
Inoltre, si prenderà cura di aggiornare i file di traduzione ogniqualvolta verranno effettuate modifiche al codice.
## Formattare una stringa
Non è raro dover inserire una variabile dentro una stringa da tradurre.
È importante che il testo non sia semplicemente concatenato, in quanto impedirebbe a chi traduce di cambiare l'ordine delle variabili all'interno della frase.
Al contrario, dovresti usare il seguente sistema di formattazione:
```lua
core.register_on_joinplayer(function(player)
core.chat_send_all(S("Everyone, say hi to @1!", player:get_player_name()))
end)
```
Se vuoi scrivere letteralmente `@` nella tua frase, dovrai usare una sequenza di escape scrivendo `@@`.
Dovresti evitare di concatenare stringhe *all'interno* di una frase; piuttosto, sarebbe meglio concatenare più frasi come da esempio, in quanto permette a chi traduce di lavorare su stringhe più piccole:
```lua
S("Hello @1!", player_name) .. " " .. S("You have @1 new messages.", #msgs)
```
## Buona prassi per una buona traduzione
* Evita di concatenare il testo, optando invece per formattare le stringhe. Questo permette a chi traduce di avere pieno controllo sull'ordine degli elementi;
* Crea i file di traduzione in automatico usando [update_translations](https://github.com/minetest-tools/update_translations);
* È cosa comune che le variabili cambino il testo circostante, per esempio tramite genere e numero. Risulta spesso difficile trovare qualcosa che si sposi bene in tutti i casi, perciò si tende a cambiare la struttura della frase in modo che risulti sempre corretta ("Hai ottenuto 3 mele" -> "Hai ottenuto mela (x3)");
* Le traduzioni potrebbero essere molto più lunghe o molto più corte rispetto all'originale. Assicurati di lasciare sempre un po' di respiro;
* Non tutte le lingue scrivono i numeri nella stessa maniera, come per esempio `1.000` e `1'000`;
* Non dar per scontato che le altre lingue usino le maiscuole nella stessa maniera della tua.
## Traduzioni lato server
Certe volte ti capiterà di voler sapere quale traduzione di una tal stringa stia venendo visualizzata dal giocatore.
Puoi usare `get_player_information` per ottenere la lingua utilizzata e `get_translated_string` per tradurne il testo formattato.
```lua
local list = {
S("Hello world!"),
S("Potato")
}
core.register_chatcommand("find", {
func = function(name, param)
local info = core.get_player_information(name)
local lingua = info and info.language or "en"
for _, riga in ipairs(lista) do
local trad = core.get_translated_string(language, riga)
if trad:contains(query) then
return riga
end
end
end,
})
```
## Per concludere
Se ben gestita, l'API per le traduzioni permette di rendere mod e giochi più accessibili.
Si tenga comunque conto che Minetest è in continua evoluzione e che l'API verrà probabilmente ampliata in futuro.
Per esempio, il supporto per i file di traduzione *gettext* permetterà l'utilizzo di piattaforme e strumenti consolidati come Weblate, mentre nel frattempo si sta lavorando al supporto per il genere e il numero.

View File

@ -15,8 +15,9 @@ Scrivere i testing d'unità per le funzioni dove vengono chiamate quelle di Mine
- [Il tuo primo test](#il-tuo-primo-test)
- [init.lua](#initlua)
- [api.lua](#apilua)
- [tests/api_spec.lua](#testsapi_speclua)
- [tests/api_spec.lua](#testsapispeclua)
- [Simulare: usare funzioni esterne](#simulare-usare-funzioni-esterne)
- [Controllare commit con Travis](#controllare-commit-con-travis)
- [Conclusione](#conclusione)
## Installare Busted
@ -52,7 +53,7 @@ Quello che fa è cercare i file Lua con il nome che termina in `_spec`, eseguend
```lua
miamod = {}
dofile(core.get_modpath("miamod") .. "/api.lua")
dofile(minetest.get_modpath("miamod") .. "/api.lua")
```
@ -112,7 +113,7 @@ _G.minetest = {}
-- Definisce la funzione simulata
local chiamate_chat_send_all = {}
function core.chat_send_all(name, message)
function minetest.chat_send_all(name, message)
table.insert(chiamate_chat_send_all, { nome = name, messaggio = message })
end
@ -153,8 +154,29 @@ end)
```
## Controllare commit con Travis
Lo script di Travis usato nel capitolo [Controllo automatico degli errori](luacheck.html) può essere modificato per eseguire (anche) 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
```
## Conclusione
I testing d'unità aumenteranno notevolmente la qualità e l'affidabilità di un progetto se usati adeguatamente, ma ti richiederanno di strutturare il codice in maniera diversa dal solito.
Per un esempio di mod con molti testing d'unità, vedere la mod [*crafting* di rubenwardy](https://github.com/rubenwardy/crafting).
Per un esempio di mod con molti testing d'unità, vedere la [crafting di rubenwardy](https://github.com/rubenwardy/crafting).

View File

@ -3,50 +3,18 @@ layout: compress
---
<!doctype html>
{% assign pathsplit = page.url | split: '/' %}
{% assign language = pathsplit[1] %}
{% assign language_info = site.data.languages | where: "code", language %}
{% if language_info %}
<html lang="{{ language }}">
{% else %}
<html>
{% endif %}
<head>
<title>{% if page.homepage %}{% else %}{{ page.title }} - {% endif %}Luanti / Minetest Modding Book</title>
<title>{% if page.homepage %}{% else %}{{ page.title }} - {% endif %}Minetest Modding Book</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width">
<meta name="description" content="An easy guide to learn how to create mods for Minetest">
<meta name="keywords" content="Minetest, modding, book, tutorial, guide, easy">
<meta name="author" content="rubenwardy">
<meta name="flattr:id" content="gl763e">
<link rel="canonical" href="https://rubenwardy.com/minetest_modding_book{{ page.url }}">
<meta name="og:url" content="https://rubenwardy.com/minetest_modding_book{{ page.url }}">
<meta name="og:title" content="{{ page.title | escape }}">
<meta name="og:author" content="rubenwardy">
<meta name="og:site_name" content="Luanti Modding Book (formerly Minetest)">
{% if page.description %}
<meta name="og:description" content="{{ page.description | escape | strip }}">
<meta name="description" content="{{ page.description | escape | strip }}">
{% endif %}
{% if page.image %}
<meta name="og:image" content="{{ page.image | absolute_url }}">
{% endif %}
{% assign oldSegment = "/" | append: language | append: "/" %}
{% for other_lang in site.data.languages %}
{% unless other_lang.code == language %}
{% assign newSegment = "/" | append: other_lang.code | append: "/" %}
<link rel="alternate" hreflang="{{ other_lang.code }}"
href="{{ page.url | replace: oldSegment, newSegment | relative_url }}">
{% endunless %}
{% endfor %}
{% if page.noindex %}
<meta name="robots" content="noindex">
{% endif %}
<style>body,html,nav{background:#333}nav,nav li,nav li a{display:block}body,html,main,nav li{margin:0;padding:0}main,nav{position:absolute;top:0}body,html{font-size:17px;color:#000}#container{width:100%;max-width:1100px;margin:auto;position:relative}nav{left:0;width:280px;list-style:none;color:#fff}nav li a{padding:5px;color:#ccc;text-decoration:none}main{left:280px;right:0}article{background:#fff;padding:0 20px 20px}</style>
<link rel="stylesheet" href="{{ page.root }}/static/style.css?v=4">
<link rel="stylesheet" href="{{ page.root }}/static/style.css?v=2">
</head>
<body>
<div id="container">

View File

@ -8,12 +8,11 @@ layout: base
{% if language == "_it" %}
{% assign language = "it" %}
{% assign links = site.it %}
{% assign links = site.it | sort: "idx" %}
{% else %}
{% assign language = "en" %}
{% assign links = site.en %}
{% assign links = site.en | sort: "idx" %}
{% endif %}
{% assign links = links | where_exp: "item", "item.sitemap != false" | sort: "idx" %}
{% assign num = 0 %}
@ -32,7 +31,9 @@ layout: base
{% assign last_section = section %}
{% assign num = num | plus:1 %}
{% endfor %}
<li><a href="https://github.com/rubenwardy/minetest_modding_book/archive/examples.zip" class="hr">Download Examples</a></li>
<li><a href="{{ page.root }}/lua_api.html" class="hr">Lua Modding API Reference</a></li>
<li><a href="https://github.com/rubenwardy/minetest_modding_book/archive/examples.zip">Download Examples</a></li>
</nav>
<main>
@ -64,7 +65,7 @@ layout: base
</ul>
<footer>
&copy; 2014-{{ site.time | date: '%Y' }}
&copy; 2014-20
{% if language == "en" %}
| Helpful? Consider
<a href="https://rubenwardy.com/donate/">donating</a>

View File

@ -33,6 +33,68 @@ figure {
padding: 0 0 0 6px;
}
.notice-info {
background: #ececec !important;
border: 1px solid #aaa !important;
}
.notice-danger {
background: #fcc !important;
border: 1px solid #a66 !important;
}
.notice-warning {
background: #FED;
border: 1px solid #fc9;
}
.notice-tip {
background: #ccf;
border: 1px solid #66a;
}
.notice-green {
background: #161;
border: 1px solid #393;
}
.notice {
margin: 10px;
display: block;
padding: 5px;
border-radius: 5px;
position: relative;
}
.notice p {
margin: 0 0 17px 0;
}
.notice p:last-child {
margin: 0;
}
.notice > span {
position: absolute;
top: 0;
left: 0;
bottom: 0;
width: 40px;
font-size: 24px;
text-align: center;
display: block;
}
.notice > div {
margin-left: 35px;
}
.notice h2 {
margin: 0 0 5px 0;
padding: 0 0 2px 0;
font-size: 100%;
}
.header-link, .anchor {
text-decoration: none;
color: #bbb;
@ -56,7 +118,7 @@ h1 {
}
h2 {
border-bottom: 1px solid #bbb;
border-bottom: 1px solid black;
margin: 30px 0 10px 0;
display: block;
padding: 0 0 5px 0;

View File

@ -1,121 +0,0 @@
.feedback {
background: white;
padding: 1em;
input[name='username'], label[for='username'] {
display: none;
}
h2 {
border: none;
margin-top: 0;
}
}
.btn {
--color-primary-dark: #007DB8;
--color-primary-dark-highlight: #06aed5;
display: inline-block;
padding: 0.375rem 0.75rem;
margin: 0.25rem 0.25rem 0.25rem 0;
font-size: 0.9375rem;
line-height: 1.5;
border-radius: 0.25rem;
background: transparent;
border: 1px solid transparent;
color: white;
transition: color 0.15s ease-in-out, filter 0.15s ease-in-out,
background-color 0.15s ease-in-out, border-color 0.15s ease-in-out,
box-shadow 0.15s ease-in-out;
cursor: pointer;
box-sizing: border-box;
text-align: center;
&:hover {
color: white;
text-decoration: none;
background-color: rgba(255, 255, 255, 0.25);
}
&.active {
color: var(--color-primary-dark);
}
img.icon {
width: 100%;
height: 100%;
object-fit: contain;
}
}
.btn-primary {
background-color: var(--color-primary-dark);
border-color: var(--color-primary-dark);
&:hover {
background-color: var(--color-primary-dark-highlight);
border-color: var(--color-primary-dark-highlight);
}
}
.form-group {
margin-bottom: 1rem;
}
button, input {
overflow: visible;
}
input, button, select, optgroup, textarea {
margin: 0;
font-family: inherit;
font-size: inherit;
line-height: inherit;
box-sizing: border-box;
}
label {
display: inline-block;
margin-bottom: 0.5rem;
}
.form-control {
display: block;
width: 100%;
height: calc(1.5em + 1.5rem + 2px);
padding: 0.75rem 1rem;
font-size: 0.9375rem;
font-weight: 400;
line-height: 1.5;
color: #52575C;
background-color: #fff;
background-clip: padding-box;
border: 1px solid #ced4da;
border-radius: 0.25rem;
transition: border-color 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out;
transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out;
}
.text-muted {
color: #7A8288 !important;
}
.form-text {
display: block;
margin-top: 0.25rem;
}
small, .small {
font-size: 80%;
font-weight: 400;
}
textarea.form-control {
height: auto;
}
textarea {
overflow: auto;
resize: vertical;
}

View File

@ -198,10 +198,3 @@ header span {
position: relative;
}
}
@import "content";
@import "code";
@import "notice";
@import "table";
@import "feedback";

View File

@ -1,61 +0,0 @@
.notice {
margin: 2em 0;
display: block;
padding: 0.5rem;
border-radius: 0.5rem;
position: relative;
p {
margin: 0 0 1em 0;
}
p:last-child {
margin: 0;
}
& > span {
position: absolute;
top: 0;
left: 0;
bottom: 0;
width: 40px;
font-size: 24px;
text-align: center;
display: block;
}
& > div {
margin-left: 35px;
}
h2 {
margin: 0 0 5px 0;
padding: 0 0 2px 0;
font-size: 100%;
}
}
.notice-info {
background: #ececec !important;
border: 1px solid #aaa !important;
}
.notice-danger {
background: #fcc !important;
border: 1px solid #a66 !important;
}
.notice-warning {
background: #FED;
border: 1px solid #fc9;
}
.notice-tip {
background: #ccf;
border: 1px solid #66a;
}
.notice-green {
background: #161;
border: 1px solid #393;
}

View File

@ -1,17 +0,0 @@
table {
width: 100%;
border-collapse: collapse;
}
table td, table th {
border: 1px solid #ddd;
padding: 8px;
}
table tr:nth-child(even) {
background-color: #f2f2f2;
}
table th {
padding: 12px 8px;
text-align: left;
background-color: #04AA6D;
color: white;
}

14
cat.html Normal file
View File

@ -0,0 +1,14 @@
---
layout: none
---
{% if site.calc_word_count %}
{% assign chapters = site.en | sort: "idx" %}
{% for chapter in chapters %}
{% unless chapter.homepage %}
{{ chapter.content }}
{% endunless %}
{% endfor %}
{% else %}
Cat disabled.
{% endif %}

View File

@ -1,29 +0,0 @@
---
title: Thanks for your feedback
layout: base
root: .
sitemap: false
noindex: true
---
<main>
<article>
<h1>Luanti Modding Book (formerly Minetest)</h1>
<h2>Thanks for sharing your feedback!</h2>
<p>
You're helping to make the modding book better.
</p>
<p>
<a href="{{ '/index.html' | relative_url }}">
Back to the book
</a>
</p>
</article>
<footer>
&copy; 2014-{{ site.time | date: '%Y' }}
</footer>
</main>

View File

@ -1,13 +1,15 @@
---
layout: none
---
<!doctype html>
<html>
<head>
<title>Luanti Modding Book (formerly Minetest)</title>
<meta name="og:description" content="An easy guide to learn how to create mods for Minetest">
<meta name="description" content="An easy guide to learn how to create mods for Minetest">
<link rel="canonical" href="https://rubenwardy.com/minetest_modding_book/">
<title>Redirecting...</title>
</head>
<body>
Detecting and redirecting to the correct translation.<br><br>
<a href="en/index.html">View English Translation</a><br><br>
<script>
var languages = {{ site.data.languages | jsonify }};
function getLanguage() {
@ -22,18 +24,12 @@ layout: none
}
var language = getLanguage() || languages[0];
window.location.replace(language.code + "/index.html");
var url = language.code + "/index.html";
document.write('<a href="' + language.code +
'/index.html">Redirecting to the ' + language.name +
' version...</a>');
window.location.replace(url);
</script>
<meta http-equiv="refresh" content="0;URL='{{ 'en/index.html' | absolute_url }}'" />
</head>
<body>
<main>
<h1>Luanti Modding Book (formerly Minetest)</h1>
<p>An easy guide to learn how to create mods for Minetest.</p>
<p>Detecting and redirecting to the correct translation.</p>
<p>
<a href="en/index.html">View Luanti Modding Book in English</a>
</p>
</main>
</body>
</html>

View File

@ -6,7 +6,7 @@ root: .
<main>
<article>
<h1>Luanti Modding Book (formerly Minetest)</h1>
<h1>Minetest Modding Book</h1>
<h2>Choose a Language</h2>
@ -19,6 +19,6 @@ root: .
</article>
<footer>
&copy; 2014-{{ site.time | date: '%Y' }}
&copy; 2014-20
</footer>
</main>

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,7 @@
---
---
{% assign pages = site.en | where_exp: "item", "item.sitemap != false" | sort: "idx" %}
{% assign pages = site.en | sort: "idx" %}
{% assign num = 0 %}
[
@ -12,7 +12,7 @@
{
"idx": {{ link.idx }},
"title": "{{ link.title }}",
"loc": "https://rubenwardy.com/minetest_modding_book{{ link.url }}",
"loc": "https://rubenwardy.com/minetest_modding_book/{{ link.url }}",
{% if link.description %}
"description": "{{ link.description }}",
{% endif %}
@ -22,6 +22,14 @@
{% assign num = num | plus:1 %}
{% endfor %}
{
"title": "Lua Modding API Reference",
"loc": "https://rubenwardy.com/minetest_modding_book/lua_api.html",
"description": "lua_api.html is an HTML version of lua_api.txt",
"priority": 0.75
},
{
"title": "Download Examples",
"loc": "https://github.com/rubenwardy/minetest_modding_book/archive/examples.zip",

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

View File

@ -2,3 +2,5 @@
---
@import "main";
@import "content";
@import "code";

2
utils/requirements.txt Normal file
View File

@ -0,0 +1,2 @@
markdown==2.6.8
bs4==0.0.1

112
utils/update_lua_api.py Normal file
View File

@ -0,0 +1,112 @@
import markdown, datetime, re, string
from bs4 import BeautifulSoup
from urllib.request import urlopen
def get_key(index, li, title):
title = title.replace(" ", "-")
title = title.replace(" ", "-")
title = title.replace(" ", "-")
#title = re.sub(r'\([^)]*\)', '', title)
title = pattern.sub('', title)
if title == "":
return None
i = 0
while True:
key = title
if i > 0:
key = key + "_" + str(i)
i = i + 1
try:
existing = index[key]
except KeyError:
return key
#
# Downloading lua_api.txt
#
print("Downloading lua_api.txt...")
url = "https://raw.githubusercontent.com/minetest/minetest/master/doc/lua_api.txt"
text = urlopen(url).read().decode("utf-8")
print("Pre-generation replacements...")
header = """Minetest Lua Modding API Reference
=================================="""
text = text.replace(header, "")
#
# Generating HTML
#
print("Generating HTML...")
md = markdown.Markdown(extensions=['markdown.extensions.toc'])
html = md.convert(text)
print("Post-generation replacements...")
links = """<ul>
<li>More information at <a href="http://www.minetest.net/">http://www.minetest.net/</a></li>
<li>Developer Wiki: <a href="http://dev.minetest.net/">http://dev.minetest.net/</a></li>
</ul>"""
html = html.replace("{{", "{ {")
html = html.replace(links, "")
credit = "This page was last updated "
credit += datetime.date.today().strftime("%d/%B/%Y")
credit += ".<br />See <a href=\"https://github.com/minetest/minetest/blob/master/doc/lua_api.txt\">doc/lua_api.txt</a> for the latest version (in plaintext)."
credit += "<br />Generated using <a href=\"https://github.com/rubenwardy/minetest_modding_book/blob/gh-pages/update_lua_api.py\">a Python script</a>."
links += credit
html = html.replace("<h2 id=\"programming-in-lua\">", links + "<h2 id=\"programming-in-lua\">")
print("Parsing HTML...")
soup = BeautifulSoup(html, 'html.parser')
pattern = re.compile('[\W]+')
lis = soup.find_all("li")
index = {}
# Build index of anchors
headings = soup.find_all({"h1", "h2", "h3", "h4", "h5", "h6"})
for tag in headings:
if tag.has_attr("id"):
index[tag["id"]] = True
if tag.has_attr("name"):
index[tag["name"]] = True
# Add anchors to <li>s containing <code>
for li in lis:
code = li.find_all('code')
if len(code) > 0:
key = get_key(index, li, code[0].string)
if key is not None:
index[key] = True
#print("Created " + key)
new_tag = soup.new_tag('a', href="#" + key)
new_tag['class'] = "anchor"
new_tag['name'] = key
new_tag.string = "#"
li.insert(0, new_tag)
html = str(soup)
#
# Writing to file
#
print("Writing to file...")
file = open("lua_api.html", "w")
file.write("---\ntitle: Lua Modding API Reference\nlayout: default\n---\n")
file.write("<div class='notice notice-info'>\n")
file.write("<h2>This is lua_api.txt nicely formated: I did not write this</h2>\n")
file.write(credit)
file.write("</div>\n")
file.write("<h2 id=\"table-of-contents\">Table of Contents</h2>\n")
file.write(md.toc)
file.write(html)
file.close()
print("Done")