This commit is contained in:
debiankaios 2023-04-29 17:21:18 +02:00
commit d584267768
43 changed files with 1731 additions and 1318 deletions

View File

@ -6,6 +6,7 @@ variables:
before_script: before_script:
- rm Gemfile.lock - rm Gemfile.lock
- bundle install
test: test:
stage: test stage: test
@ -21,10 +22,8 @@ test:
pages: pages:
stage: deploy stage: deploy
interruptible: true interruptible: true
before_script:
- rm Gemfile.lock
script: script:
- bundle exec jekyll build -d public - bundle exec jekyll build -d public --baseurl /minetest_modding_book
artifacts: artifacts:
paths: paths:
- public - public

View File

@ -6,4 +6,5 @@ gem "webrick"
group :jekyll_plugins do group :jekyll_plugins do
gem "jekyll-sitemap" gem "jekyll-sitemap"
gem "jekyll-redirect-from" gem "jekyll-redirect-from"
gem "jekyll-sass-converter", "~> 2.0"
end end

427
LICENSE Normal file
View File

@ -0,0 +1,427 @@
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

@ -12,18 +12,18 @@ redirect_from:
## Introduction <!-- omit in toc --> ## Introduction <!-- omit in toc -->
Understanding the basic structure of a mod's folder is an essential skill when Understanding the basic structure of a mod's folder is an essential skill when
creating mods. creating mods. In this chapter, you'll learn about how modding in Minetest works
and create your first mod.
- [What are Games and Mods?](#what-are-games-and-mods) - [What are Games and Mods?](#what-are-games-and-mods)
- [Where are mods stored?](#where-are-mods-stored) - [Where are mods stored?](#where-are-mods-stored)
- [Mod Directory](#mod-directory) - [Creating your first mod](#creating-your-first-mod)
- [mod.conf](#modconf) - [Mod directory](#mod-directory)
- [Dependencies](#dependencies) - [mod.conf](#modconf)
- [init.lua](#initlua)
- [Summary](#summary)
- [Dependencies](#dependencies)
- [Mod Packs](#mod-packs) - [Mod Packs](#mod-packs)
- [Example](#example)
- [Mod Folder](#mod-folder)
- [init.lua](#initlua)
- [mod.conf](#modconf-1)
## What are Games and Mods? ## What are Games and Mods?
@ -53,7 +53,7 @@ and is applicable for both game developers and modders.
<a name="mod-locations"></a> <a name="mod-locations"></a>
Each mod has its own directory where its Lua code, textures, models, and Each mod has its own directory where its Lua code, textures, models, and
sounds are placed. Minetest checks in a number of different locations for sounds are placed. Minetest checks in several different locations for
mods. These locations are commonly called *mod load paths*. mods. These locations are commonly called *mod load paths*.
For a given world/save game, three mod locations are checked. For a given world/save game, three mod locations are checked.
@ -78,54 +78,91 @@ 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. game mods by placing a mod with the same name in the global mod location.
## Mod Directory ## Creating your first mod
![Find the mod's directory]({{ page.root }}/static/folder_modfolder.jpg) ### Mod directory
A *mod name* is used to refer to a mod. Each mod should have a unique name. Go to the global mods directory (About > Open user data directory > mods) and
Mod names can include letters, numbers, and underscores. A good name should create a new folder called "mymod". `mymod` is the mod name.
describe what the mod does, and the directory which contains the components of a mod
must have the same name as the mod name. Each mod should have a unique *mod name*, a technical identifier (id) used to
To find out if a mod name is available, try searching for it on 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
[content.minetest.net](https://content.minetest.net). [content.minetest.net](https://content.minetest.net).
mymod mymod
├── init.lua (required) - Runs when the game loads. ├── textures
├── mod.conf (recommended) - Contains description and dependencies. │   └── mymod_node.png files
├── textures (optional) ├── init.lua
│   └── ... any textures or images └── mod.conf
├── sounds (optional)
│   └── ... any sounds
└── ... any other files or directories
Only the init.lua file is required in a mod for it to run on game load; Mods only require an init.lua file;
however, mod.conf is recommended and other components may be needed however, mod.conf is recommended and other components may be needed
depending on the mod's functionality. depending on the mod's functionality.
## mod.conf ### 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 This file is used for mod metadata including the mod's name, description, and other
information. information.
For example: ### init.lua
name = mymod Create an init.lua file with the following content:
description = Adds foo, bar, and bo.
depends = modone, modtwo
### Dependencies ```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}
})
minetest.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. 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 One mod may require another mod's code, items, or other resources to be
for it to use. available for it to use.
There are two types of dependencies: hard and optional dependencies. 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 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 available, a hard dependency will cause the mod to fail to load, while an optional
dependency might lead to fewer features being enabled. dependency might lead to fewer features being enabled.
An optional dependency is useful if you want to optionally support another mod; it can An optional dependency is useful if you want to optionally support another mod;
enable extra content if the user wishes to use both the mods at the same time. 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 are specified in a comma-separated list in mod.conf.
@ -148,43 +185,3 @@ a player, but don't want to make them download each one individually.
Please note that a modpack is not a *game*. Please note that a modpack is not a *game*.
Games have their own organisational structure which will be explained in the Games have their own organisational structure which will be explained in the
Games chapter. Games chapter.
## Example
Here is an example which puts all of this together:
### Mod Folder
mymod
├── textures
│   └── mymod_node.png files
├── init.lua
└── mod.conf
### 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}
})
minetest.register_craft({
type = "shapeless",
output = "mymod:node 3",
recipe = { "default:dirt", "default:stone" },
})
```
### mod.conf
name = mymod
descriptions = Adds a node
depends = default
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 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.

View File

@ -9,21 +9,39 @@ redirect_from: /en/chapters/lua.html
## Introduction <!-- omit in toc --> ## Introduction <!-- omit in toc -->
In this chapter we'll talk about scripting in Lua, the tools required In this chapter, you'll learn about scripting in Lua, the tools required
to assist with this, and some techniques which you may find useful. to help with this, and some techniques that you may find useful.
- [Code Editors](#code-editors)
- [Coding in Lua](#coding-in-lua)
- [Program Flow](#program-flow)
- [Variable Types](#variable-types)
- [Arithmetic Operators](#arithmetic-operators)
- [Selection](#selection)
- [Logical Operators](#logical-operators)
- [Programming](#programming) - [Programming](#programming)
- [Coding in Lua](#coding-in-lua)
- [Code Editors](#code-editors)
- [Local and Global Scope](#local-and-global-scope) - [Local and Global Scope](#local-and-global-scope)
- [Locals should be used as much as possible](#locals-should-be-used-as-much-as-possible) - [Locals should be used as much as possible](#locals-should-be-used-as-much-as-possible)
- [Including other Lua Scripts](#including-other-lua-scripts) - [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 ## Code Editors
A code editor with code highlighting is sufficient for writing scripts in Lua. A code editor with code highlighting is sufficient for writing scripts in Lua.
@ -54,156 +72,36 @@ Functions which come with Lua by default, such as `table.insert`, are also highl
Commonly used editors which are well-suited for Lua include: Commonly used editors which are well-suited for Lua include:
* [VSCode](https://code.visualstudio.com/) - * [VSCode](https://code.visualstudio.com/):
open source (as Code-OSS or VSCodium), popular, and has open source (as Code-OSS or VSCodium), popular, and has
[plugins for Minetest modding](https://marketplace.visualstudio.com/items?itemName=GreenXenith.minetest-tools). [plugins for Minetest](https://marketplace.visualstudio.com/items?itemName=GreenXenith.minetest-tools).
* [Notepad++](http://notepad-plus-plus.org/) - Windows-only * [Notepad++](http://notepad-plus-plus.org/): simple, Windows-only
* [Atom](http://atom.io/)
Other suitable editors are also available. 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, and
different types of flow allow you to skip or jump over sets of commands.
There are three main types of flow:
* Sequence: runs one statement after another, with no skipping.
* Selection: skips over sequences depending on conditions.
* Iteration: repeats 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)
```
In this example, `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 later, because it's part of a very important concept called
*scope*.
The `=` sign means *assignment*, so `result = a + b` means set the value of
`result` to the value of `a + b`. Variable names can be longer than one
character, as seen with the `result` variable. It's also worth noting that, like
most languages, 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
Operators in Lua include:
| 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" |
Please note that this is not an exhaustive list; it doesn't contain every
possible operator.
### Selection
The most basic method of selection is the if statement. For example:
```lua
local random_number = math.random(1, 100) -- Between 1 and 100.
if random_number > 50 then
print("Woohoo!")
else
print("No!")
end
```
This generates a random number between 1 and 100. It then prints "Woohoo!" if
that number is bigger than 50, and otherwise prints "No!".
### Logical Operators
Logical operators in Lua include:
| 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) |
Please note that this doesn't contain every possible operator.
It is also possible to combine operators. For example:
```lua
if not A and B then
print("Yay!")
end
```
This prints "Yay!" if A is false and B is true.
Logical and arithmetic operators work the same way; they both accept inputs and
return a value which can be stored. For example:
```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 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.\\
Scratch is *designed to teach children* how to program and isn't a serious
programming language.
## Local and Global Scope ## Local and Global Scope
Whether a variable is local or global determines where it can be written to or Whether a variable is local or global determines where it can be written to or
read from. A local variable is only accessible from where it is defined. Here read from. Global variables can be accessed from anywhere in the script file,
are some examples: 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:
```lua ```lua
-- Accessible from within this script file -- Accessible from within this script file
@ -220,21 +118,8 @@ function myfunc()
end end
``` ```
In contrast, global variables can be accessed from anywhere in the script file, and
from any other mod.
```lua ### Locals should be used as much as possible
function one()
foo = "bar"
end
function two()
print(dump(foo)) -- Output: "bar"
end
one()
two()
```
Local variables should be used whenever possible. Mods should only create one 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 global at most, with the same name as the mod. Creating other globals is sloppy
@ -284,6 +169,9 @@ end
mymod.foo("foobar") 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 ## Including other Lua Scripts
The recommended way to include other Lua scripts in a mod is to use *dofile*. The recommended way to include other Lua scripts in a mod is to use *dofile*.
@ -296,11 +184,13 @@ A script can return a value, which is useful for sharing private locals:
```lua ```lua
-- script.lua -- script.lua
return "Hello world!" local module = {}
module.message = "Hello World!"
return module
-- init.lua -- init.lua
local ret = dofile(minetest.get_modpath("modname") .. "/script.lua") local ret = dofile(minetest.get_modpath("modname") .. "/script.lua")
print(ret) -- Hello world! print(ret.message) -- Hello world!
``` ```
[Later chapters](../quality/clean_arch.html) will discuss how best to split up [Later chapters](../quality/clean_arch.html) will discuss how best to split up

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

@ -0,0 +1,206 @@
---
title: Node and Item Callbacks
layout: default
root: ../..
idx: 2.15
description: Learn about callbacks, actions, and events, including on_use, on_punch, on_place, on_rightclick
---
## Introduction <!-- omit in toc -->
Minetest heavily uses a callback-based modding design. A callback is a function
that you give to an API and is called when an event happens. For example, you
can provide an `on_punch` function in a node definition to be called when a player
punches a node. There are also global callbacks like
`minetest.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 | `minetest.item_place` |
| on_secondary_use | right-click not on a node | `minetest.item_secondary_use` (does nothing) |
| on_drop | Q | `minetest.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
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.
minetest.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
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 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
`minetest.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
`minetest.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,
`minetest.item_place` and `minetest.node_dig`. Some callback implementations are
used directly whereas some are functions that return the callback:
```lua
minetest.register_item("mymod:example", {
on_place = minetest.item_place,
on_use = minetest.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 `minetest.place_item` and
`minetest.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 `minetest.item_place`.
If the pointed node has an `on_rightclick` callback and sneak (shift) is held,
then the `on_rightclick` callback is called. Otherwise, `minetest.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
minetest.register_node("mymod:mynode", {
on_rightclick = function(pos, node, clicker, itemstack, pointed_thing)
if clicker:is_player() then
minetest.chat_send_player(clicker:get_player_name(), "Hello world!")
end
end,
on_construct = function(pos, node)
local meta = minetest.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 = minetest.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 `minetest.node_dig`, which will check for area protection, wear
out the tool, remove the node, and run the `after_dig_node` callback.
```lua
minetest.register_node("mymod:mynode", {
on_punch = function(pos, node, puncher, pointed_thing)
if puncher:is_player() then
minetest.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,8 +25,10 @@ available, which cover pixel art in much more detail.
- [Using the Pencil](#using-the-pencil) - [Using the Pencil](#using-the-pencil)
- [Tiling](#tiling) - [Tiling](#tiling)
- [Transparency](#transparency) - [Transparency](#transparency)
- [Color Palettes](#color-palettes)
- [Editors](#editors) - [Editors](#editors)
- [MS Paint](#ms-paint) - [MS Paint](#ms-paint)
- [Aseprite / LibreSprite](#aseprite--libresprite)
- [GIMP](#gimp) - [GIMP](#gimp)
## Techniques ## Techniques
@ -59,6 +61,13 @@ and some nodes, such as glass.
Not all editors support transparency, so make sure you choose an Not all editors support transparency, so make sure you choose an
editor which is suitable for the textures you wish to create. 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 ## Editors
### MS Paint ### MS Paint
@ -69,16 +78,21 @@ 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 but if you need transparency in your textures you should choose a
different editor. 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
GIMP is commonly used in the Minetest community. It has quite a high GIMP is commonly used in the Minetest community. It has quite a high
learning curve because many of its features are not immediately learning curve because many of its features are not immediately
obvious. obvious.
When using GIMP, the pencil tool can be selected from the Toolbox: 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
<figure> tool.
<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,6 +19,9 @@ that be a player inventory, a node inventory, or a detached inventory.
- [What are ItemStacks and Inventories?](#what-are-itemstacks-and-inventories) - [What are ItemStacks and Inventories?](#what-are-itemstacks-and-inventories)
- [ItemStacks](#itemstacks) - [ItemStacks](#itemstacks)
- [Inventory Locations](#inventory-locations) - [Inventory Locations](#inventory-locations)
- [Node Inventories](#node-inventories)
- [Player Inventories](#player-inventories)
- [Detached Inventories](#detached-inventories)
- [Lists](#lists) - [Lists](#lists)
- [Size and Width](#size-and-width) - [Size and Width](#size-and-width)
- [Checking Contents](#checking-contents) - [Checking Contents](#checking-contents)
@ -33,21 +36,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 ItemStack is the data behind a single cell in an inventory.
An *inventory* is a collection of *inventory lists*, each of which An *inventory* is a collection of *inventory lists*, each of which is a 2D grid
is a 2D grid of ItemStacks. of ItemStacks. Inventory lists are referred to as *lists* in the context of
Inventory lists are simply called *lists* in the context inventories.
of inventories.
The point of an inventory is to allow multiple grids when Players Players and nodes only have a single inventory; lists enable you to have
and Nodes only have at most one inventory in them. multiple grids within that inventory. By default, the player has the "main" list
for the bulk of its inventory and a few lists for the crafting system.
## ItemStacks ## ItemStacks
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 The item name may be the item name of a registered item, an alias, or an unknown
item name. item name. Unknown items are common when users uninstall mods, or when mods
Unknown items are common when users uninstall mods, or when mods remove items without remove items without precautions, such as registering aliases.
precautions, such as registering aliases.
```lua ```lua
print(stack:get_name()) print(stack:get_name())
@ -58,19 +61,14 @@ if not stack:is_known() then
end end
``` ```
The count will always be 0 or greater. The count will always be 0 or greater. Through normal gameplay, the count should
Through normal gameplay, the count should be no more than the maximum stack size be no more than the maximum stack size of the item - `stack_max`. However, admin
of the item - `stack_max`. commands and buggy mods may result in stacks exceeding the maximum size.
However, admin commands and buggy mods may result in stacks exceeding the maximum
size.
```lua ```lua
print(stack:get_stack_max()) print(stack:get_stack_max())
``` ```
An ItemStack can be empty, in which case the count will be 0. An ItemStack can be empty, in which case the count will be 0.
```lua ```lua
@ -78,7 +76,7 @@ print(stack:get_count())
stack:set_count(10) 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 ```lua
ItemStack() -- name="", count=0 ItemStack() -- name="", count=0
@ -87,24 +85,30 @@ ItemStack("default:stone 30")
ItemStack({ name = "default:wood", count = 10 }) ItemStack({ name = "default:wood", count = 10 })
``` ```
Item metadata is an unlimited key-value store for data about the item. Item metadata is an unlimited key-value store for data about the item. Key-value
Key-value means that you use a name (called the key) to access the data (called the value). means that you use a name (called the key) to access the data (called the
Some keys have special meaning, such as `description` which is used to have a per-stack value). Some keys have special meaning, such as `description` which is used to
item description. have a per-stack item description. This will be covered in more detail in the
This will be covered in more detail in the Metadata and Storage chapter. [Storage and Metadata](../map/storage.html) chapter.
## Inventory Locations ## Inventory Locations
An Inventory Location is where and how the inventory is stored. An Inventory Location is where and how the inventory is stored. There are three
There are three types of inventory location: player, node, and detached. types of inventory location: player, node, and detached. An inventory is
An inventory is directly tied to one and only one location - updating the inventory directly tied to one and only one location - updating the inventory will cause
will cause it to update immediately. it to update immediately.
Node inventories are related to the position of a specific node, such as a chest. ### Node Inventories
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 ```lua
local inv = minetest.get_inventory({ type="node", pos={x=1, y=2, z=3} }) on_punch = function(pos, node)
local inv = minetest.get_inventory({ type="node", pos=pos })
-- now use the inventory
end,
``` ```
The above obtains an *inventory reference*, commonly referred to as *InvRef*. The above obtains an *inventory reference*, commonly referred to as *InvRef*.
@ -118,6 +122,8 @@ The location of an inventory reference can be found like so:
local location = inv:get_location() local location = inv:get_location()
``` ```
### Player Inventories
Player inventories can be obtained similarly or using a player reference. Player inventories can be obtained similarly or using a player reference.
The player must be online to access their inventory. The player must be online to access their inventory.
@ -127,8 +133,10 @@ local inv = minetest.get_inventory({ type="player", name="player1" })
local inv = player:get_inventory() local inv = player:get_inventory()
``` ```
A detached inventory is one which is independent of players or nodes. ### Detached Inventories
Detached inventories also don't save over a restart.
A detached inventory is one that is independent of players or nodes. Detached
inventories also don't save over a restart.
```lua ```lua
local inv = minetest.get_inventory({ local inv = minetest.get_inventory({
@ -142,10 +150,9 @@ before accessing it:
minetest.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 - The `create_detached_inventory` function accepts 3 arguments, where only the
is required. first - the inventory name - is required. The second argument takes a table of
The second argument takes a table of callbacks, which can be used to control how callbacks, which can be used to control how players interact with the inventory:
players interact with the inventory:
```lua ```lua
-- Input only detached inventory -- Input only detached inventory
@ -177,9 +184,10 @@ On the contrary, action callbacks - starting with `on_` - don't have a return va
## Lists ## Lists
Inventory Lists are a concept used to allow multiple grids to be stored inside a single location. Inventory Lists are a concept used to allow multiple grids to be stored inside a
This is especially useful for the player as there are a number of common lists single location. This is especially useful for the player as there are several
which all games have, such as the *main* inventory and *craft* slots. common lists that all games have, such as the *main* inventory and *craft*
slots.
### Size and Width ### Size and Width

View File

@ -18,8 +18,6 @@ basic requirements for many mods.
- [Item Aliases](#item-aliases) - [Item Aliases](#item-aliases)
- [Textures](#textures) - [Textures](#textures)
- [Registering a basic node](#registering-a-basic-node) - [Registering a basic node](#registering-a-basic-node)
- [Actions and Callbacks](#actions-and-callbacks)
- [on_use](#onuse)
- [Crafting](#crafting) - [Crafting](#crafting)
- [Shaped](#shaped) - [Shaped](#shaped)
- [Shapeless](#shapeless) - [Shapeless](#shapeless)
@ -29,25 +27,24 @@ basic requirements for many mods.
## What are Nodes and Items? ## What are Nodes and Items?
Nodes, craftitems, and tools are all Items. Nodes, craftitems, and tools are all Items. An item is something that could be
An item is something that could be found in an inventory - found in an inventory - even if it isn't possible through normal gameplay.
even though it may not be possible through normal gameplay.
A node is an item which can be placed or be found in the world. A node is an item that can be placed or be found in the world. Every position
Every position in the world must be occupied with one and only one node - in the world must be occupied with one and only one node - seemingly blank
seemingly blank positions are usually air nodes. positions are usually air nodes.
A craftitem can't be placed and is only found in inventories or as a dropped item A craftitem can't be placed and is only found in inventories or as a dropped item
in the world. in the world.
A tool has the ability to wear and typically has non-default digging capabilities. A tool has the ability to wear and typically has non-default digging
In the future, it's likely that craftitems and tools will merge into one type of capabilities. In the future, it's likely that craftitems and tools will merge
item, as the distinction between them is rather artificial. into one type of item, as the distinction between them is rather artificial.
## Registering Items ## Registering Items
Item definitions consist of an *item name* and a *definition table*. Item definitions consist of an *item name* and a *definition table*.
The definition table contains attributes which affect the behaviour of the item. The definition table contains attributes that affect the behaviour of the item.
```lua ```lua
minetest.register_craftitem("modname:itemname", { minetest.register_craftitem("modname:itemname", {
@ -63,25 +60,26 @@ following format:
modname:itemname modname:itemname
The modname is the name of the mod in which the item is registered, and the The modname is the name of the mod in which the item is registered, and the item
item name is the name of the item itself. name is the name of the item itself. The item name should be relevant to what
The item name should be relevant to what the item is and can't already be registered. the item is and can't already be registered.
Both `modname` and `itemname` should only contain lowercase letters, numbers,
and underscores.
### Item Aliases ### Item Aliases
Items can also have *aliases* pointing to their name. Items can also have *aliases* pointing to their name. An *alias* is a
An *alias* is a pseudo-item name which results in the engine treating any pseudo-item name that results in the engine treating any occurrences of the
occurrences of the alias as if it were the item name. alias as if it were the item name. There are two main common uses of this:
There are two main common uses of this:
* Renaming removed items to something else. * Renaming removed items to something else.
There may be unknown nodes in the world and in inventories if an item is There may be unknown nodes in the world and in inventories if an item is
removed from a mod without any corrective code. removed from a mod without any corrective code.
* Adding a shortcut. `/giveme dirt` is easier than `/giveme default:dirt`. * Adding a shortcut. `/giveme dirt` is easier than `/giveme default:dirt`.
Registering an alias is pretty simple. Registering an alias is pretty simple. A good way to remember the order of the
A good way to remember the order of the arguments is `from → to` where arguments is `from → to` where *from* is the alias and *to* is the target.
*from* is the alias and *to* is the target.
```lua ```lua
minetest.register_alias("dirt", "default:dirt") minetest.register_alias("dirt", "default:dirt")
@ -103,14 +101,16 @@ JPEG textures are supported, but they do not support transparency and are genera
bad quality at low resolutions. bad quality at low resolutions.
It is often better to use the PNG format. It is often better to use the PNG format.
Textures in Minetest are usually 16 by 16 pixels. Textures in Minetest are usually 16 by 16 pixels. They can be any resolution,
They can be any resolution, but it is recommended that they are in the order of 2, but it is recommended that they are in the order of 2, for example, 16, 32, 64,
for example, 16, 32, 64, or 128. or 128. This is because other resolutions may not be supported correctly on
This is because other resolutions may not be supported correctly on older devices, older devices, especially phones, resulting in degraded performance.
resulting in decreased performance.
## Registering a basic node ## Registering a basic node
Registering nodes is similar to registering items, just with a different
function:
```lua ```lua
minetest.register_node("mymod:diamond", { minetest.register_node("mymod:diamond", {
description = "Alien Diamond", description = "Alien Diamond",
@ -120,6 +120,9 @@ minetest.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. 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. 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: To give a different texture per-side, supply the names of 6 textures in this order:
@ -128,7 +131,7 @@ To give a different texture per-side, supply the names of 6 textures in this ord
(+Y, -Y, +X, -X, +Z, -Z) (+Y, -Y, +X, -X, +Z, -Z)
Remember that +Y is upwards in Minetest, as is the convention with Remember that +Y is upwards in Minetest, as is the convention with
3D computer graphics. most 3D computer games.
```lua ```lua
minetest.register_node("mymod:diamond", { minetest.register_node("mymod:diamond", {
@ -152,49 +155,6 @@ 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. 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. 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 ## Crafting
There are several types of crafting recipe available, indicated by the `type` There are several types of crafting recipe available, indicated by the `type`
@ -327,6 +287,7 @@ minetest.register_craft({
}) })
``` ```
## Tools, Capabilities, and Dig Types ## Tools, Capabilities, and Dig Types
Dig types are groups which are used to define how strong a node is when dug Dig types are groups which are used to define how strong a node is when dug

View File

@ -202,7 +202,7 @@ local function emerge_callback(pos, action,
-- Send progress message -- Send progress message
if context.total_blocks == context.loaded_blocks then if context.total_blocks == context.loaded_blocks then
minetest.chat_send_all("Finished loading blocks!") minetest.chat_send_all("Finished loading blocks!")
end else
local perc = 100 * context.loaded_blocks / context.total_blocks local perc = 100 * context.loaded_blocks / context.total_blocks
local msg = string.format("Loading blocks %d/%d (%.2f%%)", local msg = string.format("Loading blocks %d/%d (%.2f%%)",
context.loaded_blocks, context.total_blocks, perc) context.loaded_blocks, context.total_blocks, perc)

View File

@ -80,7 +80,7 @@ minetest.register_abm({
nodenames = {"default:dirt_with_grass"}, nodenames = {"default:dirt_with_grass"},
neighbors = {"default:water_source", "default:water_flowing"}, neighbors = {"default:water_source", "default:water_flowing"},
interval = 10.0, -- Run every 10 seconds interval = 10.0, -- Run every 10 seconds
chance = 50, -- Select every 1 in 50 nodes chance = 50, -- One node has a chance of 1 in 50 to get selected
action = function(pos, node, active_object_count, action = function(pos, node, active_object_count,
active_object_count_wider) active_object_count_wider)
local pos = {x = pos.x, y = pos.y + 1, z = pos.z} local pos = {x = pos.x, y = pos.y + 1, z = pos.z}

View File

@ -8,19 +8,21 @@ redirect_from: /en/chapters/chat.html
cmd_online: cmd_online:
level: warning level: warning
title: Offline players can run commands title: Offline players can run commands
message: <p>A player name is passed instead of a player object because mods message: |
can run commands on behalf of offline players. For example, the IRC A player name is passed instead of a player object because mods
bridge allows players to run commands without joining the game.</p> can run commands on behalf of offline players. For example, the IRC
bridge allows players to run commands without joining the game.
<p>So make sure that you don't assume that the player is online. 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> You can check by seeing if `minetest.get_player_by_name` returns a player.
cb_cmdsprivs: cb_cmdsprivs:
level: warning level: warning
title: Privileges and Chat Commands title: Privileges and Chat Commands
message: The shout privilege isn't needed for a player to trigger this callback. message: |
This is because chat commands are implemented in Lua, and are just The shout privilege isn't needed for a player to trigger this callback.
chat messages that begin with a /. This is because chat commands are implemented in Lua, and are just
chat messages that begin with a /.
--- ---
@ -29,15 +31,20 @@ cb_cmdsprivs:
Mods can interact with player chat, including Mods can interact with player chat, including
sending messages, intercepting messages, and registering chat commands. sending messages, intercepting messages, and registering chat commands.
- [Sending Messages to All Players](#sending-messages-to-all-players) - [Sending Messages](#sending-messages)
- [Sending Messages to Specific Players](#sending-messages-to-specific-players) - [To All Players](#to-all-players)
- [To Specific Players](#to-specific-players)
- [Chat Commands](#chat-commands) - [Chat Commands](#chat-commands)
- [Complex Subcommands](#complex-subcommands) - [Accepting Multiple Arguments](#accepting-multiple-arguments)
- [Using string.split](#using-stringsplit)
- [Using Lua patterns](#using-lua-patterns)
- [Intercepting Messages](#intercepting-messages) - [Intercepting Messages](#intercepting-messages)
## Sending Messages to All Players ## Sending Messages
To send a message to every player in the game, call the chat_send_all function. ### To All Players
To send a message to every player in the game, call the `chat_send_all` function.
```lua ```lua
minetest.chat_send_all("This is a chat message to all players") minetest.chat_send_all("This is a chat message to all players")
@ -51,9 +58,9 @@ 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. The message appears on a separate line to distinguish it from in-game player chat.
## Sending Messages to Specific Players ### 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 ```lua
minetest.chat_send_player("player1", "This is a chat message for player1") minetest.chat_send_player("player1", "This is a chat message for player1")
@ -80,26 +87,57 @@ minetest.register_chatcommand("foo", {
In the above snippet, `interact` is listed as a required 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. [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, Chat commands can return up to two values,
the first being a Boolean indicating success, and the second being a the first being a Boolean indicating success, and the second being a
message to send to the user. message to send to the user.
{% include notice.html notice=page.cmd_online %} {% include notice.html notice=page.cmd_online %}
## Complex Subcommands ### Accepting Multiple Arguments
It is often required to make complex chat commands, such as: <a name="complex-subcommands"></a>
* `/msg <to> <message>` `param` gives you all the arguments to a chat command in a single string. It's
* `/team join <teamname>` common for chat commands to need to extract multiple arguments. There are two
* `/team leave <teamname>` ways of doing this, either using Minetest's string split or Lua patterns.
* `/team list`
This is usually done using [Lua patterns](https://www.lua.org/pil/20.2.html). #### Using string.split
Patterns are a way of extracting stuff from text using rules.
A string can be split up into words using `string.split(" ")`:
```lua ```lua
local to, msg = string.match(param, "^([%a%d_-]+) (*+)$") local parts = param:split(" ")
local cmd = parts[1]
if cmd == "join" then
local team_name = parts[2]
team.join(name, team_name)
return true, "Joined team!"
elseif cmd == "max_users" then
local team_name = parts[2]
local max_users = tonumber(parts[3])
if team_name and max_users then
return true, "Set max users of team " .. team_name .. " to " .. max_users
else
return false, "Usage: /team max_users <team_name> <number>"
end
else
return false, "Command needed"
end
```
#### Using Lua patterns
[Lua patterns](https://www.lua.org/pil/20.2.html) are a way of extracting stuff
from text using rules. They're best suited for when there are arguments that can
contain spaces or more control is needed on how parameters are captured.
```lua
local to, msg = param:match("^([%a%d_-]+) (*+)$")
``` ```
The above code implements `/msg <to> <message>`. Let's go through left to right: The above code implements `/msg <to> <message>`. Let's go through left to right:
@ -123,13 +161,6 @@ Patterns would probably be the
[lua-users.org tutorial](http://lua-users.org/wiki/PatternsTutorial) [lua-users.org tutorial](http://lua-users.org/wiki/PatternsTutorial)
or the [PIL documentation](https://www.lua.org/pil/20.2.html). 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="https://gitlab.com/rubenwardy/ChatCmdBuilder">Chat Command Builder</a>.
</p>
## Intercepting Messages ## Intercepting Messages
To intercept a message, use register_on_chat_message: To intercept a message, use register_on_chat_message:

View File

@ -35,16 +35,16 @@ unexpected windows tend to disrupt gameplay.
- [Real or Legacy Coordinates](#real-or-legacy-coordinates) - [Real or Legacy Coordinates](#real-or-legacy-coordinates)
- [Anatomy of a Formspec](#anatomy-of-a-formspec) - [Anatomy of a Formspec](#anatomy-of-a-formspec)
- [Elements](#elements) - [Elements](#elements)
- [Header](#header) - [Header](#header)
- [Guessing Game](#guessing-game) - [Guessing Game](#guessing-game)
- [Padding and Spacing](#padding-and-spacing) - [Padding and Spacing](#padding-and-spacing)
- [Receiving Formspec Submissions](#receiving-formspec-submissions) - [Receiving Formspec Submissions](#receiving-formspec-submissions)
- [Contexts](#contexts) - [Contexts](#contexts)
- [Formspec Sources](#formspec-sources) - [Formspec Sources](#formspec-sources)
- [Node Meta Formspecs](#node-meta-formspecs) - [Node Meta Formspecs](#node-meta-formspecs)
- [Player Inventory Formspecs](#player-inventory-formspecs) - [Player Inventory Formspecs](#player-inventory-formspecs)
- [Your Turn](#your-turn) - [Your Turn](#your-turn)
## Real or Legacy Coordinates ## Real or Legacy Coordinates
@ -100,7 +100,7 @@ settings of the client. Here's a formspec which is `2,2` in size:
Notice how we explicitly defined the formspec language version. Notice how we explicitly defined the formspec language version.
Without this, the legacy system will instead be used instead - which will Without this, the legacy system will instead be used instead - which will
prevent the use of consistent element positioning and other new feautures. prevent the use of consistent element positioning and other new features.
The position and anchor elements are used to place the formspec on the screen. The position and anchor elements are used to place the formspec on the screen.
The position sets where on the screen the formspec will be, and defaults to The position sets where on the screen the formspec will be, and defaults to
@ -225,9 +225,9 @@ 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 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 user, indexed by strings. Named elements will appear in the field under their own
name, but only if they are relevent for the event that caused the submission. name, depending on the event. Some elements will only be submitted if they caused
For example, a button element will only appear in fields if that particular button the event, such as buttons, and some elements will always appear in submissions,
was pressed. such as fields.
{% include notice.html notice=page.submit_vuln %} {% include notice.html notice=page.submit_vuln %}
@ -365,9 +365,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 The global callback is used to receive events from this formspec, and the
formname is `""`. formname is `""`.
There are a number of different mods which allow multiple mods to customise There are a number of different mods which allow multiple mods to customise the
the player inventory. The officially recommended mod is player inventory. Minetest Game uses
[Simple Fast Inventory (sfinv)](sfinv.html), and is included in Minetest Game. [SFINV](https://github.com/rubenwardy/sfinv/blob/master/Tutorial.md).
### Your Turn ### Your Turn

View File

@ -1,242 +1,5 @@
--- ---
title: "SFINV: Inventory Formspec" sitemap: false
layout: default
root: ../..
idx: 4.7
redirect_from: /en/chapters/sfinv.html 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

@ -24,7 +24,7 @@ The methods of ObjectRefs will always return nil when invalid, since Minetest 5.
Any call will essentially be ignored. Any call will essentially be ignored.
You should avoid storing ObjectRefs where possible. If you do to store an You should avoid storing ObjectRefs where possible. If you do to store an
ObjectRef, you should make you check it before use, like so: ObjectRef, you should make sure you check it before use, like so:
```lua ```lua
-- This only works in Minetest 5.2+ -- This only works in Minetest 5.2+

View File

@ -20,7 +20,6 @@ editor to provide alerts to any mistakes.
- [Configuring LuaCheck](#configuring-luacheck) - [Configuring LuaCheck](#configuring-luacheck)
- [Troubleshooting](#troubleshooting) - [Troubleshooting](#troubleshooting)
- [Using with editor](#using-with-editor) - [Using with editor](#using-with-editor)
- [Checking Commits with Travis](#checking-commits-with-travis)
## Installing LuaCheck ## Installing LuaCheck
@ -102,51 +101,7 @@ 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 to show you errors without running a command. Most editors will likely have a plugin
available. available.
* **Atom** - `linter-luacheck`.
* **VSCode** - Ctrl+P, then paste: `ext install dwenegar.vscode-luacheck` * **VSCode** - Ctrl+P, then paste: `ext install dwenegar.vscode-luacheck`
* **Sublime** - Install using package-control: * **Sublime** - Install using package-control:
[SublimeLinter](https://github.com/SublimeLinter/SublimeLinter), [SublimeLinter](https://github.com/SublimeLinter/SublimeLinter),
[SublimeLinter-luacheck](https://github.com/SublimeLinter/SublimeLinter-luacheck). [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

@ -14,7 +14,6 @@ After you've read this book, take a look at the following.
* Minetest's Lua API Reference - [HTML version](https://minetest.gitlab.io/minetest/class-reference/#player-only-no-op-for-other-objects) | * Minetest's Lua API Reference - [HTML version](https://minetest.gitlab.io/minetest/class-reference/#player-only-no-op-for-other-objects) |
[Text version](https://github.com/minetest/minetest/blob/master/doc/lua_api.txt). [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). * Look at [existing mods](https://forum.minetest.net/viewforum.php?f=11).
### Lua Programming ### Lua Programming

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

@ -0,0 +1,198 @@
---
title: Translation (i18n / l10n)
layout: default
root: ../..
idx: 8.05
marked_text_encoding:
level: info
title: Marked Text Encoding
message: |
You don't need to know the exact format of marked text, but it might help
you understand.
```
"\27(T@mymod)Hello everyone!\27E"
```
* `\27` is the escape character - it's used to tell Minetest to pay attention as
something special is coming up. This is used for both translations and text
colorisation.
* `(T@mymod)` says that the following text is translatable using the `mymod`
textdomain.
* `Hello everyone!` is the translatable text in English, as passed to the
translator function.
* `\27E` is the escape character again and `E`, used to signal that the end has
been reached.
---
## Introduction <!-- omit in toc -->
Adding support for translation to your mods and games allows more people to
enjoy them. According to Google Play, 64% of Minetest Android users don't have
English as their primary language. Minetest doesn't track stats for user
languages across all platforms, but there's likely to be a high proportion of
non-English speaking users.
Minetest allows you to translate your mods and games into different languages by
writing your text in English, and using translation files to map into other
languages. Translation is done on each player's client, allowing each player to
see a different language.
- [How does client-side translation work?](#how-does-client-side-translation-work)
- [Marked up text](#marked-up-text)
- [Translation files](#translation-files)
- [Format strings](#format-strings)
- [Best practices and Common Falsehoods about Translation](#best-practices-and-common-falsehoods-about-translation)
- [Server-side translations](#server-side-translations)
- [Conclusion](#conclusion)
## How does client-side translation work?
### Marked up text
The server needs to tell clients how to translate text. This is done by placing
control characters in text, telling Minetest where and how to translate
text. This is referred to as marked up text, and will be discussed more later.
To mark text as translatable, use a translator function (`S()`), obtained using
`minetest.get_translator(textdomain)`:
```lua
local S = minetest.get_translator("mymod")
minetest.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 `minetest.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 `minetest.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
minetest.register_on_joinplayer(function(player)
minetest.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")
}
minetest.register_chatcommand("find", {
func = function(name, param)
local info = minetest.get_player_information(name)
local language = info and info.language or "en"
for _, line in ipairs(list) do
local trans = minetest.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,9 +17,8 @@ we discussed how to structure your code avoid this.
- [Your First Test](#your-first-test) - [Your First Test](#your-first-test)
- [init.lua](#initlua) - [init.lua](#initlua)
- [api.lua](#apilua) - [api.lua](#apilua)
- [tests/api_spec.lua](#testsapispeclua) - [tests/api_spec.lua](#testsapi_speclua)
- [Mocking: Using External Functions](#mocking-using-external-functions) - [Mocking: Using External Functions](#mocking-using-external-functions)
- [Checking Commits with Travis](#checking-commits-with-travis)
- [Conclusion](#conclusion) - [Conclusion](#conclusion)
## Installing Busted ## Installing Busted
@ -164,28 +163,6 @@ 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 ## Conclusion
Unit tests will greatly increase the quality and reliability of your project if used Unit tests will greatly increase the quality and reliability of your project if used

View File

@ -11,17 +11,32 @@ redirect_from: /it/chapters/lua.html
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.
- [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) - [Programmare](#programmare)
- [Programmare in Lua](#programmare-in-lua)
- [Editor di codice](#editor-di-codice)
- [Portata locale e globale](#portata-locale-e-globale) - [Portata locale e globale](#portata-locale-e-globale)
- [Precedenza alla portata locale](#precedenza-alla-portata-locale)
- [Inclusione di altri script Lua](#inclusione-di-altri-script-lua) - [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 ## Editor di codice
Un editor di codice con evidenziamento delle parole chiave è sufficiente per scrivere script in Lua. Un editor di codice con evidenziamento delle parole chiave è sufficiente per scrivere script in Lua.
@ -56,126 +71,6 @@ Tra gli editor più famosi che ben si prestano a lavorare in Lua, troviamo:
(ne esistono ovviamente anche altri) (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, e a differenti tipi di flusso corrispondono comportamenti diversi.
Essi possono essere:
* Sequenziali: eseguono un'istruzione dopo l'altra, senza salti.
* Selettivi: saltano alcune sequenze a seconda delle condizioni.
* Iteranti: continuano 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)
```
In quest'esempio, `a`, `b`, e `risultato` sono *variabili*. Le variabili locali si dichiarano tramite l'uso della parola chiave `local` (che vedremo tra poco), e assegnando eventualmente loro un valore iniziale.
Il simbolo `=` significa *assegnazione*, quindi `risultato = a + b` significa impostare "risultato" ad a + b.
Per quanto riguarda i nomi delle variabili, essi possono essere più lunghi di un carattere - al contrario che in matematica - come visto in `risultato`, e vale anche la pena notare che Lua è *case-sensitive* (differenzia maiuscole 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 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
Tra gli operatori di Lua ci sono:
| 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" |
Si tenga presente che questa non è comunque una lista esaustiva.
### Selezione
Il metodo di selezione più basico è 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!".
### Operatori logici
Tra gli operatori logici di Lua ci sono:
| 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 gli operatori possono essere combinati, come da esempio:
```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. Per esempio:
```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 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.
## Portata locale e globale ## Portata locale e globale
L'essere locale o globale di una variabile determina da dove è possibile accederci. L'essere locale o globale di una variabile determina da dove è possibile accederci.
@ -211,6 +106,9 @@ one()
two() two()
``` ```
### 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. 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ò: Crearne di ulteriori è considerato cattiva programmazione, e Minetest ci avviserà di ciò:

View File

@ -8,7 +8,7 @@ idx: 0.1
--- ---
<header> <header>
<h1>Minetest: Libro del Modding</h1> <h1>Minetest: Libro del Moddaggio</h1>
<span>di <a href="https://rubenwardy.com" rel="author">rubenwardy</a></span> <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> <span>con modifiche di <a href="http://rc.minetest.tv/">Shara</a></span>
@ -17,20 +17,20 @@ idx: 0.1
## Introduzione ## Introduzione
Il modding su Minetest è supportato grazie a script in Lua. Il moddaggio su Minetest è supportato grazie a script in Lua.
Questo libro mira a insegnarti come creare le tue mod, iniziando dalle basi. 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 Ogni capitolo si concentra su un punto specifico dell'API, portandoti in breve tempo
a fare le tue mod. a fare le tue mod.
Oltre che [leggere questo libro online](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). puoi anche [scaricarlo in HTML](https://github.com/rubenwardy/minetest_modding_book/releases).
### Feedback e Contributi ### Riscontri e Contributi
Hai notato un errore o vuoi dare un feedback? Assicurati di farmelo presente. Hai notato un errore o vuoi darmi il tuo parere? Assicurati di farmelo presente.
* Crea una [Issue su GitLab](https://gitlab.com/rubenwardy/minetest_modding_book/-/issues). * Apri una [Segnalazione 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). * Rispondi alla [Discussione sul Forum](https://forum.minetest.net/viewtopic.php?f=14&t=10729).
* [Contattami (in inglese)](https://rubenwardy.com/contact/). * [Contattami (in inglese)](https://rubenwardy.com/contact/).
* Voglia di contribuire? * Voglia di contribuire?
[Leggi il README](https://gitlab.com/rubenwardy/minetest_modding_book/-/blob/master/README.md). [Leggi il README](https://gitlab.com/rubenwardy/minetest_modding_book/-/blob/master/README.md).

184
_it/items/callbacks.md Normal file
View File

@ -0,0 +1,184 @@
---
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 `minetest.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 | `minetest.item_place` |
| on_secondary_use | clic destro a vuoto | `minetest.item_secondary_use` (non fa nulla) |
| on_drop | Q | `minetest.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
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, e quest'ultimi non devono per forza essere interi - bensì anche decimali.
`minetest.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
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.
### 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 `minetest.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 `minetest.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 `minetest.item_place` e `minetest.node_dig`.
Alcune sono usate direttamente, mentre altre sono funzioni che ritornano il richiamo vero e proprio:
```lua
minetest.register_item("miamod:esempio", {
on_place = minetest.item_place,
on_use = minetest.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 `minetest.place_item` e `minetest.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 `minetest.item_place`.
Se il nodo puntato ha un richiamo `on_rightclick` e il tasto accovacciati (shift) è tenuto premuto, allora verrà chiamato `on_rightclick`.
Diversamente, `minetest.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
minetest.register_node("miamod:mionodo", {
on_rightclick = function(pos, node, clicker, itemstack, pointed_thing)
if clicker:is_player() then
minetest.chat_send_player(clicker:get_player_name(), "Ciao mondo!")
end
end,
on_construct = function(pos, node)
local meta = minetest.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 = minetest.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 `minetest.node_dig`, che controlla eventuali protezioni dell'area, usura l'oggetto, rimuove il nodo, e ne esegue il richiamo `after_dig_node`.
```lua
minetest.register_node("miamod:mionodo", {
on_punch = function(pos, node, puncher, pointed_thing)
if puncher:is_player() then
minetest.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

@ -17,8 +17,6 @@ Saper registrare nuovi nodi, oggetti fabbricabili e conseguenti ricette, è un r
- [Alias](#alias) - [Alias](#alias)
- [Texture](#texture) - [Texture](#texture)
- [Registrare un nodo base](#registrare-un-nodo-base) - [Registrare un nodo base](#registrare-un-nodo-base)
- [Azioni e callback](#azioni-e-callback)
- [on_use](#onuse)
- [Fabbricazione](#fabbricazione) - [Fabbricazione](#fabbricazione)
- [Fisse (shaped)](#fisse-shaped) - [Fisse (shaped)](#fisse-shaped)
- [Informi (shapeless)](#informi-shapeless) - [Informi (shapeless)](#informi-shapeless)
@ -138,46 +136,6 @@ minetest.register_node("miamod:diamante", {
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. 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.
Vediamone un esempio.
### 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, e quest'ultimi non devono per forza essere interi - ovvero 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 ## Fabbricazione
Ci sono diversi tipi di ricette di fabbricazione disponibili, indicate dalla proprietà `type`. Ci sono diversi tipi di ricette di fabbricazione disponibili, indicate dalla proprietà `type`.

View File

@ -184,7 +184,7 @@ local function mio_callback(pos, action,
-- Invia messaggio indicante il progresso -- Invia messaggio indicante il progresso
if param.blocchi_totali == param.blocchi_caricati then if param.blocchi_totali == param.blocchi_caricati then
minetest.chat_send_all("Ho finito di caricare blocchi!") minetest.chat_send_all("Ho finito di caricare blocchi!")
end else
local percentuale = 100 * param.blocchi_caricati / param.blocchi_totali local percentuale = 100 * param.blocchi_caricati / param.blocchi_totali
local msg = string.format("Caricamento blocchi %d/%d (%.2f%%)", local msg = string.format("Caricamento blocchi %d/%d (%.2f%%)",
param.blocchi_caricati, param.blocchi_totali, percentuale) param.blocchi_caricati, param.blocchi_totali, percentuale)

View File

@ -1,5 +1,5 @@
--- ---
title: Oggetti, giocatori e entità title: Oggetti, giocatori ed entità
layout: default layout: default
root: ../.. root: ../..
idx: 3.4 idx: 3.4

View File

@ -8,17 +8,19 @@ redirect_from: /it/chapters/chat.html
cmd_online: cmd_online:
level: warning level: warning
title: I giocatori offline possono eseguire comandi title: I giocatori offline possono eseguire comandi
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. message: |
Per esempio, il bridge IRC permette ai giocatori di eseguire comandi senza dover entrare in gioco.</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 ponte 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. 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> Puoi controllare ciò tramite `minetest.get_player_by_name`, per vedere se ritorna qualcosa o meno.
cb_cmdsprivs: cb_cmdsprivs:
level: warning level: warning
title: Privilegi e comandi title: Privilegi e comandi
message: Il privilegio shout non è necessario per far sì che un giocatore attivi questo callback. message: |
Questo perché i comandi sono implementati in Lua, e sono semplicemente dei messaggi in chat che iniziano con /. Il privilegio shout non è necessario per far sì che un giocatore attivi questo richiamo.
Questo perché i comandi sono implementati in Lua, e sono semplicemente dei messaggi in chat che iniziano con /.
--- ---
@ -26,13 +28,18 @@ cb_cmdsprivs:
Le mod possono interagire con la chat del giocatore, tra l'inviare messaggi, intercettarli e registrare dei comandi. Le mod possono interagire con la chat del giocatore, tra l'inviare messaggi, intercettarli e registrare dei comandi.
- [Inviare messaggi a tutti i giocatori](#inviare-messaggi-a-tutti-i-giocatori) - [Inviare messaggi](#inviare-messaggi)
- [Inviare messaggi a giocatori specifici](#inviare-messaggi-a-giocatori-specifici) - [A tutti i giocatori](#a-tutti-i-giocatori)
- [A giocatori specifici](#a-giocatori-specifici)
- [Comandi](#comandi) - [Comandi](#comandi)
- [Complex Subcommands](#complex-subcommands) - [Accettare più argomenti](#accettare-più-argomenti)
- [Intercettare i messaggi](#interecettare-i-messaggi) - [Usare string.split](#usare-stringsplit)
- [Usare i pattern Lua](#usare-i-pattern-lua)
- [Intercettare i messaggi](#intercettare-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`: Per inviare un messaggio a tutti i giocatori connessi in gioco, si usa la funzione `chat_send_all`:
@ -48,7 +55,7 @@ Segue un esempio di come apparirerebbe in gioco:
Il messaggio appare su una nuova riga, per distinguerlo dai messaggi dei giocatori. Il messaggio appare su una nuova riga, per distinguerlo dai messaggi dei giocatori.
## Inviare messaggi a giocatori specifici ### A giocatori specifici
Per inviare un messaggio a un giocatore in particolare, si usa invece la funzione `chat_send_player`: Per inviare un messaggio a un giocatore in particolare, si usa invece la funzione `chat_send_player`:
@ -75,21 +82,50 @@ minetest.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. 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. 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 %} {% include notice.html notice=page.cmd_online %}
## Sottocomandi complessi
È spesso necessario creare dei comandi complessi, come per esempio: ### Accettare più argomenti
* `/msg <a> <messaggio>` Non è raro che i comandi richiedano più argomenti, come per esempio `/squadra entra <nome_squadra>`.
* `/team entra <nometeam>` Ci sono due modi per implementare ciò: usare `string.split` o i pattern Lua.
* `/team esci <nometeam>`
* `/team elenco`
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. #### Usare string.split
Una stringa può essere spezzettata in più parti tramite `string.split(" ")`:
```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 ```lua
local a, msg = string.match(param, "^([%a%d_-]+) (*+)$") local a, msg = string.match(param, "^([%a%d_-]+) (*+)$")
@ -130,7 +166,7 @@ minetest.register_on_chat_message(function(name, message)
end) 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. 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 %} {% include notice.html notice=page.cb_cmdsprivs %}

View File

@ -29,16 +29,16 @@ Tieni presente che se non si ha bisogno di ricevere input dal giocatore, per ese
- [Coordinate reali o datate](#coordinate-reali-o-datate) - [Coordinate reali o datate](#coordinate-reali-o-datate)
- [Anatomia di un formspec](#anatomia-di-un-formspec) - [Anatomia di un formspec](#anatomia-di-un-formspec)
- [Elementi](#elementi) - [Elementi](#elementi)
- [Intestazione](#intestazione) - [Intestazione](#intestazione)
- [Esempio: indovina un numero](#esempio-indovina-un-numero) - [Esempio: indovina un numero](#esempio-indovina-un-numero)
- [Imbottitura e spaziatura](#imbottitura-e-spaziatura) - [Imbottitura e spaziatura](#imbottitura-e-spaziatura)
- [Ricevere i moduli di compilazione](#ricevere-i-moduli-di-compilazione) - [Ricevere i moduli di compilazione](#ricevere-i-moduli-di-compilazione)
- [Contesti](#contesti) - [Contesti](#contesti)
- [Ricavare un formspec](#ricavare-un-formspec) - [Ricavare un formspec](#ricavare-un-formspec)
- [Formspec nei nodi](#formspec-nei-nodi) - [Formspec nei nodi](#formspec-nei-nodi)
- [Inventario del giocatore](#inventario-del-giocatore) - [Inventario del giocatore](#inventario-del-giocatore)
- [Il tuo turno](#il-tuo-turno) - [Il tuo turno](#il-tuo-turno)
## Coordinate reali o datate ## Coordinate reali o datate
@ -321,7 +321,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 è `""`. 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. Ci sono svariate mod che permettono ad altrettante mod di personalizzare l'inventario del giocatore.
La mod ufficialmente raccomandata è [Simple Fast Inventory (sfinv)](sfinv.html), ed è inclusa in Minetest Game. La mod ufficialmente raccomandata è [SFINV](https://github.com/rubenwardy/sfinv/blob/master/Tutorial.md), ed è inclusa in Minetest Game.
### Il tuo turno ### Il tuo turno

View File

@ -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; * **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); * **Super rimbalzo**: Aumenta il valore del salto in modo che il giocatore possa saltare 20 metri (1 cubo = 1 metro);
* **Space**: Fai in modo che la gravità diminuisca man mano che si sale di altitudine. * **Spazio**: Fai in modo che la gravità diminuisca man mano che si sale di altitudine.

View File

@ -1,223 +1,4 @@
--- ---
title: "SFINV" sitemap: false
layout: default redirect_to: "https://github.com/rubenwardy/sfinv/blob/master/Tutorial.md"
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

@ -13,19 +13,18 @@ In questo capitolo, imparerai come usare uno strumento chiamato LuaCheck per sca
LuaCheck può essere usato in combinazione con l'editor per fornire avvertimenti vari. LuaCheck può essere usato in combinazione con l'editor per fornire avvertimenti vari.
- [Installare LuaCheck](#installare-luacheck) - [Installare LuaCheck](#installare-luacheck)
- [Windows](#windows) - [Windows](#windows)
- [Linux](#linux) - [Linux](#linux)
- [Eseguire LuaCheck](#eseguire-luacheck) - [Eseguire LuaCheck](#eseguire-luacheck)
- [Configurare LuaCheck](#configurare-luacheck) - [Configurare LuaCheck](#configurare-luacheck)
- [Risoluzione problemi](#risoluzione-problemi) - [Risoluzione problemi](#risoluzione-problemi)
- [Uso nell'editor](#uso-nelleditor) - [Uso nell'editor](#uso-nelleditor)
- [Controllare i commit con Travis](#controllare-i-commit-con-travis)
## Installare LuaCheck ## Installare LuaCheck
### Windows ### Windows
Basta scaricare luacheck.exe dall'apposita [pagina delle release su Github](https://github.com/mpeterv/luacheck/releases). Basta scaricare luacheck.exe dall'apposita [pagina delle versioni su Github](https://github.com/mpeterv/luacheck/releases).
### Linux ### Linux
@ -53,7 +52,7 @@ Su Linux, esegui `luacheck .` nella cartella principale del progetto.
## Configurare LuaCheck ## Configurare LuaCheck
Crea un file chiamato .luacheckrc nella cartella principale del tuo progetto. Crea un file chiamato .luacheckrc nella cartella principale del tuo progetto.
Questa può essere quella di un gioco, di una modpack o di una mod. Questa può essere quella di un gioco, di un pacchetto mod o di una mod singola.
Inserisci il seguente codice all'interno: Inserisci il seguente codice all'interno:
@ -80,7 +79,7 @@ read_globals = {
Poi, avrai bisogno di assicurarti che funzioni eseguendo LuaCheck: dovresti ottenere molti meno errori questa volta. 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. Partendo dal primo errore, modifica il codice per risolvere il problema, o modifica la configurazione di LuaCheck se il codice è corretto.
Dai un occhio alla lista sottostante. Dài un occhio alla lista sottostante.
### Risoluzione problemi ### Risoluzione problemi
@ -96,46 +95,7 @@ Dai 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. È 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: Queste sono disponibili nella maggior parte degli editor, come:
* **Atom** - `linter-luacheck`;
* **VSCode** - Ctrl+P, poi incolla: `ext install dwenegar.vscode-luacheck`; * **VSCode** - Ctrl+P, poi incolla: `ext install dwenegar.vscode-luacheck`;
* **Sublime** - Installala usando package-control: * **Sublime** - Installala usando package-control:
[SublimeLinter](https://github.com/SublimeLinter/SublimeLinter), [SublimeLinter](https://github.com/SublimeLinter/SublimeLinter),
[SublimeLinter-luacheck](https://github.com/SublimeLinter/SublimeLinter-luacheck). [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

@ -14,7 +14,6 @@ Dopo aver letto questo libro, se mastichi l'inglese dai un occhio a ciò che seg
* Riferimento alla API Lua di Minetest - [versione HTML](https://minetest.gitlab.io/minetest/) | * Riferimento alla API Lua di Minetest - [versione HTML](https://minetest.gitlab.io/minetest/) |
[versione solo testo](https://github.com/minetest/minetest/blob/master/doc/lua_api.txt). [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). * Spulcia le [mod esistenti](https://forum.minetest.net/viewforum.php?f=11).
### Programmazione in Lua ### Programmazione in Lua

153
_it/quality/translations.md Normal file
View File

@ -0,0 +1,153 @@
---
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 `minetest.get_translator(dominiotestuale)`, che abbrevieremo con `S()`:
```lua
local S = minetest.get_translator("miamod")
minetest.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 `minetest.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
minetest.register_on_joinplayer(function(player)
minetest.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")
}
minetest.register_chatcommand("find", {
func = function(name, param)
local info = minetest.get_player_information(name)
local lingua = info and info.language or "en"
for _, riga in ipairs(lista) do
local trad = minetest.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

@ -13,11 +13,10 @@ Scrivere i testing d'unità per le funzioni dove vengono chiamate quelle di Mine
- [Installare Busted](#installare-busted) - [Installare Busted](#installare-busted)
- [Il tuo primo test](#il-tuo-primo-test) - [Il tuo primo test](#il-tuo-primo-test)
- [init.lua](#initlua) - [init.lua](#initlua)
- [api.lua](#apilua) - [api.lua](#apilua)
- [tests/api_spec.lua](#testsapispeclua) - [tests/api_spec.lua](#testsapi_speclua)
- [Simulare: usare funzioni esterne](#simulare-usare-funzioni-esterne) - [Simulare: usare funzioni esterne](#simulare-usare-funzioni-esterne)
- [Controllare commit con Travis](#controllare-commit-con-travis)
- [Conclusione](#conclusione) - [Conclusione](#conclusione)
## Installare Busted ## Installare Busted
@ -154,29 +153,8 @@ 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 ## 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. 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 [crafting di rubenwardy](https://github.com/rubenwardy/crafting). Per un esempio di mod con molti testing d'unità, vedere la mod [*crafting* di rubenwardy](https://github.com/rubenwardy/crafting).

View File

@ -3,7 +3,15 @@ layout: compress
--- ---
<!doctype html> <!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> <html>
{% endif %}
<head> <head>
<title>{% if page.homepage %}{% else %}{{ page.title }} - {% endif %}Minetest Modding Book</title> <title>{% if page.homepage %}{% else %}{{ page.title }} - {% endif %}Minetest Modding Book</title>
<meta charset="UTF-8"> <meta charset="UTF-8">
@ -13,8 +21,20 @@ layout: compress
<meta name="author" content="rubenwardy"> <meta name="author" content="rubenwardy">
<meta name="flattr:id" content="gl763e"> <meta name="flattr:id" content="gl763e">
<link rel="canonical" href="https://rubenwardy.com/minetest_modding_book{{ page.url }}" />
{% 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 %}
<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> <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=2"> <link rel="stylesheet" href="{{ page.root }}/static/style.css?v=3">
</head> </head>
<body> <body>
<div id="container"> <div id="container">

View File

@ -8,14 +8,15 @@ layout: base
{% if language == "_it" %} {% if language == "_it" %}
{% assign language = "it" %} {% assign language = "it" %}
{% assign links = site.it | sort: "idx" %} {% assign links = site.it %}
{% elseif language == "_de" %} {% elsif language == "_de" %}
{% assign language = "de" %} {% assign language = "de" %}
{% assign links = site.de | sort: "idx" %} {% assign links = site.de %}
{% else %} {% else %}
{% assign language = "en" %} {% assign language = "en" %}
{% assign links = site.en | sort: "idx" %} {% assign links = site.en %}
{% endif %} {% endif %}
{% assign links = links | where_exp: "item", "item.sitemap != false" | sort: "idx" %}
{% assign num = 0 %} {% assign num = 0 %}
@ -66,7 +67,7 @@ layout: base
</ul> </ul>
<footer> <footer>
&copy; 2014-21 &copy; 2014-{{ site.time | date: '%Y' }}
{% if language == "en" %} {% if language == "en" %}
| Helpful? Consider | Helpful? Consider
<a href="https://rubenwardy.com/donate/">donating</a> <a href="https://rubenwardy.com/donate/">donating</a>

View File

@ -33,68 +33,6 @@ figure {
padding: 0 0 0 6px; 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 { .header-link, .anchor {
text-decoration: none; text-decoration: none;
color: #bbb; color: #bbb;

61
_sass/_notice.scss Normal file
View File

@ -0,0 +1,61 @@
.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;
}

17
_sass/_table.scss Normal file
View File

@ -0,0 +1,17 @@
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;
}

View File

@ -1,35 +1,36 @@
--- ---
layout: none layout: none
--- ---
<!doctype html>
<html> <html>
<head> <head>
<title>Redirecting...</title> <title>Minetest Modding Book</title>
<meta name="description" content="An easy guide to learn how to create mods for Minetest">
<script>
var languages = {{ site.data.languages | jsonify }};
function getLanguage() {
var userLang = navigator.language || navigator.userLanguage;
for (var i = 0; i < languages.length; i++) {
var lang = languages[i];
if (userLang.indexOf(lang.code) == 0) {
return lang;
}
}
return null;
}
var language = getLanguage() || languages[0];
window.location.replace(language.code + "/index.html");
</script>
<meta http-equiv="refresh" content="0;URL='{{ 'en/index.html' | absolute_url }}'" />
</head> </head>
<body> <body>
Detecting and redirecting to the correct translation.<br><br> <main>
<h1>Minetest Modding Book</h1>
<a href="en/index.html">View English Translation</a><br><br> <p>Detecting and redirecting to the correct translation.</p>
<p>
<script> <a href="en/index.html">View English Translation</a>
var languages = {{ site.data.languages | jsonify }}; </p>
function getLanguage() { </main>
var userLang = navigator.language || navigator.userLanguage;
for (var i = 0; i < languages.length; i++) {
var lang = languages[i];
if (userLang.indexOf(lang.code) == 0) {
return lang;
}
}
return null;
}
var language = getLanguage() || languages[0];
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>
</body> </body>
</html> </html>

View File

@ -1,7 +1,7 @@
--- ---
--- ---
{% assign pages = site.en | sort: "idx" %} {% assign pages = site.en | where_exp: "item", "item.sitemap != false" | sort: "idx" %}
{% assign num = 0 %} {% assign num = 0 %}
[ [

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

View File

@ -4,3 +4,5 @@
@import "main"; @import "main";
@import "content"; @import "content";
@import "code"; @import "code";
@import "notice";
@import "table";