Merge branch 'master' of https://gitlab.com/rubenwardy/minetest_modding_book
This commit is contained in:
commit
d584267768
@ -6,6 +6,7 @@ variables:
|
||||
|
||||
before_script:
|
||||
- rm Gemfile.lock
|
||||
- bundle install
|
||||
|
||||
test:
|
||||
stage: test
|
||||
@ -21,10 +22,8 @@ test:
|
||||
pages:
|
||||
stage: deploy
|
||||
interruptible: true
|
||||
before_script:
|
||||
- rm Gemfile.lock
|
||||
script:
|
||||
- bundle exec jekyll build -d public
|
||||
- bundle exec jekyll build -d public --baseurl /minetest_modding_book
|
||||
artifacts:
|
||||
paths:
|
||||
- public
|
||||
|
1
Gemfile
1
Gemfile
@ -6,4 +6,5 @@ gem "webrick"
|
||||
group :jekyll_plugins do
|
||||
gem "jekyll-sitemap"
|
||||
gem "jekyll-redirect-from"
|
||||
gem "jekyll-sass-converter", "~> 2.0"
|
||||
end
|
||||
|
427
LICENSE
Normal file
427
LICENSE
Normal 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.
|
@ -12,18 +12,18 @@ redirect_from:
|
||||
## Introduction <!-- omit in toc -->
|
||||
|
||||
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)
|
||||
- [Where are mods stored?](#where-are-mods-stored)
|
||||
- [Mod Directory](#mod-directory)
|
||||
- [mod.conf](#modconf)
|
||||
- [Dependencies](#dependencies)
|
||||
- [Creating your first mod](#creating-your-first-mod)
|
||||
- [Mod directory](#mod-directory)
|
||||
- [mod.conf](#modconf)
|
||||
- [init.lua](#initlua)
|
||||
- [Summary](#summary)
|
||||
- [Dependencies](#dependencies)
|
||||
- [Mod Packs](#mod-packs)
|
||||
- [Example](#example)
|
||||
- [Mod Folder](#mod-folder)
|
||||
- [init.lua](#initlua)
|
||||
- [mod.conf](#modconf-1)
|
||||
|
||||
|
||||
## What are Games and Mods?
|
||||
@ -53,7 +53,7 @@ and is applicable for both game developers and modders.
|
||||
<a name="mod-locations"></a>
|
||||
|
||||
Each mod has its own directory where its Lua code, textures, models, and
|
||||
sounds are placed. Minetest checks in 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*.
|
||||
|
||||
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.
|
||||
|
||||
|
||||
## 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.
|
||||
Mod names can include letters, numbers, and underscores. A good name should
|
||||
describe what the mod does, and the directory which contains the components of a mod
|
||||
must have the same name as the mod name.
|
||||
To find out if a mod name is available, try searching for it on
|
||||
Go to the global mods directory (About > Open user data directory > mods) and
|
||||
create a new folder called "mymod". `mymod` is the mod name.
|
||||
|
||||
Each mod should have a unique *mod name*, a technical identifier (id) used to
|
||||
refer to the mod. Mod names can include letters, numbers, and underscores. A
|
||||
good name should describe what the mod does, and the directory that contains
|
||||
the components of a mod must have the same name as the mod name. To find out if
|
||||
a mod name is available, try searching for it on
|
||||
[content.minetest.net](https://content.minetest.net).
|
||||
|
||||
mymod
|
||||
├── init.lua (required) - Runs when the game loads.
|
||||
├── mod.conf (recommended) - Contains description and dependencies.
|
||||
├── textures (optional)
|
||||
│ └── ... any textures or images
|
||||
├── sounds (optional)
|
||||
│ └── ... any sounds
|
||||
└── ... any other files or directories
|
||||
├── textures
|
||||
│ └── mymod_node.png files
|
||||
├── init.lua
|
||||
└── mod.conf
|
||||
|
||||
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
|
||||
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
|
||||
information.
|
||||
|
||||
For example:
|
||||
### init.lua
|
||||
|
||||
name = mymod
|
||||
description = Adds foo, bar, and bo.
|
||||
depends = modone, modtwo
|
||||
Create an init.lua file with the following content:
|
||||
|
||||
### 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.
|
||||
One mod may require another mod's code, items, or other resources to be available
|
||||
for it to use.
|
||||
One mod may require another mod's code, items, or other resources to be
|
||||
available for it to use.
|
||||
|
||||
There are two types of dependencies: hard and optional dependencies.
|
||||
Both require the mod to be loaded first. If the mod being depended on isn't
|
||||
available, a hard dependency will cause the mod to fail to load, while an optional
|
||||
dependency might lead to fewer features being enabled.
|
||||
|
||||
An optional dependency is useful if you want to optionally support another mod; it can
|
||||
enable extra content if the user wishes to use both the mods at the same time.
|
||||
An optional dependency is useful if you want to optionally support another mod;
|
||||
it can enable extra content if the user wishes to use both the mods at the same
|
||||
time.
|
||||
|
||||
Dependencies are specified in a comma-separated list in mod.conf.
|
||||
|
||||
@ -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*.
|
||||
Games have their own organisational structure which will be explained in the
|
||||
Games chapter.
|
||||
|
||||
## Example
|
||||
|
||||
Here is an example which puts all of this together:
|
||||
|
||||
### Mod Folder
|
||||
mymod
|
||||
├── textures
|
||||
│ └── mymod_node.png files
|
||||
├── 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.
|
||||
|
@ -9,21 +9,39 @@ redirect_from: /en/chapters/lua.html
|
||||
|
||||
## Introduction <!-- omit in toc -->
|
||||
|
||||
In this chapter we'll talk about scripting in Lua, the tools required
|
||||
to assist with this, and some techniques which you may find useful.
|
||||
In this chapter, you'll learn about scripting in Lua, the tools required
|
||||
to help with this, and some techniques that you may find useful.
|
||||
|
||||
- [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)
|
||||
- [Coding in Lua](#coding-in-lua)
|
||||
- [Code Editors](#code-editors)
|
||||
- [Local and Global Scope](#local-and-global-scope)
|
||||
- [Locals should be used as much as possible](#locals-should-be-used-as-much-as-possible)
|
||||
- [Including other Lua Scripts](#including-other-lua-scripts)
|
||||
|
||||
|
||||
## Programming
|
||||
|
||||
Programming is the action of taking a problem, such as sorting a list
|
||||
of items, and turning it into steps that a computer can understand.
|
||||
Teaching you the logical process of programming is beyond the scope of this book;
|
||||
however, the following websites are quite useful in developing this:
|
||||
|
||||
* [Codecademy](http://www.codecademy.com/) is one of the best resources for
|
||||
learning to write code. It provides an interactive tutorial experience.
|
||||
* [Scratch](https://scratch.mit.edu) is a good resource for starting from
|
||||
absolute basics, and learning the problem-solving techniques required to program.
|
||||
It's great for children and teenagers.
|
||||
* [Programming with Mosh](https://www.youtube.com/user/programmingwithmosh) is
|
||||
a good YouTube series to learn programming.
|
||||
|
||||
### Coding in Lua
|
||||
|
||||
It's also beyond the scope of this book to teach Lua coding.
|
||||
The [Programming in Lua (PiL)](https://www.lua.org/pil/contents.html) book is an
|
||||
excellent introduction to Lua programming.
|
||||
|
||||
|
||||
## Code Editors
|
||||
|
||||
A code editor with code highlighting is sufficient for writing scripts in Lua.
|
||||
@ -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:
|
||||
|
||||
* [VSCode](https://code.visualstudio.com/) -
|
||||
* [VSCode](https://code.visualstudio.com/):
|
||||
open source (as Code-OSS or VSCodium), popular, and has
|
||||
[plugins for Minetest modding](https://marketplace.visualstudio.com/items?itemName=GreenXenith.minetest-tools).
|
||||
* [Notepad++](http://notepad-plus-plus.org/) - Windows-only
|
||||
* [Atom](http://atom.io/)
|
||||
[plugins for Minetest](https://marketplace.visualstudio.com/items?itemName=GreenXenith.minetest-tools).
|
||||
* [Notepad++](http://notepad-plus-plus.org/): simple, Windows-only
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
are some examples:
|
||||
read from. Global variables can be accessed from anywhere in the script file,
|
||||
and from any other mod:
|
||||
|
||||
```lua
|
||||
function one()
|
||||
foo = "bar"
|
||||
end
|
||||
|
||||
function two()
|
||||
print(dump(foo)) -- Output: "bar"
|
||||
end
|
||||
|
||||
one()
|
||||
two()
|
||||
```
|
||||
|
||||
In constrast, a local variable is only accessible from where it is defined.
|
||||
Lua defaults to variables being global, so you need to explicitly use the
|
||||
`local` keyword:
|
||||
|
||||
```lua
|
||||
-- Accessible from within this script file
|
||||
@ -220,21 +118,8 @@ function myfunc()
|
||||
end
|
||||
```
|
||||
|
||||
In contrast, global variables can be accessed from anywhere in the script file, and
|
||||
from any other mod.
|
||||
|
||||
```lua
|
||||
function one()
|
||||
foo = "bar"
|
||||
end
|
||||
|
||||
function two()
|
||||
print(dump(foo)) -- Output: "bar"
|
||||
end
|
||||
|
||||
one()
|
||||
two()
|
||||
```
|
||||
### Locals should be used as much as possible
|
||||
|
||||
Local variables should be used whenever possible. Mods should only create one
|
||||
global at most, with the same name as the mod. Creating other globals is sloppy
|
||||
@ -284,6 +169,9 @@ end
|
||||
mymod.foo("foobar")
|
||||
```
|
||||
|
||||
`function mymod.foo()` is equivalent to `mymod.foo = function()`, it's just a
|
||||
nicer way to write it.
|
||||
|
||||
## Including other Lua Scripts
|
||||
|
||||
The recommended way to include other Lua scripts in a mod is to use *dofile*.
|
||||
@ -296,11 +184,13 @@ A script can return a value, which is useful for sharing private locals:
|
||||
|
||||
```lua
|
||||
-- script.lua
|
||||
return "Hello world!"
|
||||
local module = {}
|
||||
module.message = "Hello World!"
|
||||
return module
|
||||
|
||||
-- init.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
|
||||
|
206
_en/items/callbacks.md
Normal file
206
_en/items/callbacks.md
Normal 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.
|
@ -25,8 +25,10 @@ available, which cover pixel art in much more detail.
|
||||
- [Using the Pencil](#using-the-pencil)
|
||||
- [Tiling](#tiling)
|
||||
- [Transparency](#transparency)
|
||||
- [Color Palettes](#color-palettes)
|
||||
- [Editors](#editors)
|
||||
- [MS Paint](#ms-paint)
|
||||
- [Aseprite / LibreSprite](#aseprite--libresprite)
|
||||
- [GIMP](#gimp)
|
||||
|
||||
## Techniques
|
||||
@ -59,6 +61,13 @@ and some nodes, such as glass.
|
||||
Not all editors support transparency, so make sure you choose an
|
||||
editor which is suitable for the textures you wish to create.
|
||||
|
||||
### Color Palettes
|
||||
|
||||
Using a consistent color palette is an easy way to make your art look a lot
|
||||
better. It's a good idea to use one with a limited number of colors, perhaps 32
|
||||
at most. Premade palettes can be found at
|
||||
[lospec.com](https://lospec.com/palette-list).
|
||||
|
||||
## Editors
|
||||
|
||||
### MS Paint
|
||||
@ -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
|
||||
different editor.
|
||||
|
||||
### Aseprite / LibreSprite
|
||||
|
||||
[Aseprite](https://www.aseprite.org/) is a proprietary pixel art editor.
|
||||
It contains a lot of useful features by default such as color palettes and
|
||||
animation tools.
|
||||
|
||||
[LibreSprite](https://libresprite.github.io/) is an open-source fork of Aseprite
|
||||
from before it went proprietary.
|
||||
|
||||
### GIMP
|
||||
|
||||
GIMP is commonly used in the Minetest community. It has quite a high
|
||||
learning curve because many of its features are not immediately
|
||||
obvious.
|
||||
|
||||
When using GIMP, the pencil tool can be selected from the Toolbox:
|
||||
|
||||
<figure>
|
||||
<img src="{{ page.root }}//static/pixel_art_gimp_pencil.png" alt="Pencil in GIMP">
|
||||
</figure>
|
||||
|
||||
It's also advisable to select the Hard edge checkbox for the eraser tool.
|
||||
When using GIMP, make sure to use the Pencil tool with the Pixel brush and a
|
||||
size of 1. It's also advisable to select the "Hard edge" checkbox for the Eraser
|
||||
tool.
|
||||
|
@ -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)
|
||||
- [ItemStacks](#itemstacks)
|
||||
- [Inventory Locations](#inventory-locations)
|
||||
- [Node Inventories](#node-inventories)
|
||||
- [Player Inventories](#player-inventories)
|
||||
- [Detached Inventories](#detached-inventories)
|
||||
- [Lists](#lists)
|
||||
- [Size and Width](#size-and-width)
|
||||
- [Checking Contents](#checking-contents)
|
||||
@ -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 *inventory* is a collection of *inventory lists*, each of which
|
||||
is a 2D grid of ItemStacks.
|
||||
Inventory lists are simply called *lists* in the context
|
||||
of inventories.
|
||||
The point of an inventory is to allow multiple grids when Players
|
||||
and Nodes only have at most one inventory in them.
|
||||
An *inventory* is a collection of *inventory lists*, each of which is a 2D grid
|
||||
of ItemStacks. Inventory lists are referred to as *lists* in the context of
|
||||
inventories.
|
||||
|
||||
Players and nodes only have a single inventory; lists enable you to have
|
||||
multiple grids within that inventory. By default, the player has the "main" list
|
||||
for the bulk of its inventory and a few lists for the crafting system.
|
||||
|
||||
## ItemStacks
|
||||
|
||||
ItemStacks have four components to them: name, count, wear and metadata.
|
||||
ItemStacks have four components to them: `name`, `count`, `wear`, and metadata.
|
||||
|
||||
The item name may be the item name of a registered item, an alias, or an unknown
|
||||
item name.
|
||||
Unknown items are common when users uninstall mods, or when mods remove items without
|
||||
precautions, such as registering aliases.
|
||||
item name. Unknown items are common when users uninstall mods, or when mods
|
||||
remove items without precautions, such as registering aliases.
|
||||
|
||||
```lua
|
||||
print(stack:get_name())
|
||||
@ -58,19 +61,14 @@ if not stack:is_known() then
|
||||
end
|
||||
```
|
||||
|
||||
The count will always be 0 or greater.
|
||||
Through normal gameplay, the count should be no more than the maximum stack size
|
||||
of the item - `stack_max`.
|
||||
However, admin commands and buggy mods may result in stacks exceeding the maximum
|
||||
size.
|
||||
The count will always be 0 or greater. Through normal gameplay, the count should
|
||||
be no more than the maximum stack size of the item - `stack_max`. However, admin
|
||||
commands and buggy mods may result in stacks exceeding the maximum size.
|
||||
|
||||
```lua
|
||||
print(stack:get_stack_max())
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
An ItemStack can be empty, in which case the count will be 0.
|
||||
|
||||
```lua
|
||||
@ -78,7 +76,7 @@ print(stack:get_count())
|
||||
stack:set_count(10)
|
||||
```
|
||||
|
||||
ItemStacks can be constructed in multiple ways using the ItemStack function.
|
||||
ItemStacks can be constructed in multiple ways using the ItemStack function:
|
||||
|
||||
```lua
|
||||
ItemStack() -- name="", count=0
|
||||
@ -87,24 +85,30 @@ ItemStack("default:stone 30")
|
||||
ItemStack({ name = "default:wood", count = 10 })
|
||||
```
|
||||
|
||||
Item metadata is an unlimited key-value store for data about the item.
|
||||
Key-value means that you use a name (called the key) to access the data (called the value).
|
||||
Some keys have special meaning, such as `description` which is used to have a per-stack
|
||||
item description.
|
||||
This will be covered in more detail in the Metadata and Storage chapter.
|
||||
Item metadata is an unlimited key-value store for data about the item. Key-value
|
||||
means that you use a name (called the key) to access the data (called the
|
||||
value). Some keys have special meaning, such as `description` which is used to
|
||||
have a per-stack item description. This will be covered in more detail in the
|
||||
[Storage and Metadata](../map/storage.html) chapter.
|
||||
|
||||
## Inventory Locations
|
||||
|
||||
An Inventory Location is where and how the inventory is stored.
|
||||
There are three types of inventory location: player, node, and detached.
|
||||
An inventory is directly tied to one and only one location - updating the inventory
|
||||
will cause it to update immediately.
|
||||
An Inventory Location is where and how the inventory is stored. There are three
|
||||
types of inventory location: player, node, and detached. An inventory is
|
||||
directly tied to one and only one location - updating the inventory will cause
|
||||
it to update immediately.
|
||||
|
||||
Node inventories are related to the position of a specific node, such as a chest.
|
||||
The node must be loaded because it is stored in [node metadata](../map/storage.html#metadata).
|
||||
### Node Inventories
|
||||
|
||||
Node inventories are related to the position of a specific node, such as a
|
||||
chest. The node must be loaded because it is stored in
|
||||
[node metadata](../map/storage.html#metadata).
|
||||
|
||||
```lua
|
||||
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*.
|
||||
@ -118,6 +122,8 @@ The location of an inventory reference can be found like so:
|
||||
local location = inv:get_location()
|
||||
```
|
||||
|
||||
### Player Inventories
|
||||
|
||||
Player inventories can be obtained similarly or using a player reference.
|
||||
The player must be online to access their inventory.
|
||||
|
||||
@ -127,8 +133,10 @@ local inv = minetest.get_inventory({ type="player", name="player1" })
|
||||
local inv = player:get_inventory()
|
||||
```
|
||||
|
||||
A detached inventory is one which is independent of players or nodes.
|
||||
Detached inventories also don't save over a restart.
|
||||
### Detached Inventories
|
||||
|
||||
A detached inventory is one that is independent of players or nodes. Detached
|
||||
inventories also don't save over a restart.
|
||||
|
||||
```lua
|
||||
local inv = minetest.get_inventory({
|
||||
@ -142,10 +150,9 @@ before accessing it:
|
||||
minetest.create_detached_inventory("inventory_name")
|
||||
```
|
||||
|
||||
The create_detached_inventory function accepts 3 arguments, where only the first - the inventory name -
|
||||
is required.
|
||||
The second argument takes a table of callbacks, which can be used to control how
|
||||
players interact with the inventory:
|
||||
The `create_detached_inventory` function accepts 3 arguments, where only the
|
||||
first - the inventory name - is required. The second argument takes a table of
|
||||
callbacks, which can be used to control how players interact with the inventory:
|
||||
|
||||
```lua
|
||||
-- Input only detached inventory
|
||||
@ -177,9 +184,10 @@ On the contrary, action callbacks - starting with `on_` - don't have a return va
|
||||
|
||||
## Lists
|
||||
|
||||
Inventory Lists are a concept used to allow multiple grids to be stored inside a single location.
|
||||
This is especially useful for the player as there are a number of common lists
|
||||
which all games have, such as the *main* inventory and *craft* slots.
|
||||
Inventory Lists are a concept used to allow multiple grids to be stored inside a
|
||||
single location. This is especially useful for the player as there are several
|
||||
common lists that all games have, such as the *main* inventory and *craft*
|
||||
slots.
|
||||
|
||||
### Size and Width
|
||||
|
||||
|
@ -18,8 +18,6 @@ basic requirements for many mods.
|
||||
- [Item Aliases](#item-aliases)
|
||||
- [Textures](#textures)
|
||||
- [Registering a basic node](#registering-a-basic-node)
|
||||
- [Actions and Callbacks](#actions-and-callbacks)
|
||||
- [on_use](#onuse)
|
||||
- [Crafting](#crafting)
|
||||
- [Shaped](#shaped)
|
||||
- [Shapeless](#shapeless)
|
||||
@ -29,25 +27,24 @@ basic requirements for many mods.
|
||||
|
||||
## What are Nodes and Items?
|
||||
|
||||
Nodes, craftitems, and tools are all Items.
|
||||
An item is something that could be found in an inventory -
|
||||
even though it may not be possible through normal gameplay.
|
||||
Nodes, craftitems, and tools are all Items. An item is something that could be
|
||||
found in an inventory - even if it isn't possible through normal gameplay.
|
||||
|
||||
A node is an item which can be placed or be found in the world.
|
||||
Every position in the world must be occupied with one and only one node -
|
||||
seemingly blank positions are usually air nodes.
|
||||
A node is an item that can be placed or be found in the world. Every position
|
||||
in the world must be occupied with one and only one node - seemingly blank
|
||||
positions are usually air nodes.
|
||||
|
||||
A craftitem can't be placed and is only found in inventories or as a dropped item
|
||||
in the world.
|
||||
|
||||
A tool has the ability to wear and typically has non-default digging capabilities.
|
||||
In the future, it's likely that craftitems and tools will merge into one type of
|
||||
item, as the distinction between them is rather artificial.
|
||||
A tool has the ability to wear and typically has non-default digging
|
||||
capabilities. In the future, it's likely that craftitems and tools will merge
|
||||
into one type of item, as the distinction between them is rather artificial.
|
||||
|
||||
## Registering Items
|
||||
|
||||
Item definitions consist of an *item name* and a *definition table*.
|
||||
The definition table contains attributes which affect the behaviour of the item.
|
||||
The definition table contains attributes that affect the behaviour of the item.
|
||||
|
||||
```lua
|
||||
minetest.register_craftitem("modname:itemname", {
|
||||
@ -63,25 +60,26 @@ following format:
|
||||
|
||||
modname:itemname
|
||||
|
||||
The modname is the name of the mod in which the item is registered, and the
|
||||
item name is the name of the item itself.
|
||||
The item name should be relevant to what the item is and can't already be registered.
|
||||
The modname is the name of the mod in which the item is registered, and the item
|
||||
name is the name of the item itself. The item name should be relevant to what
|
||||
the item is and can't already be registered.
|
||||
|
||||
Both `modname` and `itemname` should only contain lowercase letters, numbers,
|
||||
and underscores.
|
||||
|
||||
### Item Aliases
|
||||
|
||||
Items can also have *aliases* pointing to their name.
|
||||
An *alias* is a pseudo-item name which results in the engine treating any
|
||||
occurrences of the alias as if it were the item name.
|
||||
There are two main common uses of this:
|
||||
Items can also have *aliases* pointing to their name. An *alias* is a
|
||||
pseudo-item name that results in the engine treating any occurrences of the
|
||||
alias as if it were the item name. There are two main common uses of this:
|
||||
|
||||
* Renaming removed items to something else.
|
||||
There may be unknown nodes in the world and in inventories if an item is
|
||||
removed from a mod without any corrective code.
|
||||
* Adding a shortcut. `/giveme dirt` is easier than `/giveme default:dirt`.
|
||||
|
||||
Registering an alias is pretty simple.
|
||||
A good way to remember the order of the arguments is `from → to` where
|
||||
*from* is the alias and *to* is the target.
|
||||
Registering an alias is pretty simple. A good way to remember the order of the
|
||||
arguments is `from → to` where *from* is the alias and *to* is the target.
|
||||
|
||||
```lua
|
||||
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.
|
||||
It is often better to use the PNG format.
|
||||
|
||||
Textures in Minetest are usually 16 by 16 pixels.
|
||||
They can be any resolution, but it is recommended that they are in the order of 2,
|
||||
for example, 16, 32, 64, or 128.
|
||||
This is because other resolutions may not be supported correctly on older devices,
|
||||
resulting in decreased performance.
|
||||
Textures in Minetest are usually 16 by 16 pixels. They can be any resolution,
|
||||
but it is recommended that they are in the order of 2, for example, 16, 32, 64,
|
||||
or 128. This is because other resolutions may not be supported correctly on
|
||||
older devices, especially phones, resulting in degraded performance.
|
||||
|
||||
## Registering a basic node
|
||||
|
||||
Registering nodes is similar to registering items, just with a different
|
||||
function:
|
||||
|
||||
```lua
|
||||
minetest.register_node("mymod: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.
|
||||
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:
|
||||
@ -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)
|
||||
|
||||
Remember that +Y is upwards in Minetest, as is the convention with
|
||||
3D computer graphics.
|
||||
most 3D computer games.
|
||||
|
||||
```lua
|
||||
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.
|
||||
Caves are cut out of the world after all the other nodes in an area have generated.
|
||||
|
||||
## Actions and Callbacks
|
||||
|
||||
Minetest heavily uses a callback-based modding design.
|
||||
Callbacks can be placed in the item definition table to allow response to various
|
||||
different user events.
|
||||
|
||||
### on_use
|
||||
|
||||
By default, the use callback is triggered when a player left-clicks with an item.
|
||||
Having a use callback prevents the item being used to dig nodes.
|
||||
One common use of the use callback is for food:
|
||||
|
||||
```lua
|
||||
minetest.register_craftitem("mymod:mudpie", {
|
||||
description = "Alien Mud Pie",
|
||||
inventory_image = "myfood_mudpie.png",
|
||||
on_use = minetest.item_eat(20),
|
||||
})
|
||||
```
|
||||
|
||||
The number supplied to the minetest.item_eat function is the number of hit points
|
||||
healed when this food is consumed.
|
||||
Each heart icon the player has is worth two hitpoints.
|
||||
A player can usually have up to 10 hearts, which is equal to 20 hitpoints.
|
||||
Hitpoints don't have to be integers (whole numbers); they can be decimals.
|
||||
|
||||
minetest.item_eat() is a function which returns a function, setting it
|
||||
as the on_use callback.
|
||||
This means the code above is roughly similar to this:
|
||||
|
||||
```lua
|
||||
minetest.register_craftitem("mymod:mudpie", {
|
||||
description = "Alien Mud Pie",
|
||||
inventory_image = "myfood_mudpie.png",
|
||||
on_use = function(...)
|
||||
return minetest.do_item_eat(20, nil, ...)
|
||||
end,
|
||||
})
|
||||
```
|
||||
|
||||
By understanding how item_eat works by simply returning a function, it's
|
||||
possible to modify it to do more complex behaviour such as play a custom sound.
|
||||
|
||||
## Crafting
|
||||
|
||||
There are several types of crafting recipe available, indicated by the `type`
|
||||
@ -327,6 +287,7 @@ minetest.register_craft({
|
||||
})
|
||||
```
|
||||
|
||||
|
||||
## Tools, Capabilities, and Dig Types
|
||||
|
||||
Dig types are groups which are used to define how strong a node is when dug
|
||||
|
@ -202,7 +202,7 @@ local function emerge_callback(pos, action,
|
||||
-- Send progress message
|
||||
if context.total_blocks == context.loaded_blocks then
|
||||
minetest.chat_send_all("Finished loading blocks!")
|
||||
end
|
||||
else
|
||||
local perc = 100 * context.loaded_blocks / context.total_blocks
|
||||
local msg = string.format("Loading blocks %d/%d (%.2f%%)",
|
||||
context.loaded_blocks, context.total_blocks, perc)
|
||||
|
@ -80,7 +80,7 @@ minetest.register_abm({
|
||||
nodenames = {"default:dirt_with_grass"},
|
||||
neighbors = {"default:water_source", "default:water_flowing"},
|
||||
interval = 10.0, -- Run every 10 seconds
|
||||
chance = 50, -- Select every 1 in 50 nodes
|
||||
chance = 50, -- One node has a chance of 1 in 50 to get selected
|
||||
action = function(pos, node, active_object_count,
|
||||
active_object_count_wider)
|
||||
local pos = {x = pos.x, y = pos.y + 1, z = pos.z}
|
||||
|
@ -8,19 +8,21 @@ redirect_from: /en/chapters/chat.html
|
||||
cmd_online:
|
||||
level: warning
|
||||
title: Offline players can run commands
|
||||
message: <p>A player name is passed instead of a player object because mods
|
||||
can run commands on behalf of offline players. For example, the IRC
|
||||
bridge allows players to run commands without joining the game.</p>
|
||||
message: |
|
||||
A player name is passed instead of a player object because mods
|
||||
can run commands on behalf of offline players. For example, the IRC
|
||||
bridge allows players to run commands without joining the game.
|
||||
|
||||
<p>So make sure that you don't assume that the player is online.
|
||||
You can check by seeing if <pre>minetest.get_player_by_name</pre> returns a player.</p>
|
||||
So make sure that you don't assume that the player is online.
|
||||
You can check by seeing if `minetest.get_player_by_name` returns a player.
|
||||
|
||||
cb_cmdsprivs:
|
||||
level: warning
|
||||
title: Privileges and Chat Commands
|
||||
message: The shout privilege isn't needed for a player to trigger this callback.
|
||||
This is because chat commands are implemented in Lua, and are just
|
||||
chat messages that begin with a /.
|
||||
message: |
|
||||
The shout privilege isn't needed for a player to trigger this callback.
|
||||
This is because chat commands are implemented in Lua, and are just
|
||||
chat messages that begin with a /.
|
||||
|
||||
---
|
||||
|
||||
@ -29,15 +31,20 @@ cb_cmdsprivs:
|
||||
Mods can interact with player chat, including
|
||||
sending messages, intercepting messages, and registering chat commands.
|
||||
|
||||
- [Sending Messages to All Players](#sending-messages-to-all-players)
|
||||
- [Sending Messages to Specific Players](#sending-messages-to-specific-players)
|
||||
- [Sending Messages](#sending-messages)
|
||||
- [To All Players](#to-all-players)
|
||||
- [To Specific Players](#to-specific-players)
|
||||
- [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)
|
||||
|
||||
## 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
|
||||
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.
|
||||
|
||||
## 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
|
||||
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
|
||||
[privilege](privileges.html) meaning that only players with the `interact` privilege can run the command.
|
||||
|
||||
`param` is a string containing everything a player writes after the chatcommand
|
||||
name. For example, if a user types `/grantme one,two,three` then `param` will be
|
||||
`one,two,three`.
|
||||
|
||||
Chat commands can return up to two values,
|
||||
the first being a Boolean indicating success, and the second being a
|
||||
message to send to the user.
|
||||
|
||||
{% include notice.html notice=page.cmd_online %}
|
||||
|
||||
## Complex Subcommands
|
||||
### Accepting Multiple Arguments
|
||||
|
||||
It is often required to make complex chat commands, such as:
|
||||
<a name="complex-subcommands"></a>
|
||||
|
||||
* `/msg <to> <message>`
|
||||
* `/team join <teamname>`
|
||||
* `/team leave <teamname>`
|
||||
* `/team list`
|
||||
`param` gives you all the arguments to a chat command in a single string. It's
|
||||
common for chat commands to need to extract multiple arguments. There are two
|
||||
ways of doing this, either using Minetest's string split or Lua patterns.
|
||||
|
||||
This is usually done using [Lua patterns](https://www.lua.org/pil/20.2.html).
|
||||
Patterns are a way of extracting stuff from text using rules.
|
||||
#### Using string.split
|
||||
|
||||
A string can be split up into words using `string.split(" ")`:
|
||||
|
||||
```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:
|
||||
@ -123,13 +161,6 @@ Patterns would probably be the
|
||||
[lua-users.org tutorial](http://lua-users.org/wiki/PatternsTutorial)
|
||||
or the [PIL documentation](https://www.lua.org/pil/20.2.html).
|
||||
|
||||
<p class="book_hide">
|
||||
There is also a library written by the author of this book which can be used
|
||||
to make complex chat commands without patterns called
|
||||
<a href="https://gitlab.com/rubenwardy/ChatCmdBuilder">Chat Command Builder</a>.
|
||||
</p>
|
||||
|
||||
|
||||
## Intercepting Messages
|
||||
|
||||
To intercept a message, use register_on_chat_message:
|
||||
|
@ -35,16 +35,16 @@ unexpected windows tend to disrupt gameplay.
|
||||
|
||||
- [Real or Legacy Coordinates](#real-or-legacy-coordinates)
|
||||
- [Anatomy of a Formspec](#anatomy-of-a-formspec)
|
||||
- [Elements](#elements)
|
||||
- [Header](#header)
|
||||
- [Elements](#elements)
|
||||
- [Header](#header)
|
||||
- [Guessing Game](#guessing-game)
|
||||
- [Padding and Spacing](#padding-and-spacing)
|
||||
- [Receiving Formspec Submissions](#receiving-formspec-submissions)
|
||||
- [Contexts](#contexts)
|
||||
- [Padding and Spacing](#padding-and-spacing)
|
||||
- [Receiving Formspec Submissions](#receiving-formspec-submissions)
|
||||
- [Contexts](#contexts)
|
||||
- [Formspec Sources](#formspec-sources)
|
||||
- [Node Meta Formspecs](#node-meta-formspecs)
|
||||
- [Player Inventory Formspecs](#player-inventory-formspecs)
|
||||
- [Your Turn](#your-turn)
|
||||
- [Node Meta Formspecs](#node-meta-formspecs)
|
||||
- [Player Inventory Formspecs](#player-inventory-formspecs)
|
||||
- [Your Turn](#your-turn)
|
||||
|
||||
|
||||
## 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.
|
||||
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 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
|
||||
user, indexed by strings. Named elements will appear in the field under their own
|
||||
name, but only if they are relevent for the event that caused the submission.
|
||||
For example, a button element will only appear in fields if that particular button
|
||||
was pressed.
|
||||
name, depending on the event. Some elements will only be submitted if they caused
|
||||
the event, such as buttons, and some elements will always appear in submissions,
|
||||
such as fields.
|
||||
|
||||
{% include notice.html notice=page.submit_vuln %}
|
||||
|
||||
@ -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
|
||||
formname is `""`.
|
||||
|
||||
There are a number of different mods which allow multiple mods to customise
|
||||
the player inventory. The officially recommended mod is
|
||||
[Simple Fast Inventory (sfinv)](sfinv.html), and is included in Minetest Game.
|
||||
There are a number of different mods which allow multiple mods to customise the
|
||||
player inventory. Minetest Game uses
|
||||
[SFINV](https://github.com/rubenwardy/sfinv/blob/master/Tutorial.md).
|
||||
|
||||
|
||||
### Your Turn
|
||||
|
@ -1,242 +1,5 @@
|
||||
---
|
||||
title: "SFINV: Inventory Formspec"
|
||||
layout: default
|
||||
root: ../..
|
||||
idx: 4.7
|
||||
sitemap: false
|
||||
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
|
||||
})
|
||||
```
|
||||
|
@ -24,7 +24,7 @@ The methods of ObjectRefs will always return nil when invalid, since Minetest 5.
|
||||
Any call will essentially be ignored.
|
||||
|
||||
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
|
||||
-- This only works in Minetest 5.2+
|
||||
|
@ -20,7 +20,6 @@ editor to provide alerts to any mistakes.
|
||||
- [Configuring LuaCheck](#configuring-luacheck)
|
||||
- [Troubleshooting](#troubleshooting)
|
||||
- [Using with editor](#using-with-editor)
|
||||
- [Checking Commits with Travis](#checking-commits-with-travis)
|
||||
|
||||
## Installing LuaCheck
|
||||
|
||||
@ -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
|
||||
available.
|
||||
|
||||
* **Atom** - `linter-luacheck`.
|
||||
* **VSCode** - Ctrl+P, then paste: `ext install dwenegar.vscode-luacheck`
|
||||
* **Sublime** - Install using package-control:
|
||||
[SublimeLinter](https://github.com/SublimeLinter/SublimeLinter),
|
||||
[SublimeLinter-luacheck](https://github.com/SublimeLinter/SublimeLinter-luacheck).
|
||||
|
||||
## Checking Commits with Travis
|
||||
|
||||
If your project is public and is on Github, you can use TravisCI - a free service
|
||||
to run jobs on commits to check them. This means that every commit you push will
|
||||
be checked against LuaCheck, and a green tick or red cross will be displayed next to them
|
||||
depending on whether LuaCheck finds any mistakes. This is especially helpful for
|
||||
when your project receives a pull request - you'll be able to see the LuaCheck output
|
||||
without downloading the code.
|
||||
|
||||
First, you should visit [travis-ci.org](https://travis-ci.org/) and sign in with
|
||||
your Github account. Then find your project's repo in your Travis profile,
|
||||
and enable Travis by flipping the switch.
|
||||
|
||||
Next, create a file called .travis.yml with the following content:
|
||||
|
||||
```yml
|
||||
language: generic
|
||||
sudo: false
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- luarocks
|
||||
before_install:
|
||||
- luarocks install --local luacheck
|
||||
script:
|
||||
- $HOME/.luarocks/bin/luacheck .
|
||||
notifications:
|
||||
email: false
|
||||
```
|
||||
|
||||
If your project is a game rather than a mod or mod pack,
|
||||
change the line after `script:` to:
|
||||
|
||||
```yml
|
||||
- $HOME/.luarocks/bin/luacheck mods/
|
||||
```
|
||||
|
||||
Now commit and push to Github. Go to your project's page on Github, and click
|
||||
'commits'. You should see an orange disc next to the commit you just made.
|
||||
After awhile it should change either into a green tick or a red cross depending on the
|
||||
outcome of LuaCheck. In either case, you can click the icon to see the build logs
|
||||
and the output of LuaCheck.
|
||||
|
@ -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) |
|
||||
[Text version](https://github.com/minetest/minetest/blob/master/doc/lua_api.txt).
|
||||
* Explore the [Developer Wiki](http://dev.minetest.net/Main_Page).
|
||||
* Look at [existing mods](https://forum.minetest.net/viewforum.php?f=11).
|
||||
|
||||
### Lua Programming
|
||||
|
198
_en/quality/translations.md
Normal file
198
_en/quality/translations.md
Normal 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.
|
@ -17,9 +17,8 @@ we discussed how to structure your code avoid this.
|
||||
- [Your First Test](#your-first-test)
|
||||
- [init.lua](#initlua)
|
||||
- [api.lua](#apilua)
|
||||
- [tests/api_spec.lua](#testsapispeclua)
|
||||
- [tests/api_spec.lua](#testsapi_speclua)
|
||||
- [Mocking: Using External Functions](#mocking-using-external-functions)
|
||||
- [Checking Commits with Travis](#checking-commits-with-travis)
|
||||
- [Conclusion](#conclusion)
|
||||
|
||||
## 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
|
||||
|
||||
Unit tests will greatly increase the quality and reliability of your project if used
|
||||
|
@ -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.
|
||||
|
||||
- [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 in Lua](#programmare-in-lua)
|
||||
- [Editor di codice](#editor-di-codice)
|
||||
- [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)
|
||||
|
||||
|
||||
## Programmare
|
||||
|
||||
Programmare è l'azione di prendere un problema, come ordinare una lista di oggetti, e tramutarlo in dei passaggi che il computer può comprendere.
|
||||
|
||||
Insegnarti i processi logici della programmazione non rientra nell'ambito di questo libro; tuttavia, i seguenti siti sono alquanto utili per approfondire l'argomento:
|
||||
|
||||
* [Codecademy](http://www.codecademy.com/) è una delle migliori risorse per imparare come scrivere codice; offre un'esperienza guidata interattiva.
|
||||
* [Scratch](https://scratch.mit.edu) è una buona risorsa quando si comincia dalle basi assolute, imparando le tecniche di problem solving necessarie per la programmazione.\\
|
||||
Scratch è *ideato per insegnare ai bambini* e non è un linguaggio serio di programmazione.
|
||||
* [Programming with Mosh](https://www.youtube.com/user/programmingwithmosh) is
|
||||
a good YouTube series to learn programming.
|
||||
|
||||
### Programmare in Lua
|
||||
|
||||
Neanche insegnarti come programmare in lua rientra nell'ambito di questo libro.
|
||||
Tuttavia, se mastichi l'inglese puoi rifarti a quest'altro libro, ["Programming in Lua"](https://www.lua.org/pil/contents.html), per un'eccellente infarinatura sull'argomento. Se invece l'inglese non è il tuo forte, troverai comunque svariate guide in italiano in giro per la rete.
|
||||
|
||||
|
||||
## Editor di codice
|
||||
|
||||
Un editor di codice con evidenziamento delle parole chiave è sufficiente per scrivere script in Lua.
|
||||
@ -56,126 +71,6 @@ Tra gli editor più famosi che ben si prestano a lavorare in Lua, troviamo:
|
||||
(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
|
||||
|
||||
L'essere locale o globale di una variabile determina da dove è possibile accederci.
|
||||
@ -211,6 +106,9 @@ one()
|
||||
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.
|
||||
Crearne di ulteriori è considerato cattiva programmazione, e Minetest ci avviserà di ciò:
|
||||
|
||||
|
14
_it/index.md
14
_it/index.md
@ -8,7 +8,7 @@ idx: 0.1
|
||||
---
|
||||
|
||||
<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>con modifiche di <a href="http://rc.minetest.tv/">Shara</a></span>
|
||||
@ -17,20 +17,20 @@ idx: 0.1
|
||||
|
||||
## 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.
|
||||
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.
|
||||
|
||||
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).
|
||||
|
||||
### 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).
|
||||
* Posta nel [Topic sul Forum](https://forum.minetest.net/viewtopic.php?f=14&t=10729).
|
||||
* Apri una [Segnalazione su GitLab](https://gitlab.com/rubenwardy/minetest_modding_book/-/issues).
|
||||
* Rispondi alla [Discussione sul Forum](https://forum.minetest.net/viewtopic.php?f=14&t=10729).
|
||||
* [Contattami (in inglese)](https://rubenwardy.com/contact/).
|
||||
* Voglia di contribuire?
|
||||
[Leggi il README](https://gitlab.com/rubenwardy/minetest_modding_book/-/blob/master/README.md).
|
||||
|
184
_it/items/callbacks.md
Normal file
184
_it/items/callbacks.md
Normal 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.
|
@ -17,8 +17,6 @@ Saper registrare nuovi nodi, oggetti fabbricabili e conseguenti ricette, è un r
|
||||
- [Alias](#alias)
|
||||
- [Texture](#texture)
|
||||
- [Registrare un nodo base](#registrare-un-nodo-base)
|
||||
- [Azioni e callback](#azioni-e-callback)
|
||||
- [on_use](#onuse)
|
||||
- [Fabbricazione](#fabbricazione)
|
||||
- [Fisse (shaped)](#fisse-shaped)
|
||||
- [Informi (shapeless)](#informi-shapeless)
|
||||
@ -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.
|
||||
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
|
||||
|
||||
Ci sono diversi tipi di ricette di fabbricazione disponibili, indicate dalla proprietà `type`.
|
||||
|
@ -184,7 +184,7 @@ local function mio_callback(pos, action,
|
||||
-- Invia messaggio indicante il progresso
|
||||
if param.blocchi_totali == param.blocchi_caricati then
|
||||
minetest.chat_send_all("Ho finito di caricare blocchi!")
|
||||
end
|
||||
else
|
||||
local percentuale = 100 * param.blocchi_caricati / param.blocchi_totali
|
||||
local msg = string.format("Caricamento blocchi %d/%d (%.2f%%)",
|
||||
param.blocchi_caricati, param.blocchi_totali, percentuale)
|
||||
|
@ -1,5 +1,5 @@
|
||||
---
|
||||
title: Oggetti, giocatori e entità
|
||||
title: Oggetti, giocatori ed entità
|
||||
layout: default
|
||||
root: ../..
|
||||
idx: 3.4
|
||||
|
@ -8,17 +8,19 @@ redirect_from: /it/chapters/chat.html
|
||||
cmd_online:
|
||||
level: warning
|
||||
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.
|
||||
Per esempio, il bridge IRC permette ai giocatori di eseguire comandi senza dover entrare in gioco.</p>
|
||||
message: |
|
||||
Viene passato il nome del giocatore al posto del giocatore in sé perché le mod possono eseguire comandi in vece di un giocatore offline.
|
||||
Per esempio, il ponte IRC permette ai giocatori di eseguire comandi senza dover entrare in gioco.
|
||||
|
||||
<p>Assicurati quindi di non dar per scontato che un giocatore sia connesso.
|
||||
Puoi controllare ciò tramite <pre>minetest.get_player_by_name</pre>, per vedere se ritorna qualcosa o meno.</p>
|
||||
Assicurati quindi di non dar per scontato che un giocatore sia connesso.
|
||||
Puoi controllare ciò tramite `minetest.get_player_by_name`, per vedere se ritorna qualcosa o meno.
|
||||
|
||||
cb_cmdsprivs:
|
||||
level: warning
|
||||
title: Privilegi e comandi
|
||||
message: Il privilegio shout non è necessario per far sì che un giocatore attivi questo callback.
|
||||
Questo perché i comandi sono implementati in Lua, e sono semplicemente dei messaggi in chat che iniziano con /.
|
||||
message: |
|
||||
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.
|
||||
|
||||
- [Inviare messaggi a tutti i giocatori](#inviare-messaggi-a-tutti-i-giocatori)
|
||||
- [Inviare messaggi a giocatori specifici](#inviare-messaggi-a-giocatori-specifici)
|
||||
- [Inviare messaggi](#inviare-messaggi)
|
||||
- [A tutti i giocatori](#a-tutti-i-giocatori)
|
||||
- [A giocatori specifici](#a-giocatori-specifici)
|
||||
- [Comandi](#comandi)
|
||||
- [Complex Subcommands](#complex-subcommands)
|
||||
- [Intercettare i messaggi](#interecettare-i-messaggi)
|
||||
- [Accettare più argomenti](#accettare-più-argomenti)
|
||||
- [Usare string.split](#usare-stringsplit)
|
||||
- [Usare i pattern Lua](#usare-i-pattern-lua)
|
||||
- [Intercettare i messaggi](#intercettare-i-messaggi)
|
||||
|
||||
## 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`:
|
||||
|
||||
@ -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.
|
||||
|
||||
## 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`:
|
||||
|
||||
@ -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.
|
||||
|
||||
`param` è una stringa contenente tutto ciò che un giocatore scrive dopo il nome del comando.
|
||||
Per esempio, in `/grantme uno,due,tre`, `param` equivarrà a `uno,due,tre`.
|
||||
|
||||
I comandi ritornano un massimo di due valori, dove il primo è un booleano che indica l'eventuale successo, mentre il secondo è un messaggio da inviare all'utente.
|
||||
|
||||
{% include notice.html notice=page.cmd_online %}
|
||||
|
||||
## Sottocomandi complessi
|
||||
|
||||
È spesso necessario creare dei comandi complessi, come per esempio:
|
||||
### Accettare più argomenti
|
||||
|
||||
* `/msg <a> <messaggio>`
|
||||
* `/team entra <nometeam>`
|
||||
* `/team esci <nometeam>`
|
||||
* `/team elenco`
|
||||
Non è raro che i comandi richiedano più argomenti, come per esempio `/squadra entra <nome_squadra>`.
|
||||
Ci sono due modi per implementare ciò: usare `string.split` o i pattern Lua.
|
||||
|
||||
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
|
||||
local a, msg = string.match(param, "^([%a%d_-]+) (*+)$")
|
||||
@ -130,7 +166,7 @@ minetest.register_on_chat_message(function(name, message)
|
||||
end)
|
||||
```
|
||||
|
||||
Ritornando false, si permette al messaggio di essere inviato.
|
||||
Ritornando `false`, si permette al messaggio di essere inviato.
|
||||
In verità `return false` può anche essere omesso in quanto `nil` verrebbe ritornato implicitamente, e nil è trattato come false.
|
||||
|
||||
{% include notice.html notice=page.cb_cmdsprivs %}
|
||||
|
@ -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)
|
||||
- [Anatomia di un formspec](#anatomia-di-un-formspec)
|
||||
- [Elementi](#elementi)
|
||||
- [Intestazione](#intestazione)
|
||||
- [Elementi](#elementi)
|
||||
- [Intestazione](#intestazione)
|
||||
- [Esempio: indovina un numero](#esempio-indovina-un-numero)
|
||||
- [Imbottitura e spaziatura](#imbottitura-e-spaziatura)
|
||||
- [Ricevere i moduli di compilazione](#ricevere-i-moduli-di-compilazione)
|
||||
- [Contesti](#contesti)
|
||||
- [Imbottitura e spaziatura](#imbottitura-e-spaziatura)
|
||||
- [Ricevere i moduli di compilazione](#ricevere-i-moduli-di-compilazione)
|
||||
- [Contesti](#contesti)
|
||||
- [Ricavare un formspec](#ricavare-un-formspec)
|
||||
- [Formspec nei nodi](#formspec-nei-nodi)
|
||||
- [Inventario del giocatore](#inventario-del-giocatore)
|
||||
- [Il tuo turno](#il-tuo-turno)
|
||||
- [Formspec nei nodi](#formspec-nei-nodi)
|
||||
- [Inventario del giocatore](#inventario-del-giocatore)
|
||||
- [Il tuo turno](#il-tuo-turno)
|
||||
|
||||
|
||||
## 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 è `""`.
|
||||
|
||||
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
|
||||
|
||||
|
@ -61,4 +61,4 @@ Quando si imposta una sovrascrittura, sovrascriverà qualsiasi altro suo simile
|
||||
|
||||
* **Sonic**: Imposta il moltiplicatore di velocità a un valore elevato (almeno 6) quando un giocatore entra in gioco;
|
||||
* **Super rimbalzo**: Aumenta il valore del salto in modo che il giocatore possa saltare 20 metri (1 cubo = 1 metro);
|
||||
* **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.
|
||||
|
@ -1,223 +1,4 @@
|
||||
---
|
||||
title: "SFINV"
|
||||
layout: default
|
||||
root: ../..
|
||||
idx: 4.7
|
||||
description: una mod per rendere più semplice la creazione di un inventario complesso
|
||||
redirect_from: /it/chapters/sfinv.html
|
||||
sitemap: false
|
||||
redirect_to: "https://github.com/rubenwardy/sfinv/blob/master/Tutorial.md"
|
||||
---
|
||||
|
||||
## 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
|
||||
})
|
||||
```
|
||||
|
@ -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.
|
||||
|
||||
- [Installare LuaCheck](#installare-luacheck)
|
||||
- [Windows](#windows)
|
||||
- [Linux](#linux)
|
||||
- [Windows](#windows)
|
||||
- [Linux](#linux)
|
||||
- [Eseguire LuaCheck](#eseguire-luacheck)
|
||||
- [Configurare LuaCheck](#configurare-luacheck)
|
||||
- [Risoluzione problemi](#risoluzione-problemi)
|
||||
- [Risoluzione problemi](#risoluzione-problemi)
|
||||
- [Uso nell'editor](#uso-nelleditor)
|
||||
- [Controllare i commit con Travis](#controllare-i-commit-con-travis)
|
||||
|
||||
## Installare LuaCheck
|
||||
|
||||
### Windows
|
||||
|
||||
Basta scaricare luacheck.exe dall'apposita [pagina delle 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
|
||||
|
||||
@ -53,7 +52,7 @@ Su Linux, esegui `luacheck .` nella cartella principale del progetto.
|
||||
## Configurare LuaCheck
|
||||
|
||||
Crea un file chiamato .luacheckrc nella cartella principale del tuo progetto.
|
||||
Questa può essere quella di un gioco, di 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:
|
||||
|
||||
@ -80,7 +79,7 @@ read_globals = {
|
||||
|
||||
Poi, avrai bisogno di assicurarti che funzioni eseguendo LuaCheck: dovresti ottenere molti meno errori questa volta.
|
||||
Partendo dal primo errore, modifica il codice per risolvere il problema, o modifica la configurazione di LuaCheck se il codice è corretto.
|
||||
Dai un occhio alla lista sottostante.
|
||||
Dài un occhio alla lista sottostante.
|
||||
|
||||
### 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.
|
||||
Queste sono disponibili nella maggior parte degli editor, come:
|
||||
|
||||
* **Atom** - `linter-luacheck`;
|
||||
* **VSCode** - Ctrl+P, poi incolla: `ext install dwenegar.vscode-luacheck`;
|
||||
* **Sublime** - Installala usando package-control:
|
||||
[SublimeLinter](https://github.com/SublimeLinter/SublimeLinter),
|
||||
[SublimeLinter-luacheck](https://github.com/SublimeLinter/SublimeLinter-luacheck).
|
||||
|
||||
## Controllare i commit con Travis
|
||||
|
||||
Se il tuo progetto è pubblico ed è su Github, puoi usare TravisCI - un servizio gratuito per eseguire controlli sui commit.
|
||||
Questo significa che ogni commit pushato verrà controllato secondo le impostazioni di LuaCheck, e una spunta verde o una X rossa appariranno al suo fianco per segnalare se sono stati trovati errori o meno.
|
||||
Ciò è utile soprattutto per quando il tuo progetto riceve una richiesta di modifica (*pull request*) per verificare se il codice è scritto bene senza doverlo scaricare.
|
||||
|
||||
Prima di tutto, vai su [travis-ci.org](https://travis-ci.org/) ed esegui l'accesso con il tuo account Github.
|
||||
Dopodiché cerca la repo del tuo progetto nel tuo profilo Travis, e abilita Travis cliccando sull'apposito bottone.
|
||||
|
||||
Poi, crea un file chiamato `.travis.yml` con il seguente contenuto:
|
||||
|
||||
```yml
|
||||
language: generic
|
||||
sudo: false
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- luarocks
|
||||
before_install:
|
||||
- luarocks install --local luacheck
|
||||
script:
|
||||
- $HOME/.luarocks/bin/luacheck .
|
||||
notifications:
|
||||
email: false
|
||||
```
|
||||
|
||||
Se il tuo progetto è un gioco piuttosto che una mod o un pacchetto di mod, cambia la riga dopo `script:` con:
|
||||
|
||||
```yml
|
||||
- $HOME/.luarocks/bin/luacheck mods/
|
||||
```
|
||||
|
||||
Ora esegui il commit e il push su Github.
|
||||
Vai alla pagina del tuo progetto e clicca su "commits".
|
||||
Dovresti vedere un cerchietto arancione di fianco al commit che hai appena fatto.
|
||||
Dopo un po' di tempo il cerchietto dovrebbe cambiare in una spunta verde o in una X rossa (a seconda dell'esito, come detto prima).
|
||||
In entrambi i casi, puoi cliccare l'icona per vedere il resoconto dell'operazione e l'output di LuaCheck.
|
||||
|
@ -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/) |
|
||||
[versione solo testo](https://github.com/minetest/minetest/blob/master/doc/lua_api.txt).
|
||||
* Esplora la [Wiki Sviluppatore](http://dev.minetest.net/Main_Page).
|
||||
* Spulcia le [mod esistenti](https://forum.minetest.net/viewforum.php?f=11).
|
||||
|
||||
### Programmazione in Lua
|
||||
|
153
_it/quality/translations.md
Normal file
153
_it/quality/translations.md
Normal 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.
|
@ -13,11 +13,10 @@ Scrivere i testing d'unità per le funzioni dove vengono chiamate quelle di Mine
|
||||
|
||||
- [Installare Busted](#installare-busted)
|
||||
- [Il tuo primo test](#il-tuo-primo-test)
|
||||
- [init.lua](#initlua)
|
||||
- [api.lua](#apilua)
|
||||
- [tests/api_spec.lua](#testsapispeclua)
|
||||
- [init.lua](#initlua)
|
||||
- [api.lua](#apilua)
|
||||
- [tests/api_spec.lua](#testsapi_speclua)
|
||||
- [Simulare: usare funzioni esterne](#simulare-usare-funzioni-esterne)
|
||||
- [Controllare commit con Travis](#controllare-commit-con-travis)
|
||||
- [Conclusione](#conclusione)
|
||||
|
||||
## 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
|
||||
|
||||
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).
|
||||
|
@ -3,7 +3,15 @@ layout: compress
|
||||
---
|
||||
|
||||
<!doctype html>
|
||||
|
||||
{% assign pathsplit = page.url | split: '/' %}
|
||||
{% assign language = pathsplit[1] %}
|
||||
{% assign language_info = site.data.languages | where: "code", language %}
|
||||
{% if language_info %}
|
||||
<html lang="{{ language }}">
|
||||
{% else %}
|
||||
<html>
|
||||
{% endif %}
|
||||
<head>
|
||||
<title>{% if page.homepage %}{% else %}{{ page.title }} - {% endif %}Minetest Modding Book</title>
|
||||
<meta charset="UTF-8">
|
||||
@ -13,8 +21,20 @@ layout: compress
|
||||
<meta name="author" content="rubenwardy">
|
||||
<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>
|
||||
<link rel="stylesheet" href="{{ page.root }}/static/style.css?v=2">
|
||||
<link rel="stylesheet" href="{{ page.root }}/static/style.css?v=3">
|
||||
</head>
|
||||
<body>
|
||||
<div id="container">
|
||||
|
@ -8,14 +8,15 @@ layout: base
|
||||
|
||||
{% if language == "_it" %}
|
||||
{% assign language = "it" %}
|
||||
{% assign links = site.it | sort: "idx" %}
|
||||
{% elseif language == "_de" %}
|
||||
{% assign links = site.it %}
|
||||
{% elsif language == "_de" %}
|
||||
{% assign language = "de" %}
|
||||
{% assign links = site.de | sort: "idx" %}
|
||||
{% assign links = site.de %}
|
||||
{% else %}
|
||||
{% assign language = "en" %}
|
||||
{% assign links = site.en | sort: "idx" %}
|
||||
{% assign links = site.en %}
|
||||
{% endif %}
|
||||
{% assign links = links | where_exp: "item", "item.sitemap != false" | sort: "idx" %}
|
||||
|
||||
{% assign num = 0 %}
|
||||
|
||||
@ -66,7 +67,7 @@ layout: base
|
||||
</ul>
|
||||
|
||||
<footer>
|
||||
© 2014-21
|
||||
© 2014-{{ site.time | date: '%Y' }}
|
||||
{% if language == "en" %}
|
||||
| Helpful? Consider
|
||||
<a href="https://rubenwardy.com/donate/">donating</a>
|
||||
|
@ -33,68 +33,6 @@ figure {
|
||||
padding: 0 0 0 6px;
|
||||
}
|
||||
|
||||
.notice-info {
|
||||
background: #ececec !important;
|
||||
border: 1px solid #aaa !important;
|
||||
}
|
||||
|
||||
.notice-danger {
|
||||
background: #fcc !important;
|
||||
border: 1px solid #a66 !important;
|
||||
}
|
||||
|
||||
.notice-warning {
|
||||
background: #FED;
|
||||
border: 1px solid #fc9;
|
||||
}
|
||||
|
||||
.notice-tip {
|
||||
background: #ccf;
|
||||
border: 1px solid #66a;
|
||||
}
|
||||
|
||||
.notice-green {
|
||||
background: #161;
|
||||
border: 1px solid #393;
|
||||
}
|
||||
|
||||
.notice {
|
||||
margin: 10px;
|
||||
display: block;
|
||||
padding: 5px;
|
||||
border-radius: 5px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.notice p {
|
||||
margin: 0 0 17px 0;
|
||||
}
|
||||
|
||||
.notice p:last-child {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.notice > span {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
width: 40px;
|
||||
font-size: 24px;
|
||||
text-align: center;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.notice > div {
|
||||
margin-left: 35px;
|
||||
}
|
||||
|
||||
.notice h2 {
|
||||
margin: 0 0 5px 0;
|
||||
padding: 0 0 2px 0;
|
||||
font-size: 100%;
|
||||
}
|
||||
|
||||
.header-link, .anchor {
|
||||
text-decoration: none;
|
||||
color: #bbb;
|
||||
|
61
_sass/_notice.scss
Normal file
61
_sass/_notice.scss
Normal 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
17
_sass/_table.scss
Normal 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;
|
||||
}
|
53
index.html
53
index.html
@ -1,35 +1,36 @@
|
||||
---
|
||||
layout: none
|
||||
---
|
||||
<!doctype html>
|
||||
<html>
|
||||
<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>
|
||||
<body>
|
||||
Detecting and redirecting to the correct translation.<br><br>
|
||||
|
||||
<a href="en/index.html">View English Translation</a><br><br>
|
||||
|
||||
<script>
|
||||
var languages = {{ site.data.languages | jsonify }};
|
||||
function getLanguage() {
|
||||
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>
|
||||
<main>
|
||||
<h1>Minetest Modding Book</h1>
|
||||
<p>Detecting and redirecting to the correct translation.</p>
|
||||
<p>
|
||||
<a href="en/index.html">View English Translation</a>
|
||||
</p>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -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 %}
|
||||
|
||||
[
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 98 KiB |
Binary file not shown.
Before Width: | Height: | Size: 28 KiB |
@ -4,3 +4,5 @@
|
||||
@import "main";
|
||||
@import "content";
|
||||
@import "code";
|
||||
@import "notice";
|
||||
@import "table";
|
||||
|
Loading…
Reference in New Issue
Block a user