Compare commits
1 Commits
master
...
build-luaa
Author | SHA1 | Date | |
---|---|---|---|
|
6929d5fd0d |
@ -5,13 +5,14 @@ variables:
|
||||
LC_ALL: C.UTF-8
|
||||
|
||||
before_script:
|
||||
- apk add python3 py3-markdown
|
||||
- rm Gemfile.lock
|
||||
- bundle install
|
||||
|
||||
test:
|
||||
stage: test
|
||||
interruptible: true
|
||||
script:
|
||||
- python3 utils/update_lua_api.py
|
||||
- bundle exec jekyll build -d test
|
||||
artifacts:
|
||||
paths:
|
||||
@ -23,6 +24,7 @@ pages:
|
||||
stage: deploy
|
||||
interruptible: true
|
||||
script:
|
||||
- python3 utils/update_lua_api.py
|
||||
- bundle exec jekyll build -d public
|
||||
artifacts:
|
||||
paths:
|
||||
|
2
Gemfile
2
Gemfile
@ -1,10 +1,8 @@
|
||||
source "https://rubygems.org"
|
||||
|
||||
gem "jekyll"
|
||||
gem "webrick"
|
||||
|
||||
group :jekyll_plugins do
|
||||
gem "jekyll-sitemap"
|
||||
gem "jekyll-redirect-from"
|
||||
gem "jekyll-sass-converter", "~> 2.0"
|
||||
end
|
||||
|
34
Gemfile.lock
34
Gemfile.lock
@ -4,31 +4,31 @@ GEM
|
||||
addressable (2.7.0)
|
||||
public_suffix (>= 2.0.2, < 5.0)
|
||||
colorator (1.1.0)
|
||||
concurrent-ruby (1.1.8)
|
||||
em-websocket (0.5.2)
|
||||
concurrent-ruby (1.1.7)
|
||||
em-websocket (0.5.1)
|
||||
eventmachine (>= 0.12.9)
|
||||
http_parser.rb (~> 0.6.0)
|
||||
eventmachine (1.2.7)
|
||||
ffi (1.15.0)
|
||||
ffi (1.13.1)
|
||||
forwardable-extended (2.6.0)
|
||||
http_parser.rb (0.6.0)
|
||||
i18n (1.8.10)
|
||||
i18n (1.8.5)
|
||||
concurrent-ruby (~> 1.0)
|
||||
jekyll (4.2.0)
|
||||
jekyll (4.1.1)
|
||||
addressable (~> 2.4)
|
||||
colorator (~> 1.0)
|
||||
em-websocket (~> 0.5)
|
||||
i18n (~> 1.0)
|
||||
jekyll-sass-converter (~> 2.0)
|
||||
jekyll-watch (~> 2.0)
|
||||
kramdown (~> 2.3)
|
||||
kramdown (~> 2.1)
|
||||
kramdown-parser-gfm (~> 1.0)
|
||||
liquid (~> 4.0)
|
||||
mercenary (~> 0.4.0)
|
||||
pathutil (~> 0.9)
|
||||
rouge (~> 3.0)
|
||||
safe_yaml (~> 1.0)
|
||||
terminal-table (~> 2.0)
|
||||
terminal-table (~> 1.8)
|
||||
jekyll-redirect-from (0.16.0)
|
||||
jekyll (>= 3.3, < 5.0)
|
||||
jekyll-sass-converter (2.1.0)
|
||||
@ -37,39 +37,37 @@ GEM
|
||||
jekyll (>= 3.7, < 5.0)
|
||||
jekyll-watch (2.2.1)
|
||||
listen (~> 3.0)
|
||||
kramdown (2.3.1)
|
||||
kramdown (2.3.0)
|
||||
rexml
|
||||
kramdown-parser-gfm (1.1.0)
|
||||
kramdown (~> 2.0)
|
||||
liquid (4.0.3)
|
||||
listen (3.5.1)
|
||||
listen (3.2.1)
|
||||
rb-fsevent (~> 0.10, >= 0.10.3)
|
||||
rb-inotify (~> 0.9, >= 0.9.10)
|
||||
mercenary (0.4.0)
|
||||
pathutil (0.16.2)
|
||||
forwardable-extended (~> 2.6)
|
||||
public_suffix (4.0.6)
|
||||
rb-fsevent (0.11.0)
|
||||
public_suffix (4.0.5)
|
||||
rb-fsevent (0.10.4)
|
||||
rb-inotify (0.10.1)
|
||||
ffi (~> 1.0)
|
||||
rexml (3.2.5)
|
||||
rouge (3.26.0)
|
||||
rexml (3.2.4)
|
||||
rouge (3.22.0)
|
||||
safe_yaml (1.0.5)
|
||||
sassc (2.4.0)
|
||||
ffi (~> 1.9)
|
||||
terminal-table (2.0.0)
|
||||
terminal-table (1.8.0)
|
||||
unicode-display_width (~> 1.1, >= 1.1.1)
|
||||
unicode-display_width (1.7.0)
|
||||
webrick (1.7.0)
|
||||
|
||||
PLATFORMS
|
||||
x86_64-linux
|
||||
ruby
|
||||
|
||||
DEPENDENCIES
|
||||
jekyll
|
||||
jekyll-redirect-from
|
||||
jekyll-sitemap
|
||||
webrick
|
||||
|
||||
BUNDLED WITH
|
||||
2.2.16
|
||||
2.0.2
|
||||
|
427
LICENSE
427
LICENSE
@ -1,427 +0,0 @@
|
||||
Attribution-ShareAlike 4.0 International
|
||||
|
||||
=======================================================================
|
||||
|
||||
Creative Commons Corporation ("Creative Commons") is not a law firm and
|
||||
does not provide legal services or legal advice. Distribution of
|
||||
Creative Commons public licenses does not create a lawyer-client or
|
||||
other relationship. Creative Commons makes its licenses and related
|
||||
information available on an "as-is" basis. Creative Commons gives no
|
||||
warranties regarding its licenses, any material licensed under their
|
||||
terms and conditions, or any related information. Creative Commons
|
||||
disclaims all liability for damages resulting from their use to the
|
||||
fullest extent possible.
|
||||
|
||||
Using Creative Commons Public Licenses
|
||||
|
||||
Creative Commons public licenses provide a standard set of terms and
|
||||
conditions that creators and other rights holders may use to share
|
||||
original works of authorship and other material subject to copyright
|
||||
and certain other rights specified in the public license below. The
|
||||
following considerations are for informational purposes only, are not
|
||||
exhaustive, and do not form part of our licenses.
|
||||
|
||||
Considerations for licensors: Our public licenses are
|
||||
intended for use by those authorized to give the public
|
||||
permission to use material in ways otherwise restricted by
|
||||
copyright and certain other rights. Our licenses are
|
||||
irrevocable. Licensors should read and understand the terms
|
||||
and conditions of the license they choose before applying it.
|
||||
Licensors should also secure all rights necessary before
|
||||
applying our licenses so that the public can reuse the
|
||||
material as expected. Licensors should clearly mark any
|
||||
material not subject to the license. This includes other CC-
|
||||
licensed material, or material used under an exception or
|
||||
limitation to copyright. More considerations for licensors:
|
||||
wiki.creativecommons.org/Considerations_for_licensors
|
||||
|
||||
Considerations for the public: By using one of our public
|
||||
licenses, a licensor grants the public permission to use the
|
||||
licensed material under specified terms and conditions. If
|
||||
the licensor's permission is not necessary for any reason--for
|
||||
example, because of any applicable exception or limitation to
|
||||
copyright--then that use is not regulated by the license. Our
|
||||
licenses grant only permissions under copyright and certain
|
||||
other rights that a licensor has authority to grant. Use of
|
||||
the licensed material may still be restricted for other
|
||||
reasons, including because others have copyright or other
|
||||
rights in the material. A licensor may make special requests,
|
||||
such as asking that all changes be marked or described.
|
||||
Although not required by our licenses, you are encouraged to
|
||||
respect those requests where reasonable. More considerations
|
||||
for the public:
|
||||
wiki.creativecommons.org/Considerations_for_licensees
|
||||
|
||||
=======================================================================
|
||||
|
||||
Creative Commons Attribution-ShareAlike 4.0 International Public
|
||||
License
|
||||
|
||||
By exercising the Licensed Rights (defined below), You accept and agree
|
||||
to be bound by the terms and conditions of this Creative Commons
|
||||
Attribution-ShareAlike 4.0 International Public License ("Public
|
||||
License"). To the extent this Public License may be interpreted as a
|
||||
contract, You are granted the Licensed Rights in consideration of Your
|
||||
acceptance of these terms and conditions, and the Licensor grants You
|
||||
such rights in consideration of benefits the Licensor receives from
|
||||
making the Licensed Material available under these terms and
|
||||
conditions.
|
||||
|
||||
|
||||
Section 1 -- Definitions.
|
||||
|
||||
a. Adapted Material means material subject to Copyright and Similar
|
||||
Rights that is derived from or based upon the Licensed Material
|
||||
and in which the Licensed Material is translated, altered,
|
||||
arranged, transformed, or otherwise modified in a manner requiring
|
||||
permission under the Copyright and Similar Rights held by the
|
||||
Licensor. For purposes of this Public License, where the Licensed
|
||||
Material is a musical work, performance, or sound recording,
|
||||
Adapted Material is always produced where the Licensed Material is
|
||||
synched in timed relation with a moving image.
|
||||
|
||||
b. Adapter's License means the license You apply to Your Copyright
|
||||
and Similar Rights in Your contributions to Adapted Material in
|
||||
accordance with the terms and conditions of this Public License.
|
||||
|
||||
c. BY-SA Compatible License means a license listed at
|
||||
creativecommons.org/compatiblelicenses, approved by Creative
|
||||
Commons as essentially the equivalent of this Public License.
|
||||
|
||||
d. Copyright and Similar Rights means copyright and/or similar rights
|
||||
closely related to copyright including, without limitation,
|
||||
performance, broadcast, sound recording, and Sui Generis Database
|
||||
Rights, without regard to how the rights are labeled or
|
||||
categorized. For purposes of this Public License, the rights
|
||||
specified in Section 2(b)(1)-(2) are not Copyright and Similar
|
||||
Rights.
|
||||
|
||||
e. Effective Technological Measures means those measures that, in the
|
||||
absence of proper authority, may not be circumvented under laws
|
||||
fulfilling obligations under Article 11 of the WIPO Copyright
|
||||
Treaty adopted on December 20, 1996, and/or similar international
|
||||
agreements.
|
||||
|
||||
f. Exceptions and Limitations means fair use, fair dealing, and/or
|
||||
any other exception or limitation to Copyright and Similar Rights
|
||||
that applies to Your use of the Licensed Material.
|
||||
|
||||
g. License Elements means the license attributes listed in the name
|
||||
of a Creative Commons Public License. The License Elements of this
|
||||
Public License are Attribution and ShareAlike.
|
||||
|
||||
h. Licensed Material means the artistic or literary work, database,
|
||||
or other material to which the Licensor applied this Public
|
||||
License.
|
||||
|
||||
i. Licensed Rights means the rights granted to You subject to the
|
||||
terms and conditions of this Public License, which are limited to
|
||||
all Copyright and Similar Rights that apply to Your use of the
|
||||
Licensed Material and that the Licensor has authority to license.
|
||||
|
||||
j. Licensor means the individual(s) or entity(ies) granting rights
|
||||
under this Public License.
|
||||
|
||||
k. Share means to provide material to the public by any means or
|
||||
process that requires permission under the Licensed Rights, such
|
||||
as reproduction, public display, public performance, distribution,
|
||||
dissemination, communication, or importation, and to make material
|
||||
available to the public including in ways that members of the
|
||||
public may access the material from a place and at a time
|
||||
individually chosen by them.
|
||||
|
||||
l. Sui Generis Database Rights means rights other than copyright
|
||||
resulting from Directive 96/9/EC of the European Parliament and of
|
||||
the Council of 11 March 1996 on the legal protection of databases,
|
||||
as amended and/or succeeded, as well as other essentially
|
||||
equivalent rights anywhere in the world.
|
||||
|
||||
m. You means the individual or entity exercising the Licensed Rights
|
||||
under this Public License. Your has a corresponding meaning.
|
||||
|
||||
|
||||
Section 2 -- Scope.
|
||||
|
||||
a. License grant.
|
||||
|
||||
1. Subject to the terms and conditions of this Public License,
|
||||
the Licensor hereby grants You a worldwide, royalty-free,
|
||||
non-sublicensable, non-exclusive, irrevocable license to
|
||||
exercise the Licensed Rights in the Licensed Material to:
|
||||
|
||||
a. reproduce and Share the Licensed Material, in whole or
|
||||
in part; and
|
||||
|
||||
b. produce, reproduce, and Share Adapted Material.
|
||||
|
||||
2. Exceptions and Limitations. For the avoidance of doubt, where
|
||||
Exceptions and Limitations apply to Your use, this Public
|
||||
License does not apply, and You do not need to comply with
|
||||
its terms and conditions.
|
||||
|
||||
3. Term. The term of this Public License is specified in Section
|
||||
6(a).
|
||||
|
||||
4. Media and formats; technical modifications allowed. The
|
||||
Licensor authorizes You to exercise the Licensed Rights in
|
||||
all media and formats whether now known or hereafter created,
|
||||
and to make technical modifications necessary to do so. The
|
||||
Licensor waives and/or agrees not to assert any right or
|
||||
authority to forbid You from making technical modifications
|
||||
necessary to exercise the Licensed Rights, including
|
||||
technical modifications necessary to circumvent Effective
|
||||
Technological Measures. For purposes of this Public License,
|
||||
simply making modifications authorized by this Section 2(a)
|
||||
(4) never produces Adapted Material.
|
||||
|
||||
5. Downstream recipients.
|
||||
|
||||
a. Offer from the Licensor -- Licensed Material. Every
|
||||
recipient of the Licensed Material automatically
|
||||
receives an offer from the Licensor to exercise the
|
||||
Licensed Rights under the terms and conditions of this
|
||||
Public License.
|
||||
|
||||
b. Additional offer from the Licensor -- Adapted Material.
|
||||
Every recipient of Adapted Material from You
|
||||
automatically receives an offer from the Licensor to
|
||||
exercise the Licensed Rights in the Adapted Material
|
||||
under the conditions of the Adapter's License You apply.
|
||||
|
||||
c. No downstream restrictions. You may not offer or impose
|
||||
any additional or different terms or conditions on, or
|
||||
apply any Effective Technological Measures to, the
|
||||
Licensed Material if doing so restricts exercise of the
|
||||
Licensed Rights by any recipient of the Licensed
|
||||
Material.
|
||||
|
||||
6. No endorsement. Nothing in this Public License constitutes or
|
||||
may be construed as permission to assert or imply that You
|
||||
are, or that Your use of the Licensed Material is, connected
|
||||
with, or sponsored, endorsed, or granted official status by,
|
||||
the Licensor or others designated to receive attribution as
|
||||
provided in Section 3(a)(1)(A)(i).
|
||||
|
||||
b. Other rights.
|
||||
|
||||
1. Moral rights, such as the right of integrity, are not
|
||||
licensed under this Public License, nor are publicity,
|
||||
privacy, and/or other similar personality rights; however, to
|
||||
the extent possible, the Licensor waives and/or agrees not to
|
||||
assert any such rights held by the Licensor to the limited
|
||||
extent necessary to allow You to exercise the Licensed
|
||||
Rights, but not otherwise.
|
||||
|
||||
2. Patent and trademark rights are not licensed under this
|
||||
Public License.
|
||||
|
||||
3. To the extent possible, the Licensor waives any right to
|
||||
collect royalties from You for the exercise of the Licensed
|
||||
Rights, whether directly or through a collecting society
|
||||
under any voluntary or waivable statutory or compulsory
|
||||
licensing scheme. In all other cases the Licensor expressly
|
||||
reserves any right to collect such royalties.
|
||||
|
||||
|
||||
Section 3 -- License Conditions.
|
||||
|
||||
Your exercise of the Licensed Rights is expressly made subject to the
|
||||
following conditions.
|
||||
|
||||
a. Attribution.
|
||||
|
||||
1. If You Share the Licensed Material (including in modified
|
||||
form), You must:
|
||||
|
||||
a. retain the following if it is supplied by the Licensor
|
||||
with the Licensed Material:
|
||||
|
||||
i. identification of the creator(s) of the Licensed
|
||||
Material and any others designated to receive
|
||||
attribution, in any reasonable manner requested by
|
||||
the Licensor (including by pseudonym if
|
||||
designated);
|
||||
|
||||
ii. a copyright notice;
|
||||
|
||||
iii. a notice that refers to this Public License;
|
||||
|
||||
iv. a notice that refers to the disclaimer of
|
||||
warranties;
|
||||
|
||||
v. a URI or hyperlink to the Licensed Material to the
|
||||
extent reasonably practicable;
|
||||
|
||||
b. indicate if You modified the Licensed Material and
|
||||
retain an indication of any previous modifications; and
|
||||
|
||||
c. indicate the Licensed Material is licensed under this
|
||||
Public License, and include the text of, or the URI or
|
||||
hyperlink to, this Public License.
|
||||
|
||||
2. You may satisfy the conditions in Section 3(a)(1) in any
|
||||
reasonable manner based on the medium, means, and context in
|
||||
which You Share the Licensed Material. For example, it may be
|
||||
reasonable to satisfy the conditions by providing a URI or
|
||||
hyperlink to a resource that includes the required
|
||||
information.
|
||||
|
||||
3. If requested by the Licensor, You must remove any of the
|
||||
information required by Section 3(a)(1)(A) to the extent
|
||||
reasonably practicable.
|
||||
|
||||
b. ShareAlike.
|
||||
|
||||
In addition to the conditions in Section 3(a), if You Share
|
||||
Adapted Material You produce, the following conditions also apply.
|
||||
|
||||
1. The Adapter's License You apply must be a Creative Commons
|
||||
license with the same License Elements, this version or
|
||||
later, or a BY-SA Compatible License.
|
||||
|
||||
2. You must include the text of, or the URI or hyperlink to, the
|
||||
Adapter's License You apply. You may satisfy this condition
|
||||
in any reasonable manner based on the medium, means, and
|
||||
context in which You Share Adapted Material.
|
||||
|
||||
3. You may not offer or impose any additional or different terms
|
||||
or conditions on, or apply any Effective Technological
|
||||
Measures to, Adapted Material that restrict exercise of the
|
||||
rights granted under the Adapter's License You apply.
|
||||
|
||||
|
||||
Section 4 -- Sui Generis Database Rights.
|
||||
|
||||
Where the Licensed Rights include Sui Generis Database Rights that
|
||||
apply to Your use of the Licensed Material:
|
||||
|
||||
a. for the avoidance of doubt, Section 2(a)(1) grants You the right
|
||||
to extract, reuse, reproduce, and Share all or a substantial
|
||||
portion of the contents of the database;
|
||||
|
||||
b. if You include all or a substantial portion of the database
|
||||
contents in a database in which You have Sui Generis Database
|
||||
Rights, then the database in which You have Sui Generis Database
|
||||
Rights (but not its individual contents) is Adapted Material,
|
||||
including for purposes of Section 3(b); and
|
||||
|
||||
c. You must comply with the conditions in Section 3(a) if You Share
|
||||
all or a substantial portion of the contents of the database.
|
||||
|
||||
For the avoidance of doubt, this Section 4 supplements and does not
|
||||
replace Your obligations under this Public License where the Licensed
|
||||
Rights include other Copyright and Similar Rights.
|
||||
|
||||
|
||||
Section 5 -- Disclaimer of Warranties and Limitation of Liability.
|
||||
|
||||
a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE
|
||||
EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS
|
||||
AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF
|
||||
ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS,
|
||||
IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION,
|
||||
WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS,
|
||||
ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT
|
||||
KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT
|
||||
ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU.
|
||||
|
||||
b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE
|
||||
TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION,
|
||||
NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT,
|
||||
INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES,
|
||||
COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR
|
||||
USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN
|
||||
ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR
|
||||
DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR
|
||||
IN PART, THIS LIMITATION MAY NOT APPLY TO YOU.
|
||||
|
||||
c. The disclaimer of warranties and limitation of liability provided
|
||||
above shall be interpreted in a manner that, to the extent
|
||||
possible, most closely approximates an absolute disclaimer and
|
||||
waiver of all liability.
|
||||
|
||||
|
||||
Section 6 -- Term and Termination.
|
||||
|
||||
a. This Public License applies for the term of the Copyright and
|
||||
Similar Rights licensed here. However, if You fail to comply with
|
||||
this Public License, then Your rights under this Public License
|
||||
terminate automatically.
|
||||
|
||||
b. Where Your right to use the Licensed Material has terminated under
|
||||
Section 6(a), it reinstates:
|
||||
|
||||
1. automatically as of the date the violation is cured, provided
|
||||
it is cured within 30 days of Your discovery of the
|
||||
violation; or
|
||||
|
||||
2. upon express reinstatement by the Licensor.
|
||||
|
||||
For the avoidance of doubt, this Section 6(b) does not affect any
|
||||
right the Licensor may have to seek remedies for Your violations
|
||||
of this Public License.
|
||||
|
||||
c. For the avoidance of doubt, the Licensor may also offer the
|
||||
Licensed Material under separate terms or conditions or stop
|
||||
distributing the Licensed Material at any time; however, doing so
|
||||
will not terminate this Public License.
|
||||
|
||||
d. Sections 1, 5, 6, 7, and 8 survive termination of this Public
|
||||
License.
|
||||
|
||||
|
||||
Section 7 -- Other Terms and Conditions.
|
||||
|
||||
a. The Licensor shall not be bound by any additional or different
|
||||
terms or conditions communicated by You unless expressly agreed.
|
||||
|
||||
b. Any arrangements, understandings, or agreements regarding the
|
||||
Licensed Material not stated herein are separate from and
|
||||
independent of the terms and conditions of this Public License.
|
||||
|
||||
|
||||
Section 8 -- Interpretation.
|
||||
|
||||
a. For the avoidance of doubt, this Public License does not, and
|
||||
shall not be interpreted to, reduce, limit, restrict, or impose
|
||||
conditions on any use of the Licensed Material that could lawfully
|
||||
be made without permission under this Public License.
|
||||
|
||||
b. To the extent possible, if any provision of this Public License is
|
||||
deemed unenforceable, it shall be automatically reformed to the
|
||||
minimum extent necessary to make it enforceable. If the provision
|
||||
cannot be reformed, it shall be severed from this Public License
|
||||
without affecting the enforceability of the remaining terms and
|
||||
conditions.
|
||||
|
||||
c. No term or condition of this Public License will be waived and no
|
||||
failure to comply consented to unless expressly agreed to by the
|
||||
Licensor.
|
||||
|
||||
d. Nothing in this Public License constitutes or may be interpreted
|
||||
as a limitation upon, or waiver of, any privileges and immunities
|
||||
that apply to the Licensor or You, including from the legal
|
||||
processes of any jurisdiction or authority.
|
||||
|
||||
|
||||
=======================================================================
|
||||
|
||||
Creative Commons is not a party to its public
|
||||
licenses. Notwithstanding, Creative Commons may elect to apply one of
|
||||
its public licenses to material it publishes and in those instances
|
||||
will be considered the “Licensor.” The text of the Creative Commons
|
||||
public licenses is dedicated to the public domain under the CC0 Public
|
||||
Domain Dedication. Except for the limited purpose of indicating that
|
||||
material is shared under a Creative Commons public license or as
|
||||
otherwise permitted by the Creative Commons policies published at
|
||||
creativecommons.org/policies, Creative Commons does not authorize the
|
||||
use of the trademark "Creative Commons" or any other trademark or logo
|
||||
of Creative Commons without its prior written consent including,
|
||||
without limitation, in connection with any unauthorized modifications
|
||||
to any of its public licenses or any other arrangements,
|
||||
understandings, or agreements concerning use of licensed material. For
|
||||
the avoidance of doubt, this paragraph does not form part of the
|
||||
public licenses.
|
||||
|
||||
Creative Commons may be contacted at creativecommons.org.
|
16
README.md
16
README.md
@ -1,4 +1,4 @@
|
||||
# Luanti Modding Book
|
||||
# Minetest Modding Book
|
||||
|
||||
[![Build status](https://gitlab.com/rubenwardy/minetest_modding_book/badges/master/pipeline.svg)](https://gitlab.com/rubenwardy/minetest_modding_book/pipelines)<br>
|
||||
[Read Online](https://rubenwardy.com/minetest_modding_book/)
|
||||
@ -23,25 +23,15 @@ License: CC-BY-SA 3.0
|
||||
I'm happy to fix the formatting of any chapters. It is
|
||||
the writing which is the hard bit, not the formatting.
|
||||
|
||||
### Chapter and Writing Guide
|
||||
### Chapter Guidelines
|
||||
|
||||
Grammar and such:
|
||||
|
||||
* British English, except when referring common code words like `color` and
|
||||
`initialize`.
|
||||
* Prefer pronounless text, but `you` if you must. Never `we` nor `I`.
|
||||
* Titles and subheadings should be in Title Case.
|
||||
* References to code (such as function names) should be formatted as \`inline-code`.
|
||||
* Italics used for emphasis, not necessarily for technical words.
|
||||
* Full stops and correct punctionation, except for lists without full sentences.
|
||||
|
||||
Formatting:
|
||||
|
||||
* Do not rely on anything that isn't printable to a physical book.
|
||||
* Any links must be invisible - ie: if they're removed, then the chapter must
|
||||
still make sense.
|
||||
* Table of contents for each chapter with anchor links.
|
||||
* Add `your turn`s to the end of a chapter when relevant.
|
||||
* Titles and subheadings should be in Title Case.
|
||||
|
||||
### Making a Chapter
|
||||
|
||||
|
@ -1,6 +1,3 @@
|
||||
url: "https://rubenwardy.com"
|
||||
baseurl: "/minetest_modding_book"
|
||||
|
||||
sass:
|
||||
# nested (default), compact, compressed, expanded
|
||||
style: compressed
|
||||
|
@ -130,7 +130,7 @@ programs such as [Geogebra](https://www.geogebra.org).
|
||||
The following code registers a simple biome named grasslands biome:
|
||||
|
||||
```lua
|
||||
core.register_biome({
|
||||
minetest.register_biome({
|
||||
name = "grasslands",
|
||||
node_top = "default:dirt_with_grass",
|
||||
depth_top = 1,
|
||||
@ -174,7 +174,7 @@ details for where it can be placed, and how frequently it occurs.
|
||||
For example:
|
||||
|
||||
```lua
|
||||
core.register_decoration({
|
||||
minetest.register_decoration({
|
||||
deco_type = "simple",
|
||||
place_on = {"base:dirt_with_grass"},
|
||||
sidelen = 16,
|
||||
@ -199,7 +199,7 @@ Schematic decorations are very similar to simple decoration, but involve the pla
|
||||
of a schematic instead of the placement of a single node. For example:
|
||||
|
||||
```lua
|
||||
core.register_decoration({
|
||||
minetest.register_decoration({
|
||||
deco_type = "schematic",
|
||||
place_on = {"base:desert_sand"},
|
||||
sidelen = 16,
|
||||
@ -207,7 +207,7 @@ core.register_decoration({
|
||||
biomes = {"desert"},
|
||||
y_max = 200,
|
||||
y_min = 1,
|
||||
schematic = core.get_modpath("plants") .. "/schematics/cactus.mts",
|
||||
schematic = minetest.get_modpath("plants") .. "/schematics/cactus.mts",
|
||||
flags = "place_center_x, place_center_z",
|
||||
rotation = "random",
|
||||
})
|
||||
@ -229,7 +229,7 @@ to consider registering mapgen aliases of your own if you are making your own ga
|
||||
Mapgen aliases provide information to the core mapgen, and can be registered in the form:
|
||||
|
||||
```lua
|
||||
core.register_alias("mapgen_stone", "base:smoke_stone")
|
||||
minetest.register_alias("mapgen_stone", "base:smoke_stone")
|
||||
```
|
||||
|
||||
At a minimum you should register:
|
||||
|
@ -10,13 +10,13 @@ redirect_from:
|
||||
mapgen_object:
|
||||
level: warning
|
||||
title: LVMs and Mapgen
|
||||
message: Don't use `core.get_voxel_manip()` with mapgen, as it can cause glitches.
|
||||
Use `core.get_mapgen_object("voxelmanip")` instead.
|
||||
message: Don't use `minetest.get_voxel_manip()` with mapgen, as it can cause glitches.
|
||||
Use `minetest.get_mapgen_object("voxelmanip")` instead.
|
||||
---
|
||||
|
||||
## Introduction <!-- omit in toc -->
|
||||
|
||||
The functions outlined in the [Basic Map Operations](../map/environment.html) chapter
|
||||
The functions outlined in the [Basic Map Operations](environment.html) chapter
|
||||
are convenient and easy to use, but for large areas they are inefficient.
|
||||
Every time you call `set_node` or `get_node`, your mod needs to communicate with
|
||||
the engine. This results in constant individual copying operations between the
|
||||
@ -45,7 +45,7 @@ and maximum positions that you need to modify. Then you can create and read into
|
||||
an LVM. For example:
|
||||
|
||||
```lua
|
||||
local vm = core.get_voxel_manip()
|
||||
local vm = minetest.get_voxel_manip()
|
||||
local emin, emax = vm:read_from_map(pos1, pos2)
|
||||
```
|
||||
|
||||
@ -93,7 +93,7 @@ You can find out the content ID for a particular type of node with
|
||||
`get_content_id()`. For example:
|
||||
|
||||
```lua
|
||||
local c_stone = core.get_content_id("default:stone")
|
||||
local c_stone = minetest.get_content_id("default:stone")
|
||||
```
|
||||
|
||||
You can then check whether the node is stone:
|
||||
@ -105,8 +105,9 @@ if data[idx] == c_stone then
|
||||
end
|
||||
```
|
||||
|
||||
Content IDs of a node type may change during load time, so it is recommended that
|
||||
you don't try getting them during this time.
|
||||
It is recommended that you find and store the content IDs of nodes types
|
||||
at load time because the IDs of a node type will never change. Make sure to store
|
||||
the IDs in a local variable for performance reasons.
|
||||
|
||||
Nodes in an LVM data array are stored in reverse co-ordinate order, so you should
|
||||
always iterate in the order `z, y, x`. For example:
|
||||
@ -164,17 +165,18 @@ For setting lighting and param2 data, use the appropriately named
|
||||
|
||||
`write_to_map()` takes a Boolean which is true if you want lighting to be
|
||||
calculated. If you pass false, you need to recalculate lighting at a future
|
||||
time using `core.fix_light`.
|
||||
time using `minetest.fix_light`.
|
||||
|
||||
## Example
|
||||
|
||||
```lua
|
||||
local function grass_to_dirt(pos1, pos2)
|
||||
local c_dirt = core.get_content_id("default:dirt")
|
||||
local c_grass = core.get_content_id("default:dirt_with_grass")
|
||||
-- Get content IDs during load time, and store into a local
|
||||
local c_dirt = minetest.get_content_id("default:dirt")
|
||||
local c_grass = minetest.get_content_id("default:dirt_with_grass")
|
||||
|
||||
local function grass_to_dirt(pos1, pos2)
|
||||
-- Read data into LVM
|
||||
local vm = core.get_voxel_manip()
|
||||
local vm = minetest.get_voxel_manip()
|
||||
local emin, emax = vm:read_from_map(pos1, pos2)
|
||||
local a = VoxelArea:new{
|
||||
MinEdge = emin,
|
||||
|
@ -12,18 +12,20 @@ redirect_from:
|
||||
## Introduction <!-- omit in toc -->
|
||||
|
||||
Understanding the basic structure of a mod's folder is an essential skill when
|
||||
creating mods. In this chapter, you'll learn about how modding in Minetest works
|
||||
and create your first mod.
|
||||
creating mods.
|
||||
|
||||
- [What are Games and Mods?](#what-are-games-and-mods)
|
||||
- [Where are mods stored?](#where-are-mods-stored)
|
||||
- [Creating your first mod](#creating-your-first-mod)
|
||||
- [Mod directory](#mod-directory)
|
||||
- [mod.conf](#modconf)
|
||||
- [init.lua](#initlua)
|
||||
- [Summary](#summary)
|
||||
- [Mod Directory](#mod-directory)
|
||||
- [Dependencies](#dependencies)
|
||||
- [mod.conf](#modconf)
|
||||
- [depends.txt](#dependstxt)
|
||||
- [Mod Packs](#mod-packs)
|
||||
- [Example](#example)
|
||||
- [Mod Folder](#mod-folder)
|
||||
- [depends.txt](#dependstxt-1)
|
||||
- [init.lua](#initlua)
|
||||
- [mod.conf](#modconf-1)
|
||||
|
||||
|
||||
## What are Games and Mods?
|
||||
@ -53,7 +55,7 @@ and is applicable for both game developers and modders.
|
||||
<a name="mod-locations"></a>
|
||||
|
||||
Each mod has its own directory where its Lua code, textures, models, and
|
||||
sounds are placed. Minetest checks in several different locations for
|
||||
sounds are placed. Minetest checks in a number of different locations for
|
||||
mods. These locations are commonly called *mod load paths*.
|
||||
|
||||
For a given world/save game, three mod locations are checked.
|
||||
@ -68,115 +70,103 @@ They are, in order:
|
||||
particular world.
|
||||
Eg: `minetest/worlds/world/worldmods/`
|
||||
|
||||
`minetest` is the user-data directory. You can find the location of the
|
||||
user-data directory by opening up Minetest and clicking
|
||||
"Open User Data Directory" in the Credits tab.
|
||||
Minetest will check the locations in the order given above. If it encounters a mod
|
||||
with a name the same as one found previously, the later mod will be loaded in place
|
||||
of the earlier mod.
|
||||
This means that you can override game mods by placing a mod with the same name
|
||||
in the global mod location.
|
||||
|
||||
When loading mods, Minetest will check each of the above locations in order.
|
||||
If it encounters a mod with a name the same as one found previously, the later
|
||||
mod will be loaded in place of the earlier mod. This means that you can override
|
||||
game mods by placing a mod with the same name in the global mod location.
|
||||
The actual location of each mod load path depends on what operating system you're
|
||||
using, and how you installed Minetest.
|
||||
|
||||
* **Windows:**
|
||||
* For portable builds, ie: from a .zip file, just go to the directory where
|
||||
you extracted the zip and look for the `games`, `mods`, and `worlds`
|
||||
directories.
|
||||
* For installed builds, ie: from a setup.exe,
|
||||
look in C:\\\\Minetest or C:\\\\Games\\Minetest.
|
||||
* **GNU/Linux:**
|
||||
* For system-wide installs, look in `~/.minetest`.
|
||||
Note that `~` means the user home directory, and that files and directories
|
||||
starting with a dot (`.`) are hidden.
|
||||
* For portable installs, look in the build directory.
|
||||
* For Flatpak installs, look in `~/.var/app/net.minetest.Minetest/.minetest/mods/`.
|
||||
* **MacOS**
|
||||
* Look in `~/Library/Application Support/minetest/`.
|
||||
Note that `~` means the user home, ie: `/Users/USERNAME/`.
|
||||
|
||||
## Creating your first mod
|
||||
## Mod Directory
|
||||
|
||||
### Mod directory
|
||||
![Find the mod's directory]({{ page.root }}/static/folder_modfolder.jpg)
|
||||
|
||||
Go to the global mods directory (About > Open user data directory > mods) and
|
||||
create a new folder called "mymod". `mymod` is the mod name.
|
||||
|
||||
Each mod should have a unique *mod name*, a technical identifier (id) used to
|
||||
refer to the mod. Mod names can include letters, numbers, and underscores. A
|
||||
good name should describe what the mod does, and the directory that contains
|
||||
the components of a mod must have the same name as the mod name. To find out if
|
||||
a mod name is available, try searching for it on
|
||||
A *mod name* is used to refer to a mod. Each mod should have a unique name.
|
||||
Mod names can include letters, numbers, and underscores. A good name should
|
||||
describe what the mod does, and the directory which contains the components of a mod
|
||||
must have the same name as the mod name.
|
||||
To find out if a mod name is available, try searching for it on
|
||||
[content.minetest.net](https://content.minetest.net).
|
||||
|
||||
mymod
|
||||
├── textures
|
||||
│ └── mymod_node.png files
|
||||
├── init.lua
|
||||
└── mod.conf
|
||||
├── init.lua (required) - Runs when the game loads.
|
||||
├── mod.conf (recommended) - Contains description and dependencies.
|
||||
├── textures (optional)
|
||||
│ └── ... any textures or images
|
||||
├── sounds (optional)
|
||||
│ └── ... any sounds
|
||||
└── ... any other files or directories
|
||||
|
||||
Mods only require an init.lua file;
|
||||
Only the init.lua file is required in a mod for it to run on game load;
|
||||
however, mod.conf is recommended and other components may be needed
|
||||
depending on the mod's functionality.
|
||||
|
||||
### mod.conf
|
||||
|
||||
Create a mod.conf file with the following content:
|
||||
|
||||
```
|
||||
name = mymod
|
||||
description = Adds foo, bar, and bo.
|
||||
depends = default
|
||||
```
|
||||
|
||||
This file is used for mod metadata including the mod's name, description, and other
|
||||
information.
|
||||
|
||||
### init.lua
|
||||
|
||||
Create an init.lua file with the following content:
|
||||
|
||||
```lua
|
||||
print("This file will be run at load time!")
|
||||
|
||||
core.register_node("mymod:node", {
|
||||
description = "This is a node",
|
||||
tiles = {"mymod_node.png"},
|
||||
groups = {cracky = 1}
|
||||
})
|
||||
|
||||
core.register_craft({
|
||||
type = "shapeless",
|
||||
output = "mymod:node 3",
|
||||
recipe = { "default:dirt", "default:stone" },
|
||||
})
|
||||
```
|
||||
|
||||
The init.lua file is the entrypoint to a mod, and runs when the mod is loaded.
|
||||
|
||||
|
||||
### Summary
|
||||
|
||||
|
||||
This mod has the name "mymod". It has two text files: init.lua and mod.conf. The
|
||||
script prints a message and then registers a node and a craft recipe – these
|
||||
will be explained later on. There's a single dependency, the
|
||||
[default mod](https://content.minetest.net/metapackages/default/), which is
|
||||
usually found in Minetest Game. There is also a texture in textures/ for the
|
||||
node.
|
||||
|
||||
|
||||
## Dependencies
|
||||
|
||||
A dependency occurs when a mod requires another mod to be loaded before itself.
|
||||
One mod may require another mod's code, items, or other resources to be
|
||||
available for it to use.
|
||||
One mod may require another mod's code, items, or other resources to be available
|
||||
for it to use.
|
||||
|
||||
There are two types of dependencies: hard and optional dependencies.
|
||||
Both require the mod to be loaded first. If the mod being depended on isn't
|
||||
available, a hard dependency will cause the mod to fail to load, while an optional
|
||||
dependency might lead to fewer features being enabled.
|
||||
|
||||
An optional dependency is useful if you want to optionally support another mod;
|
||||
it can enable extra content if the user wishes to use both the mods at the same
|
||||
time.
|
||||
An optional dependency is useful if you want to optionally support another mod; it can
|
||||
enable extra content if the user wishes to use both the mods at the same time.
|
||||
|
||||
Dependencies are specified in a comma-separated list in mod.conf.
|
||||
Dependencies should be listed in mod.conf.
|
||||
|
||||
### mod.conf
|
||||
|
||||
This file is used for mod metadata including the mod's name, description, and other
|
||||
information. For example:
|
||||
|
||||
name = mymod
|
||||
description = Adds foo, bar, and bo.
|
||||
depends = modone, modtwo
|
||||
optional_depends = modthree
|
||||
|
||||
### depends.txt
|
||||
|
||||
For compatibility with 0.4.x versions of Minetest, instead of only specifying
|
||||
dependencies in mod.conf, you need to provide a depends.txt file in which
|
||||
you list all dependencies:
|
||||
|
||||
modone
|
||||
modtwo
|
||||
modthree?
|
||||
|
||||
Each mod name is on its own line, and mod names with a question mark
|
||||
following them are optional dependencies.
|
||||
|
||||
## Mod Packs
|
||||
|
||||
Mods can be grouped into mod packs, which allow multiple mods to be packaged
|
||||
Mods can be grouped into mod packs which allow multiple mods to be packaged
|
||||
and moved together. They are useful if you want to supply multiple mods to
|
||||
a player, but don't want to make them download each one individually.
|
||||
|
||||
modpack1
|
||||
├── modpack.conf (required) - signals that this is a mod pack
|
||||
├── modpack.lua (required) - signals that this is a mod pack
|
||||
├── mod1
|
||||
│ └── ... mod files
|
||||
└── mymod (optional)
|
||||
@ -185,3 +175,43 @@ a player, but don't want to make them download each one individually.
|
||||
Please note that a modpack is not a *game*.
|
||||
Games have their own organisational structure which will be explained in the
|
||||
Games chapter.
|
||||
|
||||
## Example
|
||||
|
||||
Here is an example which puts all of this together:
|
||||
|
||||
### Mod Folder
|
||||
mymod
|
||||
├── textures
|
||||
│ └── mymod_node.png files
|
||||
├── depends.txt
|
||||
├── init.lua
|
||||
└── mod.conf
|
||||
|
||||
### depends.txt
|
||||
default
|
||||
|
||||
### init.lua
|
||||
```lua
|
||||
print("This file will be run at load time!")
|
||||
|
||||
minetest.register_node("mymod:node", {
|
||||
description = "This is a node",
|
||||
tiles = {"mymod_node.png"},
|
||||
groups = {cracky = 1}
|
||||
})
|
||||
```
|
||||
|
||||
### mod.conf
|
||||
name = mymod
|
||||
descriptions = Adds a node
|
||||
depends = default
|
||||
|
||||
This mod has the name "mymod". It has three text files: init.lua, mod.conf,
|
||||
and depends.txt.\\
|
||||
The script prints a message and then registers a node –
|
||||
which will be explained in the next chapter.\\
|
||||
There's a single dependency, the
|
||||
[default mod](https://content.minetest.net/metapackages/default/), which is
|
||||
usually found in Minetest Game.\\
|
||||
There is also a texture in textures/ for the node.
|
||||
|
@ -9,47 +9,26 @@ redirect_from: /en/chapters/lua.html
|
||||
|
||||
## Introduction <!-- omit in toc -->
|
||||
|
||||
In this chapter, you'll learn about scripting in Lua, the tools required
|
||||
to help with this, and some techniques that you may find useful.
|
||||
In this chapter we will talk about scripting in Lua, the tools required,
|
||||
and go over some techniques which you will probably find useful.
|
||||
|
||||
- [Programming](#programming)
|
||||
- [Coding in Lua](#coding-in-lua)
|
||||
- [Code Editors](#code-editors)
|
||||
- [Coding in Lua](#coding-in-lua)
|
||||
- [Program Flow](#program-flow)
|
||||
- [Variable Types](#variable-types)
|
||||
- [Arithmetic Operators](#arithmetic-operators)
|
||||
- [Selection](#selection)
|
||||
- [Logical Operators](#logical-operators)
|
||||
- [Programming](#programming)
|
||||
- [Local and Global Scope](#local-and-global-scope)
|
||||
- [Locals should be used as much as possible](#locals-should-be-used-as-much-as-possible)
|
||||
- [Including other Lua Scripts](#including-other-lua-scripts)
|
||||
|
||||
|
||||
## Programming
|
||||
|
||||
Programming is the action of taking a problem, such as sorting a list
|
||||
of items, and turning it into steps that a computer can understand.
|
||||
Teaching you the logical process of programming is beyond the scope of this book;
|
||||
however, the following websites are quite useful in developing this:
|
||||
|
||||
* [Codecademy](http://www.codecademy.com/) is one of the best resources for
|
||||
learning to write code. It provides an interactive tutorial experience.
|
||||
* [Scratch](https://scratch.mit.edu) is a good resource for starting from
|
||||
absolute basics, and learning the problem-solving techniques required to program.
|
||||
It's great for children and teenagers.
|
||||
* [Programming with Mosh](https://www.youtube.com/user/programmingwithmosh) is
|
||||
a good YouTube series to learn programming.
|
||||
|
||||
### Coding in Lua
|
||||
|
||||
It's also beyond the scope of this book to teach Lua coding.
|
||||
The [Programming in Lua (PiL)](https://www.lua.org/pil/contents.html) book is an
|
||||
excellent introduction to Lua programming.
|
||||
|
||||
|
||||
## Code Editors
|
||||
|
||||
A code editor with code highlighting is sufficient for writing scripts in Lua.
|
||||
Code highlighting uses different colours for words and characters
|
||||
depending on what they represent. This allows you to easily notice
|
||||
mistakes and inconsistencies.
|
||||
|
||||
For example:
|
||||
Code highlighting gives different colours to different words and characters
|
||||
depending on what they mean. This allows you to spot mistakes.
|
||||
|
||||
```lua
|
||||
function ctf.post(team,msg)
|
||||
@ -67,41 +46,151 @@ function ctf.post(team,msg)
|
||||
end
|
||||
```
|
||||
|
||||
Keywords in this example are highlighted, including `if`, `then`, `end`, and `return`.
|
||||
Functions which come with Lua by default, such as `table.insert`, are also highlighted.
|
||||
For example, keywords in the above snippet are highlighted such as if, then, end, and return.
|
||||
table.insert is a function which comes with Lua by default.
|
||||
|
||||
Commonly used editors which are well-suited for Lua include:
|
||||
Here is a list of common editors well suited for Lua.
|
||||
Other editors are available, of course.
|
||||
|
||||
* [VSCode](https://code.visualstudio.com/):
|
||||
open source (as Code-OSS or VSCodium), popular, and has
|
||||
[plugins for Minetest](https://marketplace.visualstudio.com/items?itemName=GreenXenith.minetest-tools).
|
||||
* [Notepad++](http://notepad-plus-plus.org/): simple, Windows-only
|
||||
* Windows: [Notepad++](http://notepad-plus-plus.org/), [Atom](http://atom.io/), [VS Code](https://code.visualstudio.com/)
|
||||
* Linux: Kate, Gedit, [Atom](http://atom.io/), [VS Code](https://code.visualstudio.com/)
|
||||
* OSX: [Atom](http://atom.io/), [VS Code](https://code.visualstudio.com/)
|
||||
|
||||
Other suitable editors are also available.
|
||||
## Coding in Lua
|
||||
|
||||
### Program Flow
|
||||
|
||||
Programs are a series of commands that run one after another.
|
||||
We call these commands "statements."
|
||||
Program flow is how these statements are executed.
|
||||
Different types of flow allow you to skip or jump over sets of commands.
|
||||
There are three main types of flow:
|
||||
|
||||
* Sequence: Just run one statement after another, no skipping.
|
||||
* Selection: Skip over sequences depending on conditions.
|
||||
* Iteration: Repeating, looping. Keep running the same
|
||||
statements until a condition is met.
|
||||
|
||||
So, what do statements in Lua look like?
|
||||
|
||||
```lua
|
||||
local a = 2 -- Set 'a' to 2
|
||||
local b = 2 -- Set 'b' to 2
|
||||
local result = a + b -- Set 'result' to a + b, which is 4
|
||||
a = a + 10
|
||||
print("Sum is "..result)
|
||||
```
|
||||
|
||||
Whoa, what happened there?
|
||||
|
||||
a, b, and result are *variables*. Local variables are declared
|
||||
by using the local keyword, and then given an initial value.
|
||||
Local will be discussed in a bit, as it's part of a very important concept called
|
||||
*scope*.
|
||||
|
||||
The `=` means *assignment*, so `result = a + b` means set "result" to a + b.
|
||||
Variable names can be longer than one character unlike in mathematics, as seen with the "result" variable.
|
||||
It's also worth noting that Lua is *case-sensitive*; A is a different variable to a.
|
||||
|
||||
### Variable Types
|
||||
|
||||
A variable will be only one of the following types and can change type after an
|
||||
assignment.
|
||||
It's good practice to make sure a variable is only ever nil or a single non-nil type.
|
||||
|
||||
| Type | Description | Example |
|
||||
|----------|---------------------------------|----------------|
|
||||
| Nil | Not initialised. The variable is empty, it has no value | `local A`, `D = nil` |
|
||||
| Number | A whole or decimal number. | `local A = 4` |
|
||||
| String | A piece of text | `local D = "one two three"` |
|
||||
| Boolean | True or False | `local is_true = false`, `local E = (1 == 1)` |
|
||||
| Table | Lists | Explained below |
|
||||
| Function | Can run. May require inputs and may return a value | `local result = func(1, 2, 3)` |
|
||||
|
||||
### Arithmetic Operators
|
||||
|
||||
Not an exhaustive list. Doesn't contain every possible operator.
|
||||
|
||||
| Symbol | Purpose | Example |
|
||||
|--------|----------------|---------------------------|
|
||||
| A + B | Addition | 2 + 2 = 4 |
|
||||
| A - B | Subtraction | 2 - 10 = -8 |
|
||||
| A * B | Multiplication | 2 * 2 = 4 |
|
||||
| A / B | Division | 100 / 50 = 2 |
|
||||
| A ^ B | Powers | 2 ^ 2 = 2<sup>2</sup> = 4 |
|
||||
| A .. B | Join strings | "foo" .. "bar" = "foobar" |
|
||||
|
||||
### Selection
|
||||
|
||||
The most basic selection is the if statement. It looks like this:
|
||||
|
||||
```lua
|
||||
local random_number = math.random(1, 100) -- Between 1 and 100.
|
||||
if random_number > 50 then
|
||||
print("Woohoo!")
|
||||
else
|
||||
print("No!")
|
||||
end
|
||||
```
|
||||
|
||||
That example generates a random number between 1 and 100. It then prints
|
||||
"Woohoo!" if that number is bigger than 50, otherwise it prints "No!".
|
||||
What else can you get apart from '>'?
|
||||
|
||||
### Logical Operators
|
||||
|
||||
| Symbol | Purpose | Example |
|
||||
|---------|--------------------------------------|-------------------------------------------------------------|
|
||||
| A == B | Equals | 1 == 1 (true), 1 == 2 (false) |
|
||||
| A ~= B | Doesn't equal | 1 ~= 1 (false), 1 ~= 2 (true) |
|
||||
| A > B | Greater than | 5 > 2 (true), 1 > 2 (false), 1 > 1 (false) |
|
||||
| A < B | Less than | 1 < 3 (true), 3 < 1 (false), 1 < 1 (false) |
|
||||
| A >= B | Greater than or equals | 5 >= 5 (true), 5 >= 3 (true), 5 >= 6 (false) |
|
||||
| A <= B | Less than or equals | 3 <= 6 (true), 3 <= 3 (true) |
|
||||
| A and B | And (both must be correct) | (2 > 1) and (1 == 1) (true), (2 > 3) and (1 == 1) (false) |
|
||||
| A or B | either or. One or both must be true. | (2 > 1) or (1 == 2) (true), (2 > 4) or (1 == 3) (false) |
|
||||
| not A | not true | not (1 == 2) (true), not (1 == 1) (false) |
|
||||
|
||||
That doesn't contain every possible operator, and you can combine operators like this:
|
||||
|
||||
```lua
|
||||
if not A and B then
|
||||
print("Yay!")
|
||||
end
|
||||
```
|
||||
|
||||
Which prints "Yay!" if A is false and B is true.
|
||||
|
||||
Logical and arithmetic operators work exactly the same;
|
||||
they both accept inputs and return a value which can be stored.
|
||||
|
||||
```lua
|
||||
local A = 5
|
||||
local is_equal = (A == 5)
|
||||
if is_equal then
|
||||
print("Is equal!")
|
||||
end
|
||||
```
|
||||
|
||||
## Programming
|
||||
|
||||
Programming is the action of taking a problem, such as sorting a list
|
||||
of items, and then turning it into steps that a computer can understand.
|
||||
|
||||
Teaching you the logical process of programming is beyond the scope of this book;
|
||||
however, the following websites are quite useful in developing this:
|
||||
|
||||
* [Codecademy](http://www.codecademy.com/) is one of the best resources for
|
||||
learning to 'code', it provides an interactive tutorial experience.
|
||||
* [Scratch](https://scratch.mit.edu) is a good resource when starting from
|
||||
absolute basics, learning the problem-solving techniques required to program.\\
|
||||
Scratch is **designed to teach children** how to program and isn't a serious
|
||||
programming language.
|
||||
|
||||
## Local and Global Scope
|
||||
|
||||
Whether a variable is local or global determines where it can be written to or
|
||||
read from. Global variables can be accessed from anywhere in the script file,
|
||||
and from any other mod:
|
||||
|
||||
```lua
|
||||
function one()
|
||||
foo = "bar"
|
||||
end
|
||||
|
||||
function two()
|
||||
print(dump(foo)) -- Output: "bar"
|
||||
end
|
||||
|
||||
one()
|
||||
two()
|
||||
```
|
||||
|
||||
In constrast, a local variable is only accessible from where it is defined.
|
||||
Lua defaults to variables being global, so you need to explicitly use the
|
||||
`local` keyword:
|
||||
Whether a variable is local or global determines where it can be written to or read to.
|
||||
A local variable is only accessible from where it is defined. Here are some examples:
|
||||
|
||||
```lua
|
||||
-- Accessible from within this script file
|
||||
@ -118,12 +207,44 @@ function myfunc()
|
||||
end
|
||||
```
|
||||
|
||||
Whereas global variables can be accessed from anywhere in the script file, and from any other mod.
|
||||
|
||||
```lua
|
||||
my_global_variable = "blah"
|
||||
|
||||
function one()
|
||||
my_global_variable = "three"
|
||||
end
|
||||
|
||||
print(my_global_variable) -- Output: "blah"
|
||||
one()
|
||||
print(my_global_variable) -- Output: "three"
|
||||
```
|
||||
|
||||
|
||||
### Locals should be used as much as possible
|
||||
|
||||
Local variables should be used whenever possible. Mods should only create one
|
||||
global at most, with the same name as the mod. Creating other globals is sloppy
|
||||
coding, and Minetest will warn about this:
|
||||
Lua is global by default (unlike most other programming languages).
|
||||
Local variables must be identified as such.
|
||||
|
||||
```lua
|
||||
function one()
|
||||
foo = "bar"
|
||||
end
|
||||
|
||||
function two()
|
||||
print(dump(foo)) -- Output: "bar"
|
||||
end
|
||||
|
||||
one()
|
||||
two()
|
||||
```
|
||||
|
||||
dump() is a function that can turn any variable into a string so the programmer can
|
||||
see what it is. The foo variable will be printed as "bar", including the quotes
|
||||
which show it is a string.
|
||||
|
||||
This is sloppy coding and Minetest will, in fact, warn about this:
|
||||
|
||||
Assignment to undeclared global 'foo' inside function at init.lua:2
|
||||
|
||||
@ -142,11 +263,12 @@ one()
|
||||
two()
|
||||
```
|
||||
|
||||
Remember that nil means **not initialised**. The variable hasn't been assigned a
|
||||
value yet, doesn't exist, or has been uninitialised (meaning set to nil).
|
||||
Remember that nil means **not initialised**.
|
||||
The variable hasn't been assigned a value yet,
|
||||
doesn't exist, or has been uninitialised (ie: set to nil).
|
||||
|
||||
Functions are variables of a special type, but should also be made local,
|
||||
because other mods could have functions with the same names.
|
||||
The same goes for functions. Functions are variables of a special type, and
|
||||
should be made local, as other mods could have functions of the same name.
|
||||
|
||||
```lua
|
||||
local function foo(bar)
|
||||
@ -154,9 +276,7 @@ local function foo(bar)
|
||||
end
|
||||
```
|
||||
|
||||
To allow mods to call your functions, you should create a table with the same
|
||||
name as the mod and add your function to it. This table is often called an API
|
||||
table or namespace.
|
||||
API tables should be used to allow other mods to call the functions, like so:
|
||||
|
||||
```lua
|
||||
mymod = {}
|
||||
@ -169,29 +289,25 @@ end
|
||||
mymod.foo("foobar")
|
||||
```
|
||||
|
||||
`function mymod.foo()` is equivalent to `mymod.foo = function()`, it's just a
|
||||
nicer way to write it.
|
||||
|
||||
## Including other Lua Scripts
|
||||
|
||||
The recommended way to include other Lua scripts in a mod is to use *dofile*.
|
||||
|
||||
```lua
|
||||
dofile(core.get_modpath("modname") .. "/script.lua")
|
||||
dofile(minetest.get_modpath("modname") .. "/script.lua")
|
||||
```
|
||||
|
||||
A script can return a value, which is useful for sharing private locals:
|
||||
|
||||
```lua
|
||||
-- script.lua
|
||||
local module = {}
|
||||
module.message = "Hello World!"
|
||||
return module
|
||||
return "Hello world!"
|
||||
|
||||
-- init.lua
|
||||
local ret = dofile(core.get_modpath("modname") .. "/script.lua")
|
||||
print(ret.message) -- Hello world!
|
||||
local ret = dofile(minetest.get_modpath("modname") .. "/script.lua")
|
||||
print(ret) -- Hello world!
|
||||
```
|
||||
|
||||
[Later chapters](../quality/clean_arch.html) will discuss how best to split up
|
||||
code for a mod.
|
||||
Later chapters will discuss how to split up the code of a mod in a lot of detail.
|
||||
However, the simplistic approach for now is to have different files for different
|
||||
types of things - nodes.lua, crafts.lua, craftitems.lua, etc.
|
||||
|
@ -57,7 +57,7 @@ convenient, as it'll make porting mods to another game much easier.
|
||||
|
||||
The best way to keep compatibility with another game is to keep API compatibility
|
||||
with any mods which have the same name.
|
||||
That is, if a mod uses the same name as another mod, even if third-party,
|
||||
That is, if a mod uses the same name as another mod, even if third party,
|
||||
it should have a compatible API.
|
||||
For example, if a game includes a mod called `doors`, then it should have the
|
||||
same API as `doors` in Minetest Game.
|
||||
|
@ -1,7 +1,6 @@
|
||||
---
|
||||
title: Front Cover
|
||||
layout: default
|
||||
description: An easy guide to learn how to create mods for Minetest
|
||||
homepage: true
|
||||
no_header: true
|
||||
root: ..
|
||||
@ -9,7 +8,7 @@ idx: 0.1
|
||||
---
|
||||
|
||||
<header>
|
||||
<h1>Luanti Modding Book (formerly Minetest)</h1>
|
||||
<h1>Minetest Modding Book</h1>
|
||||
|
||||
<span>by <a href="https://rubenwardy.com" rel="author">rubenwardy</a></span>
|
||||
<span>with editing by <a href="http://rc.minetest.tv/">Shara</a></span>
|
||||
|
@ -1,206 +0,0 @@
|
||||
---
|
||||
title: Node and Item Callbacks
|
||||
layout: default
|
||||
root: ../..
|
||||
idx: 2.15
|
||||
description: Learn about callbacks, actions, and events, including on_use, on_punch, on_place, on_rightclick
|
||||
---
|
||||
|
||||
## Introduction <!-- omit in toc -->
|
||||
|
||||
Minetest heavily uses a callback-based modding design. A callback is a function
|
||||
that you give to an API and is called when an event happens. For example, you
|
||||
can provide an `on_punch` function in a node definition to be called when a player
|
||||
punches a node. There are also global callbacks like
|
||||
`core.register_on_punchnode` to receive events for all nodes.
|
||||
|
||||
- [Item Callbacks](#item-callbacks)
|
||||
- [on_use](#on_use)
|
||||
- [on_place and on_secondary_use](#on_place-and-on_secondary_use)
|
||||
- [on_drop](#on_drop)
|
||||
- [after_use](#after_use)
|
||||
- [item_place vs place_item](#item_place-vs-place_item)
|
||||
- [Node Callbacks](#node-callbacks)
|
||||
- [Right-clicking and placing a node](#right-clicking-and-placing-a-node)
|
||||
- [Punching and digging](#punching-and-digging)
|
||||
- [...and more!](#and-more)
|
||||
|
||||
|
||||
## Item Callbacks
|
||||
|
||||
When a player has a node, craftitem, or tool in their inventory, they may trigger
|
||||
certain events:
|
||||
|
||||
| Callback | Default binding | Default value |
|
||||
|------------------|---------------------------|----------------------------------------------|
|
||||
| on_use | left-click | nil |
|
||||
| on_place | right-click on a node | `core.item_place` |
|
||||
| on_secondary_use | right-click not on a node | `core.item_secondary_use` (does nothing) |
|
||||
| on_drop | Q | `core.item_drop` |
|
||||
| after_use | digging a node | nil |
|
||||
|
||||
|
||||
### on_use
|
||||
|
||||
Having a use callback prevents the item from being used to dig nodes. One common
|
||||
use of the use callback is for food:
|
||||
|
||||
```lua
|
||||
core.register_craftitem("mymod:mudpie", {
|
||||
description = "Alien Mud Pie",
|
||||
inventory_image = "myfood_mudpie.png",
|
||||
on_use = core.item_eat(20),
|
||||
})
|
||||
```
|
||||
|
||||
The number supplied to the core.item_eat function is the number of hit
|
||||
points healed when this food is consumed. Each heart icon the player has is
|
||||
worth two hitpoints. A player can usually have up to 10 hearts, which is equal
|
||||
to 20 hitpoints.
|
||||
|
||||
core.item_eat() is a function that returns a function, setting it as the
|
||||
on_use callback. This means the code above is equivalent to this:
|
||||
|
||||
```lua
|
||||
core.register_craftitem("mymod:mudpie", {
|
||||
description = "Alien Mud Pie",
|
||||
inventory_image = "myfood_mudpie.png",
|
||||
on_use = function(...)
|
||||
return core.do_item_eat(20, nil, ...)
|
||||
end,
|
||||
})
|
||||
```
|
||||
|
||||
By understanding how item_eat works by simply returning a function, it's
|
||||
possible to modify it to do more complex behaviour like playing a custom sound.
|
||||
|
||||
|
||||
### on_place and on_secondary_use
|
||||
|
||||
The difference between `on_place` and `on_secondary_use` is that `on_place` is
|
||||
called when the player is pointing at a node and `on_secondary_use` when the
|
||||
player isn't.
|
||||
|
||||
Both callbacks are called for all types of items. `on_place` defaults to the
|
||||
`core.item_place` function, which handles calling the `on_rightclick`
|
||||
callback of the pointed node or placing the wielded item if it is a node.
|
||||
|
||||
|
||||
### on_drop
|
||||
|
||||
on_drop is called when the player requests to drop an item, for example using
|
||||
the drop key (Q) or dragging it outside of the inventory. It defaults to the
|
||||
`core.item_drop` function, which will handle dropping the item.
|
||||
|
||||
|
||||
### after_use
|
||||
|
||||
`after_use` is called when digging a node and allows you to customise how wear
|
||||
is applied to a tool. If after_use doesn't exist, then it is the same as:
|
||||
|
||||
```lua
|
||||
after_use = function(itemstack, user, node, digparams)
|
||||
itemstack:add_wear(digparams.wear)
|
||||
return itemstack
|
||||
end
|
||||
```
|
||||
|
||||
|
||||
## item_place vs place_item
|
||||
|
||||
Minetest's API includes many different built-in callback implementations for you
|
||||
to use. These callbacks are named with the item type first, for example,
|
||||
`core.item_place` and `core.node_dig`. Some callback implementations are
|
||||
used directly whereas some are functions that return the callback:
|
||||
|
||||
```lua
|
||||
core.register_item("mymod:example", {
|
||||
on_place = core.item_place,
|
||||
on_use = core.item_eat(10),
|
||||
})
|
||||
```
|
||||
|
||||
Minetest's API also includes built-in functions that _do_ something. These are
|
||||
often named in a confusingly similar way to built-in callback implementations
|
||||
but have the verb first. Examples include `core.place_item` and
|
||||
`core.dig_node` - these functions allow you to dig and place nodes with a
|
||||
similar effect to players.
|
||||
|
||||
|
||||
## Node Callbacks
|
||||
|
||||
When a node is in an inventory, it uses Item Callbacks, as discussed above. When
|
||||
a node is placed in the world, it uses Node Callbacks. There are quite a lot of
|
||||
node callbacks, too many to discuss in this book. However, quite a few of them
|
||||
will be talked about later in the book.
|
||||
|
||||
Several of the callbacks are related to node operations such as placing and
|
||||
removing from the world. It's important to note that node operation callbacks
|
||||
like these aren't called from bulk changes - those that set a large number of
|
||||
nodes at once - for performance reasons. Therefore, you can't rely on these
|
||||
callbacks to always be called.
|
||||
|
||||
|
||||
### Right-clicking and placing a node
|
||||
|
||||
When the user right-clicks with an item whilst pointing at a node, the item's
|
||||
`on_place` callback is called. By default, this is set to `core.item_place`.
|
||||
If the pointed node has an `on_rightclick` callback and sneak (shift) is held,
|
||||
then the `on_rightclick` callback is called. Otherwise, `core.item_place`
|
||||
will place the node.
|
||||
|
||||
Placing a node will call both `on_construct` and `after_place_node`.
|
||||
`on_construct` is called by any node set event that wasn't in bulk and is just
|
||||
given the node's position and value .`after_place_node` is only called by node
|
||||
place, and so has more information - such as the placer and itemstack.
|
||||
|
||||
It's important to note that players aren't the only objects that can place
|
||||
nodes; it's common for mobs and mods to place nodes. To account for this,
|
||||
`placer` could be a player, entity, or nil.
|
||||
|
||||
```lua
|
||||
core.register_node("mymod:mynode", {
|
||||
on_rightclick = function(pos, node, clicker, itemstack, pointed_thing)
|
||||
if clicker:is_player() then
|
||||
core.chat_send_player(clicker:get_player_name(), "Hello world!")
|
||||
end
|
||||
end,
|
||||
on_construct = function(pos, node)
|
||||
local meta = core.get_meta(pos)
|
||||
meta:set_string("infotext", "My node!")
|
||||
end,
|
||||
after_place_node = function(pos, placer, itemstack, pointed_thing)
|
||||
-- Make sure to check placer
|
||||
if placer and placer:is_player() then
|
||||
local meta = core.get_meta(pos)
|
||||
meta:set_string("owner", placer:get_player_name())
|
||||
end
|
||||
end,
|
||||
})
|
||||
```
|
||||
|
||||
### Punching and digging
|
||||
|
||||
Punching is when the player left-clicks for a short period. If the wielded item
|
||||
has an `on_use` callback, this will be called. Otherwise, the `on_punch`
|
||||
callback on the pointed node will be called.
|
||||
|
||||
When the player attempts to dig a node, the `on_dig` callback on the node will be called.
|
||||
This defaults to `core.node_dig`, which will check for area protection, wear
|
||||
out the tool, remove the node, and run the `after_dig_node` callback.
|
||||
|
||||
|
||||
```lua
|
||||
core.register_node("mymod:mynode", {
|
||||
on_punch = function(pos, node, puncher, pointed_thing)
|
||||
if puncher:is_player() then
|
||||
core.chat_send_player(puncher:get_player_name(), "Ow!")
|
||||
end
|
||||
end,
|
||||
})
|
||||
```
|
||||
|
||||
### ...and more!
|
||||
|
||||
Check out Minetest's Lua API reference for a list of all node callbacks, and
|
||||
more information on the callbacks above.
|
@ -25,10 +25,8 @@ available, which cover pixel art in much more detail.
|
||||
- [Using the Pencil](#using-the-pencil)
|
||||
- [Tiling](#tiling)
|
||||
- [Transparency](#transparency)
|
||||
- [Color Palettes](#color-palettes)
|
||||
- [Editors](#editors)
|
||||
- [MS Paint](#ms-paint)
|
||||
- [Aseprite / LibreSprite](#aseprite--libresprite)
|
||||
- [GIMP](#gimp)
|
||||
|
||||
## Techniques
|
||||
@ -61,13 +59,6 @@ and some nodes, such as glass.
|
||||
Not all editors support transparency, so make sure you choose an
|
||||
editor which is suitable for the textures you wish to create.
|
||||
|
||||
### Color Palettes
|
||||
|
||||
Using a consistent color palette is an easy way to make your art look a lot
|
||||
better. It's a good idea to use one with a limited number of colors, perhaps 32
|
||||
at most. Premade palettes can be found at
|
||||
[lospec.com](https://lospec.com/palette-list).
|
||||
|
||||
## Editors
|
||||
|
||||
### MS Paint
|
||||
@ -78,21 +69,16 @@ This usually won't matter when making textures for the sides of nodes,
|
||||
but if you need transparency in your textures you should choose a
|
||||
different editor.
|
||||
|
||||
### Aseprite / LibreSprite
|
||||
|
||||
[Aseprite](https://www.aseprite.org/) is a proprietary pixel art editor.
|
||||
It contains a lot of useful features by default such as color palettes and
|
||||
animation tools.
|
||||
|
||||
[LibreSprite](https://libresprite.github.io/) is an open-source fork of Aseprite
|
||||
from before it went proprietary.
|
||||
|
||||
### GIMP
|
||||
|
||||
GIMP is commonly used in the Minetest community. It has quite a high
|
||||
learning curve because many of its features are not immediately
|
||||
obvious.
|
||||
|
||||
When using GIMP, make sure to use the Pencil tool with the Pixel brush and a
|
||||
size of 1. It's also advisable to select the "Hard edge" checkbox for the Eraser
|
||||
tool.
|
||||
When using GIMP, the pencil tool can be selected from the Toolbox:
|
||||
|
||||
<figure>
|
||||
<img src="{{ page.root }}//static/pixel_art_gimp_pencil.png" alt="Pencil in GIMP">
|
||||
</figure>
|
||||
|
||||
It's also advisable to select the Hard edge checkbox for the eraser tool.
|
||||
|
@ -19,9 +19,6 @@ that be a player inventory, a node inventory, or a detached inventory.
|
||||
- [What are ItemStacks and Inventories?](#what-are-itemstacks-and-inventories)
|
||||
- [ItemStacks](#itemstacks)
|
||||
- [Inventory Locations](#inventory-locations)
|
||||
- [Node Inventories](#node-inventories)
|
||||
- [Player Inventories](#player-inventories)
|
||||
- [Detached Inventories](#detached-inventories)
|
||||
- [Lists](#lists)
|
||||
- [Size and Width](#size-and-width)
|
||||
- [Checking Contents](#checking-contents)
|
||||
@ -36,21 +33,21 @@ that be a player inventory, a node inventory, or a detached inventory.
|
||||
|
||||
An ItemStack is the data behind a single cell in an inventory.
|
||||
|
||||
An *inventory* is a collection of *inventory lists*, each of which is a 2D grid
|
||||
of ItemStacks. Inventory lists are referred to as *lists* in the context of
|
||||
inventories.
|
||||
|
||||
Players and nodes only have a single inventory; lists enable you to have
|
||||
multiple grids within that inventory. By default, the player has the "main" list
|
||||
for the bulk of its inventory and a few lists for the crafting system.
|
||||
An *inventory* is a collection of *inventory lists*, each of which
|
||||
is a 2D grid of ItemStacks.
|
||||
Inventory lists are simply called *lists* in the context
|
||||
of inventories.
|
||||
The point of an inventory is to allow multiple grids when Players
|
||||
and Nodes only have at most one inventory in them.
|
||||
|
||||
## ItemStacks
|
||||
|
||||
ItemStacks have four components to them: `name`, `count`, `wear`, and metadata.
|
||||
ItemStacks have four components to them: name, count, wear and metadata.
|
||||
|
||||
The item name may be the item name of a registered item, an alias, or an unknown
|
||||
item name. Unknown items are common when users uninstall mods, or when mods
|
||||
remove items without precautions, such as registering aliases.
|
||||
item name.
|
||||
Unknown items are common when users uninstall mods, or when mods remove items without
|
||||
precautions, such as registering aliases.
|
||||
|
||||
```lua
|
||||
print(stack:get_name())
|
||||
@ -61,14 +58,19 @@ if not stack:is_known() then
|
||||
end
|
||||
```
|
||||
|
||||
The count will always be 0 or greater. Through normal gameplay, the count should
|
||||
be no more than the maximum stack size of the item - `stack_max`. However, admin
|
||||
commands and buggy mods may result in stacks exceeding the maximum size.
|
||||
The count will always be 0 or greater.
|
||||
Through normal gameplay, the count should be no more than the maximum stack size
|
||||
of the item - `stack_max`.
|
||||
However, admin commands and buggy mods may result in stacks exceeding the maximum
|
||||
size.
|
||||
|
||||
```lua
|
||||
print(stack:get_stack_max())
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
An ItemStack can be empty, in which case the count will be 0.
|
||||
|
||||
```lua
|
||||
@ -76,7 +78,7 @@ print(stack:get_count())
|
||||
stack:set_count(10)
|
||||
```
|
||||
|
||||
ItemStacks can be constructed in multiple ways using the ItemStack function:
|
||||
ItemStacks can be constructed in multiple ways using the ItemStack function.
|
||||
|
||||
```lua
|
||||
ItemStack() -- name="", count=0
|
||||
@ -85,30 +87,24 @@ ItemStack("default:stone 30")
|
||||
ItemStack({ name = "default:wood", count = 10 })
|
||||
```
|
||||
|
||||
Item metadata is an unlimited key-value store for data about the item. Key-value
|
||||
means that you use a name (called the key) to access the data (called the
|
||||
value). Some keys have special meaning, such as `description` which is used to
|
||||
have a per-stack item description. This will be covered in more detail in the
|
||||
[Storage and Metadata](../map/storage.html) chapter.
|
||||
Item metadata is an unlimited key-value store for data about the item.
|
||||
Key-value means that you use a name (called the key) to access the data (called the value).
|
||||
Some keys have special meaning, such as `description` which is used to have a per-stack
|
||||
item description.
|
||||
This will be covered in more detail in the Metadata and Storage chapter.
|
||||
|
||||
## Inventory Locations
|
||||
|
||||
An Inventory Location is where and how the inventory is stored. There are three
|
||||
types of inventory location: player, node, and detached. An inventory is
|
||||
directly tied to one and only one location - updating the inventory will cause
|
||||
it to update immediately.
|
||||
An Inventory Location is where and how the inventory is stored.
|
||||
There are three types of inventory location: player, node, and detached.
|
||||
An inventory is directly tied to one and only one location - updating the inventory
|
||||
will cause it to update immediately.
|
||||
|
||||
### Node Inventories
|
||||
|
||||
Node inventories are related to the position of a specific node, such as a
|
||||
chest. The node must be loaded because it is stored in
|
||||
[node metadata](../map/storage.html#metadata).
|
||||
Node inventories are related to the position of a specific node, such as a chest.
|
||||
The node must be loaded because it is stored in [node metadata](../map/storage.html#metadata).
|
||||
|
||||
```lua
|
||||
on_punch = function(pos, node)
|
||||
local inv = core.get_inventory({ type="node", pos=pos })
|
||||
-- now use the inventory
|
||||
end,
|
||||
local inv = minetest.get_inventory({ type="node", pos={x=1, y=2, z=3} })
|
||||
```
|
||||
|
||||
The above obtains an *inventory reference*, commonly referred to as *InvRef*.
|
||||
@ -122,24 +118,20 @@ The location of an inventory reference can be found like so:
|
||||
local location = inv:get_location()
|
||||
```
|
||||
|
||||
### Player Inventories
|
||||
|
||||
Player inventories can be obtained similarly or using a player reference.
|
||||
The player must be online to access their inventory.
|
||||
|
||||
```lua
|
||||
local inv = core.get_inventory({ type="player", name="player1" })
|
||||
local inv = minetest.get_inventory({ type="player", name="player1" })
|
||||
-- or
|
||||
local inv = player:get_inventory()
|
||||
```
|
||||
|
||||
### Detached Inventories
|
||||
|
||||
A detached inventory is one that is independent of players or nodes. Detached
|
||||
inventories also don't save over a restart.
|
||||
A detached inventory is one which is independent of players or nodes.
|
||||
Detached inventories also don't save over a restart.
|
||||
|
||||
```lua
|
||||
local inv = core.get_inventory({
|
||||
local inv = minetest.get_inventory({
|
||||
type="detached", name="inventory_name" })
|
||||
```
|
||||
|
||||
@ -147,16 +139,17 @@ Unlike the other types of inventory, you must first create a detached inventory
|
||||
before accessing it:
|
||||
|
||||
```lua
|
||||
core.create_detached_inventory("inventory_name")
|
||||
minetest.create_detached_inventory("inventory_name")
|
||||
```
|
||||
|
||||
The `create_detached_inventory` function accepts 3 arguments, where only the
|
||||
first - the inventory name - is required. The second argument takes a table of
|
||||
callbacks, which can be used to control how players interact with the inventory:
|
||||
The create_detached_inventory function accepts 3 arguments, where only the first - the inventory name -
|
||||
is required.
|
||||
The second argument takes a table of callbacks, which can be used to control how
|
||||
players interact with the inventory:
|
||||
|
||||
```lua
|
||||
-- Input only detached inventory
|
||||
core.create_detached_inventory("inventory_name", {
|
||||
minetest.create_detached_inventory("inventory_name", {
|
||||
allow_move = function(inv, from_list, from_index, to_list, to_index, count, player)
|
||||
return count -- allow moving
|
||||
end,
|
||||
@ -166,28 +159,27 @@ core.create_detached_inventory("inventory_name", {
|
||||
end,
|
||||
|
||||
allow_take = function(inv, listname, index, stack, player)
|
||||
return 0 -- don't allow taking
|
||||
return -1 -- don't allow taking
|
||||
end,
|
||||
|
||||
on_put = function(inv, listname, index, stack, player)
|
||||
core.chat_send_all(player:get_player_name() ..
|
||||
minetest.chat_send_all(player:get_player_name() ..
|
||||
" gave " .. stack:to_string() ..
|
||||
" to the donation chest from " .. core.pos_to_string(player:get_pos()))
|
||||
" to the donation chest from " .. minetest.pos_to_string(player:get_pos()))
|
||||
end,
|
||||
})
|
||||
```
|
||||
|
||||
Permission callbacks - ie: those starting with `allow_` - return the number
|
||||
of items to transfer, with 0 being used to prevent transfer completely.
|
||||
of items to transfer, with -1 being used to prevent transfer completely.
|
||||
|
||||
On the contrary, action callbacks - starting with `on_` - don't have a return value.
|
||||
|
||||
## Lists
|
||||
|
||||
Inventory Lists are a concept used to allow multiple grids to be stored inside a
|
||||
single location. This is especially useful for the player as there are several
|
||||
common lists that all games have, such as the *main* inventory and *craft*
|
||||
slots.
|
||||
Inventory Lists are a concept used to allow multiple grids to be stored inside a single location.
|
||||
This is especially useful for the player as there are a number of common lists
|
||||
which all games have, such as the *main* inventory and *craft* slots.
|
||||
|
||||
### Size and Width
|
||||
|
||||
|
@ -19,7 +19,7 @@ In the previous chapter, the concept of nodes and items was introduced, but a
|
||||
full definition of a node wasn't given. The Minetest world is a 3D grid of
|
||||
positions. Each position is called a node, and consists of the node type
|
||||
(name) and two parameters (param1 and param2). The function
|
||||
`core.register_node` is a bit misleading in that it doesn't actually
|
||||
`minetest.register_node` is a bit misleading in that it doesn't actually
|
||||
register a node - it registers a new *type* of node.
|
||||
|
||||
The node params are used to control how a node is individually rendered.
|
||||
@ -28,7 +28,7 @@ The node params are used to control how a node is individually rendered.
|
||||
|
||||
- [Cubic Nodes: Normal and Allfaces](#cubic-nodes-normal-and-allfaces)
|
||||
- [Glasslike Nodes](#glasslike-nodes)
|
||||
- [Glasslike_Framed](#glasslike_framed)
|
||||
- [Glasslike_Framed](#glasslikeframed)
|
||||
- [Airlike Nodes](#airlike-nodes)
|
||||
- [Lighting and Sunlight Propagation](#lighting-and-sunlight-propagation)
|
||||
- [Liquid Nodes](#liquid-nodes)
|
||||
@ -60,13 +60,13 @@ leaf nodes. You can use the allfaces_optional drawtype to allow users to opt-out
|
||||
of the slower drawing, in which case it'll act like a normal node.
|
||||
|
||||
```lua
|
||||
core.register_node("mymod:diamond", {
|
||||
minetest.register_node("mymod:diamond", {
|
||||
description = "Alien Diamond",
|
||||
tiles = {"mymod_diamond.png"},
|
||||
groups = {cracky = 3},
|
||||
})
|
||||
|
||||
core.register_node("default:leaves", {
|
||||
minetest.register_node("default:leaves", {
|
||||
description = "Leaves",
|
||||
drawtype = "allfaces_optional",
|
||||
tiles = {"default_leaves.png"}
|
||||
@ -92,7 +92,7 @@ drawtype would result in the ability to see through the world.
|
||||
</figure>
|
||||
|
||||
```lua
|
||||
core.register_node("default:obsidian_glass", {
|
||||
minetest.register_node("default:obsidian_glass", {
|
||||
description = "Obsidian Glass",
|
||||
drawtype = "glasslike",
|
||||
tiles = {"default_obsidian_glass.png"},
|
||||
@ -120,11 +120,11 @@ You can use the glasslike_framed_optional drawtype to allow the user to *opt-in*
|
||||
to the framed appearance.
|
||||
|
||||
```lua
|
||||
core.register_node("default:glass", {
|
||||
minetest.register_node("default:glass", {
|
||||
description = "Glass",
|
||||
drawtype = "glasslike_framed",
|
||||
tiles = {"default_glass.png", "default_glass_detail.png"},
|
||||
inventory_image = core.inventorycube("default_glass.png"),
|
||||
inventory_image = minetest.inventorycube("default_glass.png"),
|
||||
paramtype = "light",
|
||||
sunlight_propagates = true, -- Sunlight can shine through block
|
||||
groups = {cracky = 3, oddly_breakable_by_hand = 3},
|
||||
@ -138,7 +138,7 @@ core.register_node("default:glass", {
|
||||
These nodes are not rendered and thus have no textures.
|
||||
|
||||
```lua
|
||||
core.register_node("myair:air", {
|
||||
minetest.register_node("myair:air", {
|
||||
description = "MyAir (you hacker you!)",
|
||||
drawtype = "airlike",
|
||||
paramtype = "light",
|
||||
@ -147,7 +147,7 @@ core.register_node("myair:air", {
|
||||
walkable = false, -- Would make the player collide with the air node
|
||||
pointable = false, -- You can't select the node
|
||||
diggable = false, -- You can't dig the node
|
||||
buildable_to = true, -- Nodes can replace this node.
|
||||
buildable_to = true, -- Nodes can be replace this node.
|
||||
-- (you can place a node and remove the air node
|
||||
-- that used to be there)
|
||||
|
||||
@ -192,11 +192,11 @@ another for flowing liquid.
|
||||
```lua
|
||||
-- Some properties have been removed as they are beyond
|
||||
-- the scope of this chapter.
|
||||
core.register_node("default:water_source", {
|
||||
minetest.register_node("default:water_source", {
|
||||
drawtype = "liquid",
|
||||
paramtype = "light",
|
||||
|
||||
inventory_image = core.inventorycube("default_water.png"),
|
||||
inventory_image = minetest.inventorycube("default_water.png"),
|
||||
-- ^ this is required to stop the inventory image from being animated
|
||||
|
||||
tiles = {
|
||||
@ -271,7 +271,7 @@ Node boxes allow you to create a node which is not cubic, but is instead made ou
|
||||
of as many cuboids as you like.
|
||||
|
||||
```lua
|
||||
core.register_node("stairs:stair_stone", {
|
||||
minetest.register_node("stairs:stair_stone", {
|
||||
drawtype = "nodebox",
|
||||
paramtype = "light",
|
||||
node_box = {
|
||||
@ -305,7 +305,7 @@ create node boxes by dragging the edges, it is more visual than doing it by hand
|
||||
Sometimes you want different nodeboxes for when it is placed on the floor, wall, or ceiling like with torches.
|
||||
|
||||
```lua
|
||||
core.register_node("default:sign_wall", {
|
||||
minetest.register_node("default:sign_wall", {
|
||||
drawtype = "nodebox",
|
||||
node_box = {
|
||||
type = "wallmounted",
|
||||
@ -341,7 +341,7 @@ invisible but still rendered.
|
||||
You can register a mesh node as so:
|
||||
|
||||
```lua
|
||||
core.register_node("mymod:meshy", {
|
||||
minetest.register_node("mymod:meshy", {
|
||||
drawtype = "mesh",
|
||||
|
||||
-- Holds the texture for each "material"
|
||||
@ -370,7 +370,7 @@ instead use the `nodebox` drawtype to provide a 3D effect. The `signlike` drawty
|
||||
is, however, commonly used by ladders.
|
||||
|
||||
```lua
|
||||
core.register_node("default:ladder_wood", {
|
||||
minetest.register_node("default:ladder_wood", {
|
||||
drawtype = "signlike",
|
||||
|
||||
tiles = {"default_ladder_wood.png"},
|
||||
@ -397,7 +397,7 @@ core.register_node("default:ladder_wood", {
|
||||
Plantlike nodes draw their tiles in an X like pattern.
|
||||
|
||||
```lua
|
||||
core.register_node("default:papyrus", {
|
||||
minetest.register_node("default:papyrus", {
|
||||
drawtype = "plantlike",
|
||||
|
||||
-- Only one texture used
|
||||
@ -423,7 +423,7 @@ and ceilings.
|
||||
</figure>
|
||||
|
||||
```lua
|
||||
core.register_node("mymod:clingere", {
|
||||
minetest.register_node("mymod:clingere", {
|
||||
drawtype = "firelike",
|
||||
|
||||
-- Only one texture used
|
||||
@ -442,5 +442,5 @@ This is not a comprehensive list, there are more types including:
|
||||
The torches in Minetest Game actually use two different node definitions of
|
||||
mesh nodes (default:torch and default:torch_wall).
|
||||
|
||||
As always, read the [Lua API documentation](https://minetest.gitlab.io/minetest/nodes/#node-drawtypes)
|
||||
As always, read the [Lua API documentation](../../lua_api.html#node-drawtypes)
|
||||
for the complete list.
|
||||
|
@ -14,10 +14,11 @@ basic requirements for many mods.
|
||||
|
||||
- [What are Nodes and Items?](#what-are-nodes-and-items)
|
||||
- [Registering Items](#registering-items)
|
||||
- [Item Names](#item-names)
|
||||
- [Item Aliases](#item-aliases)
|
||||
- [Item Names and Aliases](#item-names-and-aliases)
|
||||
- [Textures](#textures)
|
||||
- [Registering a basic node](#registering-a-basic-node)
|
||||
- [Actions and Callbacks](#actions-and-callbacks)
|
||||
- [on_use](#onuse)
|
||||
- [Crafting](#crafting)
|
||||
- [Shaped](#shaped)
|
||||
- [Shapeless](#shapeless)
|
||||
@ -27,63 +28,62 @@ basic requirements for many mods.
|
||||
|
||||
## What are Nodes and Items?
|
||||
|
||||
Nodes, craftitems, and tools are all Items. An item is something that could be
|
||||
found in an inventory - even if it isn't possible through normal gameplay.
|
||||
Nodes, craftitems, and tools are all Items.
|
||||
An item is something that could be found in an inventory -
|
||||
even though it may not be possible through normal gameplay.
|
||||
|
||||
A node is an item that can be placed or be found in the world. Every position
|
||||
in the world must be occupied with one and only one node - seemingly blank
|
||||
positions are usually air nodes.
|
||||
A node is an item which can be placed or be found in the world.
|
||||
Every position in the world must be occupied with one and only one node -
|
||||
seemingly blank positions are usually air nodes.
|
||||
|
||||
A craftitem can't be placed and is only found in inventories or as a dropped item
|
||||
in the world.
|
||||
|
||||
A tool is like a craftitem but has the ability to wear. As you use the tool, the
|
||||
wear bar goes down until the tool breaks. Tools can also never be stacked. In
|
||||
the future, it's likely that craftitems and tools will merge into one type of
|
||||
A tool has the ability to wear and typically has non-default digging capabilities.
|
||||
In the future, it's likely that craftitems and tools will merge into one type of
|
||||
item, as the distinction between them is rather artificial.
|
||||
|
||||
## Registering Items
|
||||
|
||||
Item definitions consist of an *item name* and a *definition table*.
|
||||
The definition table contains attributes that affect the behaviour of the item.
|
||||
The definition table contains attributes which affect the behaviour of the item.
|
||||
|
||||
```lua
|
||||
core.register_craftitem("modname:itemname", {
|
||||
minetest.register_craftitem("modname:itemname", {
|
||||
description = "My Special Item",
|
||||
inventory_image = "modname_itemname.png"
|
||||
})
|
||||
```
|
||||
|
||||
### Item Names
|
||||
### Item Names and Aliases
|
||||
|
||||
Every item has an item name used to refer to it, which should be in the
|
||||
following format:
|
||||
|
||||
modname:itemname
|
||||
|
||||
The modname is the name of the mod in which the item is registered, and the item
|
||||
name is the name of the item itself. The item name should be relevant to what
|
||||
the item is and can't already be registered.
|
||||
The modname is the name of the mod in which the item is registered, and the
|
||||
item name is the name of the item itself.
|
||||
The item name should be relevant to what the item is and can't already be registered.
|
||||
|
||||
Both `modname` and `itemname` should only contain lowercase letters, numbers,
|
||||
and underscores.
|
||||
|
||||
### Item Aliases
|
||||
|
||||
Items can also have *aliases* pointing to their name. An *alias* is a
|
||||
pseudo-item name that results in the engine treating any occurrences of the
|
||||
alias as if it were the item name. There are two main common uses of this:
|
||||
Items can also have *aliases* pointing to their name.
|
||||
An *alias* is a pseudo-item name which results in the engine treating any
|
||||
occurrences of the alias as if it were the item name.
|
||||
There are two main common uses of this:
|
||||
|
||||
* Renaming removed items to something else.
|
||||
There may be unknown nodes in the world and in inventories if an item is
|
||||
removed from a mod without any corrective code.
|
||||
It's important to avoid aliasing to an unobtainable node if the remove node
|
||||
could be obtained.
|
||||
* Adding a shortcut. `/giveme dirt` is easier than `/giveme default:dirt`.
|
||||
|
||||
Registering an alias is pretty simple. A good way to remember the order of the
|
||||
arguments is `from → to` where *from* is the alias and *to* is the target.
|
||||
Registering an alias is pretty simple.
|
||||
A good way to remember the order of the arguments is `from → to` where
|
||||
*from* is the alias and *to* is the target.
|
||||
|
||||
```lua
|
||||
core.register_alias("dirt", "default:dirt")
|
||||
minetest.register_alias("dirt", "default:dirt")
|
||||
```
|
||||
|
||||
Mods need to make sure to resolve aliases before dealing directly with item names,
|
||||
@ -91,7 +91,7 @@ as the engine won't do this.
|
||||
This is pretty simple though:
|
||||
|
||||
```lua
|
||||
itemname = core.registered_aliases[itemname] or itemname
|
||||
itemname = minetest.registered_aliases[itemname] or itemname
|
||||
```
|
||||
|
||||
### Textures
|
||||
@ -102,18 +102,16 @@ JPEG textures are supported, but they do not support transparency and are genera
|
||||
bad quality at low resolutions.
|
||||
It is often better to use the PNG format.
|
||||
|
||||
Textures in Minetest are usually 16 by 16 pixels. They can be any resolution,
|
||||
but it is recommended that they are in the order of 2, for example, 16, 32, 64,
|
||||
or 128. This is because other resolutions may not be supported correctly on
|
||||
older devices, especially phones, resulting in degraded performance.
|
||||
Textures in Minetest are usually 16 by 16 pixels.
|
||||
They can be any resolution, but it is recommended that they are in the order of 2,
|
||||
for example, 16, 32, 64, or 128.
|
||||
This is because other resolutions may not be supported correctly on older devices,
|
||||
resulting in decreased performance.
|
||||
|
||||
## Registering a basic node
|
||||
|
||||
Registering nodes is similar to registering items, just with a different
|
||||
function:
|
||||
|
||||
```lua
|
||||
core.register_node("mymod:diamond", {
|
||||
minetest.register_node("mymod:diamond", {
|
||||
description = "Alien Diamond",
|
||||
tiles = {"mymod_diamond.png"},
|
||||
is_ground_content = true,
|
||||
@ -121,9 +119,6 @@ core.register_node("mymod:diamond", {
|
||||
})
|
||||
```
|
||||
|
||||
Node definitions can contain any property in an item definition, and also
|
||||
contain additional properties specific to nodes.
|
||||
|
||||
The `tiles` property is a table of texture names the node will use.
|
||||
When there is only one texture, this texture is used on every side.
|
||||
To give a different texture per-side, supply the names of 6 textures in this order:
|
||||
@ -132,10 +127,10 @@ To give a different texture per-side, supply the names of 6 textures in this ord
|
||||
(+Y, -Y, +X, -X, +Z, -Z)
|
||||
|
||||
Remember that +Y is upwards in Minetest, as is the convention with
|
||||
most 3D computer games.
|
||||
3D computer graphics.
|
||||
|
||||
```lua
|
||||
core.register_node("mymod:diamond", {
|
||||
minetest.register_node("mymod:diamond", {
|
||||
description = "Alien Diamond",
|
||||
tiles = {
|
||||
"mymod_diamond_up.png", -- y+
|
||||
@ -152,10 +147,53 @@ core.register_node("mymod:diamond", {
|
||||
})
|
||||
```
|
||||
|
||||
The `is_ground_content` attribute allows caves to be generated over the stone.
|
||||
The is_ground_content attribute allows caves to be generated over the stone.
|
||||
This is essential for any node which may be placed during map generation underground.
|
||||
Caves are cut out of the world after all the other nodes in an area have generated.
|
||||
|
||||
## Actions and Callbacks
|
||||
|
||||
Minetest heavily uses a callback-based modding design.
|
||||
Callbacks can be placed in the item definition table to allow response to various
|
||||
different user events.
|
||||
|
||||
### on_use
|
||||
|
||||
By default, the use callback is triggered when a player left-clicks with an item.
|
||||
Having a use callback prevents the item being used to dig nodes.
|
||||
One common use of the use callback is for food:
|
||||
|
||||
```lua
|
||||
minetest.register_craftitem("mymod:mudpie", {
|
||||
description = "Alien Mud Pie",
|
||||
inventory_image = "myfood_mudpie.png",
|
||||
on_use = minetest.item_eat(20),
|
||||
})
|
||||
```
|
||||
|
||||
The number supplied to the minetest.item_eat function is the number of hit points
|
||||
healed when this food is consumed.
|
||||
Each heart icon the player has is worth two hitpoints.
|
||||
A player can usually have up to 10 hearts, which is equal to 20 hitpoints.
|
||||
Hitpoints don't have to be integers (whole numbers); they can be decimals.
|
||||
|
||||
minetest.item_eat() is a function which returns a function, setting it
|
||||
as the on_use callback.
|
||||
This means the code above is roughly similar to this:
|
||||
|
||||
```lua
|
||||
minetest.register_craftitem("mymod:mudpie", {
|
||||
description = "Alien Mud Pie",
|
||||
inventory_image = "myfood_mudpie.png",
|
||||
on_use = function(...)
|
||||
return minetest.do_item_eat(20, nil, ...)
|
||||
end,
|
||||
})
|
||||
```
|
||||
|
||||
By understanding how item_eat works by simply returning a function, it's
|
||||
possible to modify it to do more complex behaviour such as play a custom sound.
|
||||
|
||||
## Crafting
|
||||
|
||||
There are several types of crafting recipe available, indicated by the `type`
|
||||
@ -178,7 +216,7 @@ pattern to work. In the example below, the fragments need to be in a
|
||||
chair-like pattern for the craft to work.
|
||||
|
||||
```lua
|
||||
core.register_craft({
|
||||
minetest.register_craft({
|
||||
type = "shaped",
|
||||
output = "mymod:diamond_chair 99",
|
||||
recipe = {
|
||||
@ -196,7 +234,7 @@ If this empty column shouldn't be required, then the empty strings can be left
|
||||
out like so:
|
||||
|
||||
```lua
|
||||
core.register_craft({
|
||||
minetest.register_craft({
|
||||
output = "mymod:diamond_chair 99",
|
||||
recipe = {
|
||||
{"mymod:diamond_fragments", "" },
|
||||
@ -215,7 +253,7 @@ Shapeless recipes are a type of recipe which is used when it doesn't matter
|
||||
where the ingredients are placed, just that they're there.
|
||||
|
||||
```lua
|
||||
core.register_craft({
|
||||
minetest.register_craft({
|
||||
type = "shapeless",
|
||||
output = "mymod:diamond 3",
|
||||
recipe = {
|
||||
@ -232,7 +270,7 @@ Recipes with the type "cooking" are not made in the crafting grid,
|
||||
but are cooked in furnaces, or other cooking tools that might be found in mods.
|
||||
|
||||
```lua
|
||||
core.register_craft({
|
||||
minetest.register_craft({
|
||||
type = "cooking",
|
||||
output = "mymod:diamond_fragments",
|
||||
recipe = "default:coalblock",
|
||||
@ -254,7 +292,7 @@ This type is an accompaniment to the cooking type, as it defines
|
||||
what can be burned in furnaces and other cooking tools from mods.
|
||||
|
||||
```lua
|
||||
core.register_craft({
|
||||
minetest.register_craft({
|
||||
type = "fuel",
|
||||
recipe = "mymod:diamond",
|
||||
burntime = 300,
|
||||
@ -281,14 +319,13 @@ Secondly, groups can be used in a craft recipe instead of an item name to allow
|
||||
any item in the group to be used.
|
||||
|
||||
```lua
|
||||
core.register_craft({
|
||||
minetest.register_craft({
|
||||
type = "shapeless",
|
||||
output = "mymod:diamond_thing 3",
|
||||
recipe = {"group:wood", "mymod:diamond"}
|
||||
})
|
||||
```
|
||||
|
||||
|
||||
## Tools, Capabilities, and Dig Types
|
||||
|
||||
Dig types are groups which are used to define how strong a node is when dug
|
||||
@ -322,7 +359,7 @@ If the item a player is currently wielding doesn't have an explicit tool
|
||||
capability, then the capability of the current hand is used instead.
|
||||
|
||||
```lua
|
||||
core.register_tool("mymod:tool", {
|
||||
minetest.register_tool("mymod:tool", {
|
||||
description = "My Tool",
|
||||
inventory_image = "mymod_tool.png",
|
||||
tool_capabilities = {
|
||||
|
@ -9,8 +9,7 @@ redirect_from: /en/chapters/environment.html
|
||||
|
||||
## Introduction <!-- omit in toc -->
|
||||
|
||||
In this chapter, you will learn how to perform basic actions on the map, such as
|
||||
adding, removing, and finding nodes.
|
||||
In this chapter, you will learn how to perform basic actions on the map.
|
||||
|
||||
- [Map Structure](#map-structure)
|
||||
- [Reading](#reading)
|
||||
@ -24,17 +23,15 @@ adding, removing, and finding nodes.
|
||||
|
||||
## Map Structure
|
||||
|
||||
The Minetest map is split into MapBlocks, each MapBlocks being a cube of
|
||||
size 16. As players travel around the map, MapBlocks are created, loaded,
|
||||
activated, and unloaded. Areas of the map which are not yet loaded are full of
|
||||
*ignore* nodes, an impassable unselectable placeholder node. Empty space is
|
||||
full of *air* nodes, an invisible node you can walk through.
|
||||
|
||||
An active MapBlock is one which is loaded and has updates running on it.
|
||||
The Minetest map is split into MapBlocks, each MapBlocks being a cube of size 16.
|
||||
As players travel around the map, MapBlocks are created, loaded, and unloaded.
|
||||
Areas of the map which are not yet loaded are full of *ignore* nodes, an impassable
|
||||
unselectable placeholder node. Empty space is full of *air* nodes, an invisible node
|
||||
you can walk through.
|
||||
|
||||
Loaded map blocks are often referred to as *active blocks*. Active Blocks can be
|
||||
read from or written to by mods or players, and have active entities. The Engine
|
||||
also performs operations on the map, such as performing liquid physics.
|
||||
read from or written to by mods or players, and have active entities. The Engine also
|
||||
performs operations on the map, such as performing liquid physics.
|
||||
|
||||
MapBlocks can either be loaded from the world database or generated. MapBlocks
|
||||
will be generated up to the map generation limit (`mapgen_limit`) which is set
|
||||
@ -48,7 +45,7 @@ loaded from the world database outside of the generation limit.
|
||||
You can read from the map once you have a position:
|
||||
|
||||
```lua
|
||||
local node = core.get_node({ x = 1, y = 3, z = 4 })
|
||||
local node = minetest.get_node({ x = 1, y = 3, z = 4 })
|
||||
print(dump(node)) --> { name=.., param1=.., param2=.. }
|
||||
```
|
||||
|
||||
@ -62,7 +59,7 @@ The function will always return a table containing the node information:
|
||||
It's worth noting that the function won't load the containing block if the block
|
||||
is inactive, but will instead return a table with `name` being `ignore`.
|
||||
|
||||
You can use `core.get_node_or_nil` instead, which will return `nil` rather
|
||||
You can use `minetest.get_node_or_nil` instead, which will return `nil` rather
|
||||
than a table with a name of `ignore`. It still won't load the block, however.
|
||||
This may still return `ignore` if a block actually contains ignore.
|
||||
This will happen near the edge of the map as defined by the map generation
|
||||
@ -77,53 +74,48 @@ For example, say we wanted to make a certain type of plant that grows
|
||||
better near mese; you would need to search for any nearby mese nodes,
|
||||
and adapt the growth rate accordingly.
|
||||
|
||||
`core.find_node_near` will return the first found node in a certain radius
|
||||
which matches the node names or groups given. In the following example,
|
||||
we look for a mese node within 5 nodes of the position:
|
||||
|
||||
```lua
|
||||
local grow_speed = 1
|
||||
local node_pos = core.find_node_near(pos, 5, { "default:mese" })
|
||||
local node_pos = minetest.find_node_near(pos, 5, { "default:mese" })
|
||||
if node_pos then
|
||||
core.chat_send_all("Node found at: " .. dump(node_pos))
|
||||
minetest.chat_send_all("Node found at: " .. dump(node_pos))
|
||||
grow_speed = 2
|
||||
end
|
||||
```
|
||||
|
||||
Let's say, for example, that the growth rate increases the more mese there is
|
||||
nearby. You should then use a function that can find multiple nodes in the area:
|
||||
nearby. You should then use a function which can find multiple nodes in area:
|
||||
|
||||
```lua
|
||||
local pos1 = vector.subtract(pos, { x = 5, y = 5, z = 5 })
|
||||
local pos2 = vector.add(pos, { x = 5, y = 5, z = 5 })
|
||||
local pos_list =
|
||||
core.find_nodes_in_area(pos1, pos2, { "default:mese" })
|
||||
minetest.find_nodes_in_area(pos1, pos2, { "default:mese" })
|
||||
local grow_speed = 1 + #pos_list
|
||||
```
|
||||
|
||||
The above code finds the number of nodes in a *cuboid volume*. This is different
|
||||
to `find_node_near`, which uses the distance to the position (ie: a *sphere*). In
|
||||
order to fix this, we will need to manually check the range ourselves:
|
||||
The above code doesn't quite do what we want, as it checks based on area, whereas
|
||||
`find_node_near` checks based on range. In order to fix this, we will,
|
||||
unfortunately, need to manually check the range ourselves.
|
||||
|
||||
```lua
|
||||
local pos1 = vector.subtract(pos, { x = 5, y = 5, z = 5 })
|
||||
local pos2 = vector.add(pos, { x = 5, y = 5, z = 5 })
|
||||
local pos_list =
|
||||
core.find_nodes_in_area(pos1, pos2, { "default:mese" })
|
||||
minetest.find_nodes_in_area(pos1, pos2, { "default:mese" })
|
||||
local grow_speed = 1
|
||||
for i=1, #pos_list do
|
||||
local delta = vector.subtract(pos_list[i], pos)
|
||||
if delta.x*delta.x + delta.y*delta.y + delta.z*delta.z <= 5*5 then
|
||||
if delta.x*delta.x + delta.y*delta.y <= 5*5 then
|
||||
grow_speed = grow_speed + 1
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
Now the code will correctly increase `grow_speed` based on mese nodes in range.
|
||||
|
||||
Now your code will correctly increase `grow_speed` based on mese nodes in range.
|
||||
Note how we compared the squared distance from the position, rather than square
|
||||
rooting it to obtain the actual distance. This is because computers find square
|
||||
roots computationally expensive, so they should avoided as much as possible.
|
||||
roots computationally expensive, so you should avoid them as much as possible.
|
||||
|
||||
There are more variations of the above two functions, such as
|
||||
`find_nodes_with_meta` and `find_nodes_in_area_under_air`, which work similarly
|
||||
@ -134,26 +126,26 @@ and are useful in other circumstances.
|
||||
### Writing Nodes
|
||||
|
||||
You can use `set_node` to write to the map. Each call to set_node will cause
|
||||
lighting to be recalculated and node callbacks to run, which means that set_node
|
||||
is fairly slow for large numbers of nodes.
|
||||
lighting to be recalculated, which means that set_node is fairly slow for large
|
||||
numbers of nodes.
|
||||
|
||||
```lua
|
||||
core.set_node({ x = 1, y = 3, z = 4 }, { name = "default:mese" })
|
||||
minetest.set_node({ x = 1, y = 3, z = 4 }, { name = "default:mese" })
|
||||
|
||||
local node = core.get_node({ x = 1, y = 3, z = 4 })
|
||||
local node = minetest.get_node({ x = 1, y = 3, z = 4 })
|
||||
print(node.name) --> default:mese
|
||||
```
|
||||
|
||||
set_node will remove any associated metadata or inventory from that position.
|
||||
This isn't desirable in all circumstances, especially if you're using multiple
|
||||
node definitions to represent one conceptual node. An example of this is the
|
||||
furnace node - whilst you conceptually think of it as one node, it's actually
|
||||
furnace node - whilst you think conceptually of it as one node, it's actually
|
||||
two.
|
||||
|
||||
You can set a node without deleting metadata or the inventory like so:
|
||||
|
||||
```lua
|
||||
core.swap_node({ x = 1, y = 3, z = 4 }, { name = "default:mese" })
|
||||
minetest.swap_node({ x = 1, y = 3, z = 4 }, { name = "default:mese" })
|
||||
```
|
||||
|
||||
### Removing Nodes
|
||||
@ -163,15 +155,15 @@ A node must always be present. To remove a node, you set the position to `air`.
|
||||
The following two lines will both remove a node, and are both identical:
|
||||
|
||||
```lua
|
||||
core.remove_node(pos)
|
||||
core.set_node(pos, { name = "air" })
|
||||
minetest.remove_node(pos)
|
||||
minetest.set_node(pos, { name = "air" })
|
||||
```
|
||||
|
||||
In fact, remove_node is just a helper function that calls set_node with `"air"`.
|
||||
In fact, remove_node will call set_node with the name being air.
|
||||
|
||||
## Loading Blocks
|
||||
|
||||
You can use `core.emerge_area` to load map blocks. Emerge area is asynchronous,
|
||||
You can use `minetest.emerge_area` to load map blocks. Emerge area is asynchronous,
|
||||
meaning the blocks won't be loaded instantly. Instead, they will be loaded
|
||||
soon in the future, and the callback will be called each time.
|
||||
|
||||
@ -182,7 +174,7 @@ local pos1 = vector.subtract(pos, halfsize)
|
||||
local pos2 = vector.add (pos, halfsize)
|
||||
|
||||
local context = {} -- persist data between callback calls
|
||||
core.emerge_area(pos1, pos2, emerge_callback, context)
|
||||
minetest.emerge_area(pos1, pos2, emerge_callback, context)
|
||||
```
|
||||
|
||||
Minetest will call `emerge_callback` whenever it loads a block, with some
|
||||
@ -202,18 +194,17 @@ local function emerge_callback(pos, action,
|
||||
|
||||
-- Send progress message
|
||||
if context.total_blocks == context.loaded_blocks then
|
||||
core.chat_send_all("Finished loading blocks!")
|
||||
else
|
||||
minetest.chat_send_all("Finished loading blocks!")
|
||||
end
|
||||
local perc = 100 * context.loaded_blocks / context.total_blocks
|
||||
local msg = string.format("Loading blocks %d/%d (%.2f%%)",
|
||||
context.loaded_blocks, context.total_blocks, perc)
|
||||
core.chat_send_all(msg)
|
||||
minetest.chat_send_all(msg)
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
This is not the only way of loading blocks; using an
|
||||
[Lua Voxel Manipulator (LVM)](../advmap/lvm.html) will also cause the
|
||||
This is not the only way of loading blocks; using an LVM will also cause the
|
||||
encompassed blocks to be loaded synchronously.
|
||||
|
||||
## Deleting Blocks
|
||||
@ -226,7 +217,7 @@ local halfsize = { x = 10, y = 10, z = 10 }
|
||||
local pos1 = vector.subtract(pos, halfsize)
|
||||
local pos2 = vector.add (pos, halfsize)
|
||||
|
||||
core.delete_area(pos1, pos2)
|
||||
minetest.delete_area(pos1, pos2)
|
||||
```
|
||||
|
||||
This will delete all map blocks in that area, *inclusive*. This means that some
|
||||
|
@ -20,16 +20,12 @@ own.
|
||||
- [Position and Velocity](#position-and-velocity)
|
||||
- [Object Properties](#object-properties)
|
||||
- [Entities](#entities)
|
||||
- [Health and Damage](#health-and-damage)
|
||||
- [Health Points (HP)](#health-points-hp)
|
||||
- [Punch, Damage Groups, and Armor Groups](#punch-damage-groups-and-armor-groups)
|
||||
- [Example Damage Calculation](#example-damage-calculation)
|
||||
- [Attachments](#attachments)
|
||||
- [Your Turn](#your-turn)
|
||||
|
||||
## What are Objects, Players, and Entities?
|
||||
|
||||
Players and Entities are both types of Objects. An object is something that can move
|
||||
Players and Entities are both types of Objects. An Object is something that can move
|
||||
independently of the node grid and has properties such as velocity and scale.
|
||||
Objects aren't items, and they have their own separate registration system.
|
||||
|
||||
@ -48,7 +44,7 @@ which is referred to as a Lua entity, as discussed later.
|
||||
`get_pos` and `set_pos` exist to allow you to get and set an entity's position.
|
||||
|
||||
```lua
|
||||
local object = core.get_player_by_name("bob")
|
||||
local object = minetest.get_player_by_name("bob")
|
||||
local pos = object:get_pos()
|
||||
object:set_pos({ x = pos.x, y = pos.y + 1, z = pos.z })
|
||||
```
|
||||
@ -107,6 +103,28 @@ An Entity has a definition table that resembles an item definition table.
|
||||
This table can contain callback methods, initial object properties, and custom
|
||||
members.
|
||||
|
||||
However, entities differ in one very important way from items. When an entity is
|
||||
emerged (ie: loaded or created), a new table is created for that entity that
|
||||
*inherits* from the definition table using metatables.
|
||||
This new table is commonly referred to as a Lua Entity table.
|
||||
|
||||
Metatables are an important Lua feature that you will need
|
||||
to be aware of, as it is an essential part of the Lua language.
|
||||
|
||||
In layman's terms, a metatable allows you to control how the table behaves when
|
||||
using certain Lua syntax. The most common use of metatables is the ability to use
|
||||
another table as a prototype, defaulting to the other table's properties and methods when
|
||||
they do not exist in the current table.
|
||||
|
||||
Say you want to access member X on table A. If table A has that member, then
|
||||
it will be returned as normal. However, if the table doesn't have that member but
|
||||
it does have a metatable could table B, then table B will be checked to see if it
|
||||
has that member.
|
||||
|
||||
<!--table A is a metatable of table B, then table
|
||||
B will have all the properties and methods of table A if the derived table doesn't
|
||||
have any itself.-->
|
||||
|
||||
```lua
|
||||
local MyEntity = {
|
||||
initial_properties = {
|
||||
@ -129,34 +147,20 @@ function MyEntity:set_message(msg)
|
||||
end
|
||||
```
|
||||
|
||||
Entity definitions differ in one very important way from Item definitions.
|
||||
When an entity is emerged (ie: loaded or created), a new table is created for
|
||||
that entity that *inherits* from the definition table.
|
||||
|
||||
<!--
|
||||
This inheritance is done using a metatables.
|
||||
Metatables are an important Lua feature that you will need to be aware of, as it
|
||||
is an essential part of the Lua language. In layman's terms, a metatable allows
|
||||
you to control how the table behaves when using certain Lua syntax. The most
|
||||
common use of metatables is the ability to use another table as a prototype,
|
||||
defaulting to the other table's properties and methods when they do not exist in
|
||||
the current table.
|
||||
Say you want to access `a.x`. If the table `a` has that member, then it will be
|
||||
returned as normal. However, if the table doesn't have that member and the
|
||||
metatable lists a table `b` as a prototype, then table `b` will be checked to
|
||||
see if it has that member.
|
||||
-->
|
||||
When an entity has emerged, a table is created for it by copying everything from
|
||||
its type table.
|
||||
This table can be used to store variables for that particular entity.
|
||||
|
||||
Both an ObjectRef and an entity table provide ways to get the counterpart:
|
||||
|
||||
```lua
|
||||
local entity = object:get_luaentity()
|
||||
local object = entity.object
|
||||
print("entity is at " .. core.pos_to_string(object:get_pos()))
|
||||
print("entity is at " .. minetest.pos_to_string(object:get_pos()))
|
||||
```
|
||||
|
||||
There are a number of available callbacks for use with entities.
|
||||
A complete list can be found in [lua_api.md](https://minetest.gitlab.io/minetest/minetest-namespace-reference/#registered-definition-tables).
|
||||
A complete list can be found in [lua_api.txt]({{ page.root }}/lua_api.html#registered-entities).
|
||||
|
||||
```lua
|
||||
function MyEntity:on_step(dtime)
|
||||
@ -164,9 +168,9 @@ function MyEntity:on_step(dtime)
|
||||
local pos_down = vector.subtract(pos, vector.new(0, 1, 0))
|
||||
|
||||
local delta
|
||||
if core.get_node(pos_down).name == "air" then
|
||||
if minetest.get_node(pos_down).name == "air" then
|
||||
delta = vector.new(0, -1, 0)
|
||||
elseif core.get_node(pos).name == "air" then
|
||||
elseif minetest.get_node(pos).name == "air" then
|
||||
delta = vector.new(0, 0, 1)
|
||||
else
|
||||
delta = vector.new(0, 1, 0)
|
||||
@ -178,7 +182,7 @@ function MyEntity:on_step(dtime)
|
||||
end
|
||||
|
||||
function MyEntity:on_punch(hitter)
|
||||
core.chat_send_player(hitter:get_player_name(), self.message)
|
||||
minetest.chat_send_player(hitter:get_player_name(), self.message)
|
||||
end
|
||||
```
|
||||
|
||||
@ -192,14 +196,14 @@ needs to stored.
|
||||
|
||||
```lua
|
||||
function MyEntity:get_staticdata()
|
||||
return core.write_json({
|
||||
return minetest.write_json({
|
||||
message = self.message,
|
||||
})
|
||||
end
|
||||
|
||||
function MyEntity:on_activate(staticdata, dtime_s)
|
||||
if staticdata ~= "" and staticdata ~= nil then
|
||||
local data = core.parse_json(staticdata) or {}
|
||||
local data = minetest.parse_json(staticdata) or {}
|
||||
self:set_message(data.message)
|
||||
end
|
||||
end
|
||||
@ -217,14 +221,14 @@ This means that staticdata could be empty.
|
||||
Finally, you need to register the type table using the aptly named `register_entity`.
|
||||
|
||||
```lua
|
||||
core.register_entity("mymod:entity", MyEntity)
|
||||
minetest.register_entity("mymod:entity", MyEntity)
|
||||
```
|
||||
|
||||
The entity can be spawned by a mod like so:
|
||||
|
||||
```lua
|
||||
local pos = { x = 1, y = 2, z = 3 }
|
||||
local obj = core.add_entity(pos, "mymod:entity", nil)
|
||||
local obj = minetest.add_entity(pos, "mymod:entity", nil)
|
||||
```
|
||||
|
||||
The third parameter is the initial staticdata.
|
||||
@ -239,90 +243,6 @@ use a [chat command](../players/chat.html) to spawn entities:
|
||||
|
||||
/spawnentity mymod:entity
|
||||
|
||||
|
||||
## Health and Damage
|
||||
|
||||
### Health Points (HP)
|
||||
|
||||
Each object has a Health Points (HP) number, which represents the current health.
|
||||
Players have a maximum hp set using the `hp_max` object property.
|
||||
An object will die if its hp reaches 0.
|
||||
|
||||
```lua
|
||||
local hp = object:get_hp()
|
||||
object:set_hp(hp + 3)
|
||||
```
|
||||
|
||||
### Punch, Damage Groups, and Armor Groups
|
||||
|
||||
Damage is the reduction of an object's HP. An object can *punch* another object to
|
||||
inflict damage. A punch isn't necessarily an actual punch - it can be an
|
||||
explosion, a sword slash, or something else.
|
||||
|
||||
The total damage is calculated by multiplying the punch's damage groups with the
|
||||
target's vulnerabilities. This is then limited depending on how recent the last
|
||||
punch was. We will go over an example of this calculation in a bit.
|
||||
|
||||
Just like [node dig groups](../items/nodes_items_crafting.html#tools-capabilities-and-dig-types),
|
||||
these groups can take any name and do not need to be registered. However, it's
|
||||
common to use the same group names as with node digging.
|
||||
|
||||
How vulnerable an object is to particular types of damage depends on its
|
||||
`armor_groups`. Despite its misleading name, `armor_groups` specify the
|
||||
percentage damage taken from particular damage groups, not the resistance. If a
|
||||
damage group is not listed in an object's armor groups, that object is
|
||||
completely invulnerable to it.
|
||||
|
||||
```lua
|
||||
target:set_armor_groups({
|
||||
fleshy = 90,
|
||||
crumbly = 50,
|
||||
})
|
||||
```
|
||||
|
||||
In the above example, the object will take 90% of `fleshy` damage and 50% of
|
||||
`crumbly` damage.
|
||||
|
||||
When a player punches an object, the damage groups come from the item they are
|
||||
currently wielding. In other cases, mods decide which damage groups are used.
|
||||
|
||||
### Example Damage Calculation
|
||||
|
||||
Let's punch the `target` object:
|
||||
|
||||
```lua
|
||||
local tool_capabilities = {
|
||||
full_punch_interval = 0.8,
|
||||
damage_groups = { fleshy = 5, choppy = 10 },
|
||||
|
||||
-- This is only used for digging nodes, but is still required
|
||||
max_drop_level=1,
|
||||
groupcaps={
|
||||
fleshy={times={[1]=2.5, [2]=1.20, [3]=0.35}, uses=30, maxlevel=2},
|
||||
},
|
||||
}
|
||||
|
||||
local time_since_last_punch = tool_capabilities.full_punch_interval
|
||||
target:punch(object, time_since_last_punch, tool_capabilities)
|
||||
```
|
||||
|
||||
Now, let's work out what the damage will be. The punch's damage groups are
|
||||
`fleshy=5` and `choppy=10`, and `target` will take 90% damage from fleshy and 0%
|
||||
from choppy.
|
||||
|
||||
First, we multiply the damage groups by the vulnerability and sum the result.
|
||||
We then multiply by a number between 0 or 1 depending on the `time_since_last_punch`.
|
||||
|
||||
```lua
|
||||
= (5*90/100 + 10*0/100) * limit(time_since_last_punch / full_punch_interval, 0, 1)
|
||||
= (5*90/100 + 10*0/100) * 1
|
||||
= 4.5
|
||||
```
|
||||
|
||||
As HP is an integer, the damage is rounded to 5 points.
|
||||
|
||||
|
||||
|
||||
## Attachments
|
||||
|
||||
Attached objects will move when the parent - the object they are attached to -
|
||||
@ -333,7 +253,7 @@ An object can have an unlimited number of children, but at most one parent.
|
||||
child:set_attach(parent, bone, position, rotation)
|
||||
```
|
||||
|
||||
An object's `get_pos()` will always return the global position of the object, no
|
||||
An Object's `get_pos()` will always return the global position of the object, no
|
||||
matter whether it is attached or not.
|
||||
`set_attach` takes a relative position, but not as you'd expect.
|
||||
The attachment position is relative to the parent's origin as scaled up by 10 times.
|
||||
|
@ -53,7 +53,7 @@ The data itself, such as a node's type or an stack's count, is not metadata.
|
||||
If you know the position of a node, you can retrieve its metadata:
|
||||
|
||||
```lua
|
||||
local meta = core.get_meta({ x = 1, y = 2, z = 3 })
|
||||
local meta = minetest.get_meta({ x = 1, y = 2, z = 3 })
|
||||
```
|
||||
|
||||
Player and ItemStack metadata are obtained using `get_meta()`:
|
||||
@ -99,7 +99,7 @@ This is useful when showing the ownership or status of a node.
|
||||
|
||||
`description` is used in ItemStack Metadata to override the description when
|
||||
hovering over the stack in an inventory.
|
||||
You can use colours by encoding them with `core.colorize()`.
|
||||
You can use colours by encoding them with `minetest.colorize()`.
|
||||
|
||||
`owner` is a common key used to store the username of the player that owns the
|
||||
item or node.
|
||||
@ -111,20 +111,20 @@ Minetest offers two formats for doing this: Lua and JSON.
|
||||
|
||||
The Lua method tends to be a lot faster and matches the format Lua
|
||||
uses for tables, while JSON is a more standard format, is better
|
||||
structured, and is well suited for when you need to exchange information
|
||||
structured, and is well suited when you need to exchange information
|
||||
with another program.
|
||||
|
||||
```lua
|
||||
local data = { username = "player1", score = 1234 }
|
||||
meta:set_string("foo", core.serialize(data))
|
||||
meta:set_string("foo", minetest.serialize(data))
|
||||
|
||||
data = core.deserialize(meta:get_string("foo"))
|
||||
data = minetest.deserialize(minetest:get_string("foo"))
|
||||
```
|
||||
|
||||
### Private Metadata
|
||||
|
||||
By default, all node metadata is sent to the client.
|
||||
You can mark keys as private to prevent this.
|
||||
Entries in Node Metadata can be marked as private, and not sent to the client.
|
||||
Entries not marked as private will be sent to the client.
|
||||
|
||||
```lua
|
||||
meta:set_string("secret", "asd34dn")
|
||||
@ -149,7 +149,7 @@ Mod storage is per-mod, and can only be obtained during load time in order to
|
||||
know which mod is requesting it.
|
||||
|
||||
```lua
|
||||
local storage = core.get_mod_storage()
|
||||
local storage = minetest.get_mod_storage()
|
||||
```
|
||||
|
||||
You can now manipulate the storage just like metadata:
|
||||
@ -169,10 +169,10 @@ it is used.
|
||||
local backend
|
||||
if use_database then
|
||||
backend =
|
||||
dofile(core.get_modpath("mymod") .. "/backend_sqlite.lua")
|
||||
dofile(minetest.get_modpath("mymod") .. "/backend_sqlite.lua")
|
||||
else
|
||||
backend =
|
||||
dofile(core.get_modpath("mymod") .. "/backend_storage.lua")
|
||||
dofile(minetest.get_modpath("mymod") .. "/backend_storage.lua")
|
||||
end
|
||||
|
||||
backend.get_foo("a")
|
||||
@ -182,15 +182,15 @@ backend.set_foo("a", { score = 3 })
|
||||
The backend_storage.lua file should include a mod storage implementation:
|
||||
|
||||
```lua
|
||||
local storage = core.get_mod_storage()
|
||||
local storage = minetest.get_mod_storage()
|
||||
local backend = {}
|
||||
|
||||
function backend.set_foo(key, value)
|
||||
storage:set_string(key, core.serialize(value))
|
||||
storage:set_string(key, minetest.serialize(value))
|
||||
end
|
||||
|
||||
function backend.get_foo(key)
|
||||
return core.deserialize(storage:get_string(key))
|
||||
function backend.get_foo(key, value)
|
||||
return minetest.deserialize(storage:get_string(key))
|
||||
end
|
||||
|
||||
return backend
|
||||
@ -207,7 +207,7 @@ Insecure environments will be covered in more detail in the
|
||||
[Security](../quality/security.html) chapter.
|
||||
|
||||
```lua
|
||||
local ie = core.request_insecure_environment()
|
||||
local ie = minetest.request_insecure_environment()
|
||||
assert(ie, "Please add mymod to secure.trusted_mods in the settings")
|
||||
|
||||
local _sql = ie.require("lsqlite3")
|
||||
@ -244,4 +244,4 @@ They're well suited for large data sets.
|
||||
## Your Turn
|
||||
|
||||
* Make a node which disappears after it has been punched five times.
|
||||
(Use `on_punch` in the node definition and `core.set_node`.)
|
||||
(Use `on_punch` in the node definition and `minetest.set_node`.)
|
||||
|
@ -37,25 +37,35 @@ Node timers are directly tied to a single node.
|
||||
You can manage node timers by obtaining a NodeTimerRef object.
|
||||
|
||||
```lua
|
||||
local timer = core.get_node_timer(pos)
|
||||
local timer = minetest.get_node_timer(pos)
|
||||
timer:start(10.5) -- in seconds
|
||||
```
|
||||
|
||||
When a node timer is up, the `on_timer` method in the node's definition table will
|
||||
be called. The method only takes a single parameter, the position of the node:
|
||||
You can also check the status or stop the timer:
|
||||
|
||||
```lua
|
||||
core.register_node("autodoors:door_open", {
|
||||
if timer:is_started() then
|
||||
print("The timer is running, and has " .. timer:get_timeout() .. "s remaining!")
|
||||
print(timer:get_elapsed() .. "s has elapsed.")
|
||||
end
|
||||
|
||||
timer:stop()
|
||||
```
|
||||
|
||||
When a node timer is up, the `on_timer` method in the node's definition table will
|
||||
be called.
|
||||
The method only takes a single parameter, the position of the node.
|
||||
|
||||
```lua
|
||||
minetest.register_node("autodoors:door_open", {
|
||||
on_timer = function(pos)
|
||||
core.set_node(pos, { name = "autodoors:door" })
|
||||
minetest.set_node(pos, { name = "autodoors:door" })
|
||||
return false
|
||||
end
|
||||
})
|
||||
```
|
||||
|
||||
Returning true in `on_timer` will cause the timer to run again for the same interval.
|
||||
It's also possible to use `get_node_timer(pos)` inside of `on_timer`, just make
|
||||
sure you return false to avoid conflict.
|
||||
|
||||
You may have noticed a limitation with timers: for optimisation reasons, it's
|
||||
only possible to have one type of timer per node type, and only one timer running per node.
|
||||
@ -68,23 +78,23 @@ has a chance to appear near water.
|
||||
|
||||
|
||||
```lua
|
||||
core.register_node("aliens:grass", {
|
||||
minetest.register_node("aliens:grass", {
|
||||
description = "Alien Grass",
|
||||
light_source = 3, -- The node radiates light. Min 0, max 14
|
||||
tiles = {"aliens_grass.png"},
|
||||
groups = {choppy=1},
|
||||
on_use = core.item_eat(20)
|
||||
on_use = minetest.item_eat(20)
|
||||
})
|
||||
|
||||
core.register_abm({
|
||||
minetest.register_abm({
|
||||
nodenames = {"default:dirt_with_grass"},
|
||||
neighbors = {"default:water_source", "default:water_flowing"},
|
||||
interval = 10.0, -- Run every 10 seconds
|
||||
chance = 50, -- One node has a chance of 1 in 50 to get selected
|
||||
chance = 50, -- Select every 1 in 50 nodes
|
||||
action = function(pos, node, active_object_count,
|
||||
active_object_count_wider)
|
||||
local pos = {x = pos.x, y = pos.y + 1, z = pos.z}
|
||||
core.set_node(pos, {name = "aliens:grass"})
|
||||
minetest.set_node(pos, {name = "aliens:grass"})
|
||||
end
|
||||
})
|
||||
```
|
||||
@ -93,7 +103,7 @@ This ABM runs every ten seconds, and for each matching node, there is
|
||||
a 1 in 50 chance of it running.
|
||||
If the ABM runs on a node, an alien grass node is placed above it.
|
||||
Please be warned, this will delete any node previously located in that position.
|
||||
To prevent this you should include a check using core.get_node to make sure there is space for the grass.
|
||||
To prevent this you should include a check using minetest.get_node to make sure there is space for the grass.
|
||||
|
||||
Specifying a neighbour is optional.
|
||||
If you specify multiple neighbours, only one of them needs to be
|
||||
|
@ -8,19 +8,17 @@ redirect_from: /en/chapters/chat.html
|
||||
cmd_online:
|
||||
level: warning
|
||||
title: Offline players can run commands
|
||||
message: |
|
||||
A player name is passed instead of a player object because mods
|
||||
message: <p>A player name is passed instead of a player object because mods
|
||||
can run commands on behalf of offline players. For example, the IRC
|
||||
bridge allows players to run commands without joining the game.
|
||||
bridge allows players to run commands without joining the game.</p>
|
||||
|
||||
So make sure that you don't assume that the player is online.
|
||||
You can check by seeing if `core.get_player_by_name` returns a player.
|
||||
<p>So make sure that you don't assume that the player is online.
|
||||
You can check by seeing if <pre>minetest.get_player_by_name</pre> returns a player.</p>
|
||||
|
||||
cb_cmdsprivs:
|
||||
level: warning
|
||||
title: Privileges and Chat Commands
|
||||
message: |
|
||||
The shout privilege isn't needed for a player to trigger this callback.
|
||||
message: The shout privilege isn't needed for a player to trigger this callback.
|
||||
This is because chat commands are implemented in Lua, and are just
|
||||
chat messages that begin with a /.
|
||||
|
||||
@ -31,23 +29,18 @@ cb_cmdsprivs:
|
||||
Mods can interact with player chat, including
|
||||
sending messages, intercepting messages, and registering chat commands.
|
||||
|
||||
- [Sending Messages](#sending-messages)
|
||||
- [To All Players](#to-all-players)
|
||||
- [To Specific Players](#to-specific-players)
|
||||
- [Sending Messages to All Players](#sending-messages-to-all-players)
|
||||
- [Sending Messages to Specific Players](#sending-messages-to-specific-players)
|
||||
- [Chat Commands](#chat-commands)
|
||||
- [Accepting Multiple Arguments](#accepting-multiple-arguments)
|
||||
- [Using string.split](#using-stringsplit)
|
||||
- [Using Lua patterns](#using-lua-patterns)
|
||||
- [Complex Subcommands](#complex-subcommands)
|
||||
- [Intercepting Messages](#intercepting-messages)
|
||||
|
||||
## Sending Messages
|
||||
## Sending Messages to All Players
|
||||
|
||||
### To All Players
|
||||
|
||||
To send a message to every player in the game, call the `chat_send_all` function.
|
||||
To send a message to every player in the game, call the chat_send_all function.
|
||||
|
||||
```lua
|
||||
core.chat_send_all("This is a chat message to all players")
|
||||
minetest.chat_send_all("This is a chat message to all players")
|
||||
```
|
||||
|
||||
Here is an example of how this appears in-game:
|
||||
@ -58,12 +51,12 @@ Here is an example of how this appears in-game:
|
||||
|
||||
The message appears on a separate line to distinguish it from in-game player chat.
|
||||
|
||||
### To Specific Players
|
||||
## Sending Messages to Specific Players
|
||||
|
||||
To send a message to a specific player, call the `chat_send_player` function:
|
||||
To send a message to a specific player, call the chat_send_player function:
|
||||
|
||||
```lua
|
||||
core.chat_send_player("player1", "This is a chat message for player1")
|
||||
minetest.chat_send_player("player1", "This is a chat message for player1")
|
||||
```
|
||||
|
||||
This message displays in the same manner as messages to all players, but is
|
||||
@ -74,7 +67,7 @@ only visible to the named player, in this case, player1.
|
||||
To register a chat command, for example `/foo`, use `register_chatcommand`:
|
||||
|
||||
```lua
|
||||
core.register_chatcommand("foo", {
|
||||
minetest.register_chatcommand("foo", {
|
||||
privs = {
|
||||
interact = true,
|
||||
},
|
||||
@ -87,57 +80,26 @@ core.register_chatcommand("foo", {
|
||||
In the above snippet, `interact` is listed as a required
|
||||
[privilege](privileges.html) meaning that only players with the `interact` privilege can run the command.
|
||||
|
||||
`param` is a string containing everything a player writes after the chatcommand
|
||||
name. For example, if a user types `/grantme one,two,three` then `param` will be
|
||||
`one,two,three`.
|
||||
|
||||
Chat commands can return up to two values,
|
||||
the first being a Boolean indicating success, and the second being a
|
||||
message to send to the user.
|
||||
|
||||
{% include notice.html notice=page.cmd_online %}
|
||||
|
||||
### Accepting Multiple Arguments
|
||||
## Complex Subcommands
|
||||
|
||||
<a name="complex-subcommands"></a>
|
||||
It is often required to make complex chat commands, such as:
|
||||
|
||||
`param` gives you all the arguments to a chat command in a single string. It's
|
||||
common for chat commands to need to extract multiple arguments. There are two
|
||||
ways of doing this, either using Minetest's string split or Lua patterns.
|
||||
* `/msg <to> <message>`
|
||||
* `/team join <teamname>`
|
||||
* `/team leave <teamname>`
|
||||
* `/team list`
|
||||
|
||||
#### Using string.split
|
||||
|
||||
A string can be split up into words using `string.split(" ")`:
|
||||
This is usually done using [Lua patterns](https://www.lua.org/pil/20.2.html).
|
||||
Patterns are a way of extracting stuff from text using rules.
|
||||
|
||||
```lua
|
||||
local parts = param:split(" ")
|
||||
local cmd = parts[1]
|
||||
|
||||
if cmd == "join" then
|
||||
local team_name = parts[2]
|
||||
team.join(name, team_name)
|
||||
return true, "Joined team!"
|
||||
elseif cmd == "max_users" then
|
||||
local team_name = parts[2]
|
||||
local max_users = tonumber(parts[3])
|
||||
if team_name and max_users then
|
||||
return true, "Set max users of team " .. team_name .. " to " .. max_users
|
||||
else
|
||||
return false, "Usage: /team max_users <team_name> <number>"
|
||||
end
|
||||
else
|
||||
return false, "Command needed"
|
||||
end
|
||||
```
|
||||
|
||||
#### Using Lua patterns
|
||||
|
||||
[Lua patterns](https://www.lua.org/pil/20.2.html) are a way of extracting stuff
|
||||
from text using rules. They're best suited for when there are arguments that can
|
||||
contain spaces or more control is needed on how parameters are captured.
|
||||
|
||||
```lua
|
||||
local to, msg = param:match("^([%a%d_-]+) (.+)$")
|
||||
local to, msg = string.match(param, "^([%a%d_-]+) (*+)$")
|
||||
```
|
||||
|
||||
The above code implements `/msg <to> <message>`. Let's go through left to right:
|
||||
@ -147,9 +109,9 @@ The above code implements `/msg <to> <message>`. Let's go through left to right:
|
||||
returned from string.match.
|
||||
* `[]` means accept characters in this list.
|
||||
* `%a` means accept any letter and `%d` means accept any digit.
|
||||
* `[%a%d_-]` means accept any letter or digit or `_` or `-`.
|
||||
* `[%d%a_-]` means accept any letter or digit or `_` or `-`.
|
||||
* `+` means match the thing before one or more times.
|
||||
* `.` means match any character in this context.
|
||||
* `*` means match any character in this context.
|
||||
* `$` means match the end of the string.
|
||||
|
||||
Put simply, the pattern matches the name (a word with only letters/numbers/-/_),
|
||||
@ -161,12 +123,19 @@ Patterns would probably be the
|
||||
[lua-users.org tutorial](http://lua-users.org/wiki/PatternsTutorial)
|
||||
or the [PIL documentation](https://www.lua.org/pil/20.2.html).
|
||||
|
||||
<p class="book_hide">
|
||||
There is also a library written by the author of this book which can be used
|
||||
to make complex chat commands without patterns called
|
||||
<a href="chat_complex.html">Chat Command Builder</a>.
|
||||
</p>
|
||||
|
||||
|
||||
## Intercepting Messages
|
||||
|
||||
To intercept a message, use register_on_chat_message:
|
||||
|
||||
```lua
|
||||
core.register_on_chat_message(function(name, message)
|
||||
minetest.register_on_chat_message(function(name, message)
|
||||
print(name .. " said " .. message)
|
||||
return false
|
||||
end)
|
||||
@ -182,10 +151,10 @@ You should make sure you take into account that it may be a chat command,
|
||||
or the user may not have `shout`.
|
||||
|
||||
```lua
|
||||
core.register_on_chat_message(function(name, message)
|
||||
minetest.register_on_chat_message(function(name, message)
|
||||
if message:sub(1, 1) == "/" then
|
||||
print(name .. " ran chat command")
|
||||
elseif core.check_player_privs(name, { shout = true }) then
|
||||
elseif minetest.check_player_privs(name, { shout = true }) then
|
||||
print(name .. " said " .. message)
|
||||
else
|
||||
print(name .. " tried to say " .. message ..
|
||||
|
183
_en/players/chat_complex.md
Normal file
183
_en/players/chat_complex.md
Normal file
@ -0,0 +1,183 @@
|
||||
---
|
||||
title: Chat Command Builder
|
||||
layout: default
|
||||
root: ../..
|
||||
idx: 4.3
|
||||
description: Use ChatCmdBuilder to make a complex chat command
|
||||
redirect_from: /en/chapters/chat_complex.html
|
||||
---
|
||||
|
||||
## Introduction <!-- omit in toc -->
|
||||
|
||||
This chapter will show you how to make complex chat commands with ChatCmdBuilder,
|
||||
such as `/msg <name> <message>`, `/team join <teamname>` or `/team leave <teamname>`.
|
||||
|
||||
Note that ChatCmdBuilder is a library created by the author of this book, and most
|
||||
modders tend to use the method outlined in the
|
||||
[Chat and Commands](chat.html#complex-subcommands) chapter.
|
||||
|
||||
- [Why ChatCmdBuilder?](#why-chatcmdbuilder)
|
||||
- [Routes](#routes)
|
||||
- [Subcommand functions](#subcommand-functions)
|
||||
- [Installing ChatCmdBuilder](#installing-chatcmdbuilder)
|
||||
- [Admin complex command](#admin-complex-command)
|
||||
|
||||
## Why ChatCmdBuilder?
|
||||
|
||||
Traditionally mods implemented these complex commands using Lua patterns.
|
||||
|
||||
```lua
|
||||
local name = string.match(param, "^join ([%a%d_-]+)")
|
||||
```
|
||||
|
||||
I, however, find Lua patterns annoying to write and unreadable.
|
||||
Because of this, I created a library to do this for you.
|
||||
|
||||
```lua
|
||||
ChatCmdBuilder.new("sethp", function(cmd)
|
||||
cmd:sub(":target :hp:int", function(name, target, hp)
|
||||
local player = minetest.get_player_by_name(target)
|
||||
if player then
|
||||
player:set_hp(hp)
|
||||
return true, "Killed " .. target
|
||||
else
|
||||
return false, "Unable to find " .. target
|
||||
end
|
||||
end)
|
||||
end, {
|
||||
description = "Set hp of player",
|
||||
privs = {
|
||||
kick = true
|
||||
-- ^ probably better to register a custom priv
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
`ChatCmdBuilder.new(name, setup_func, def)` creates a new chat command called
|
||||
`name`. It then calls the function passed to it (`setup_func`), which then creates
|
||||
subcommands. Each `cmd:sub(route, func)` is a subcommand.
|
||||
|
||||
A subcommand is a particular response to an input param. When a player runs
|
||||
the chat command, the first subcommand that matches their input will be run,
|
||||
and no others. If no subcommands match, then the user will be told of the invalid
|
||||
syntax. For example, in the above code snippet if a player
|
||||
types something of the form `/sethp username 12` then the function passed
|
||||
to cmd:sub will be called. If they type `/sethp 12 bleh`, then a wrong
|
||||
input message will appear.
|
||||
|
||||
`:name :hp:int` is a route. It describes the format of the param passed to /teleport.
|
||||
|
||||
## Routes
|
||||
|
||||
A route is made up of terminals and variables. Terminals must always be there.
|
||||
For example, `join` in `/team join :username :teamname`. The spaces also count
|
||||
as terminals.
|
||||
|
||||
Variables can change value depending on what the user types. For example, `:username`
|
||||
and `:teamname`.
|
||||
|
||||
Variables are defined as `:name:type`. The `name` is used in the help documentation.
|
||||
The `type` is used to match the input. If the type is not given, then the type is
|
||||
`word`.
|
||||
|
||||
Valid types are:
|
||||
|
||||
* `word` - default. Any string without spaces.
|
||||
* `int` - Any integer/whole number, no decimals.
|
||||
* `number` - Any number, including ints and decimals.
|
||||
* `pos` - 1,2,3 or 1.1,2,3.4567 or (1,2,3) or 1.2, 2 ,3.2
|
||||
* `text` - Any string. There can only ever be one text variable,
|
||||
no variables or terminals can come afterwards.
|
||||
|
||||
In `:name :hp:int`, there are two variables:
|
||||
|
||||
* `name` - type of `word` as no type is specified. Accepts any string without spaces.
|
||||
* `hp` - type of `int`
|
||||
|
||||
## Subcommand functions
|
||||
|
||||
The first argument is the caller's name. The variables are then passed to the
|
||||
function in order.
|
||||
|
||||
```lua
|
||||
cmd:sub(":target :hp:int", function(name, target, hp)
|
||||
-- subcommand function
|
||||
end)
|
||||
```
|
||||
|
||||
## Installing ChatCmdBuilder
|
||||
|
||||
The source code can be found and downloaded on
|
||||
[Github](https://github.com/rubenwardy/ChatCmdBuilder/).
|
||||
|
||||
There are two ways to install:
|
||||
|
||||
1. Install ChatCmdBuilder as a mod and depend on it.
|
||||
2. Include the init.lua file in ChatCmdBuilder as chatcmdbuilder.lua in your mod,
|
||||
and dofile it.
|
||||
|
||||
## Admin complex command
|
||||
|
||||
Here is an example that creates a chat command that allows us to do this:
|
||||
|
||||
* `/admin kill <username>` - kill user
|
||||
* `/admin move <username> to <pos>` - teleport user
|
||||
* `/admin log <username>` - show report log
|
||||
* `/admin log <username> <message>` - log to report log
|
||||
|
||||
```lua
|
||||
local admin_log
|
||||
local function load()
|
||||
admin_log = {}
|
||||
end
|
||||
local function save()
|
||||
-- todo
|
||||
end
|
||||
load()
|
||||
|
||||
ChatCmdBuilder.new("admin", function(cmd)
|
||||
cmd:sub("kill :name", function(name, target)
|
||||
local player = minetest.get_player_by_name(target)
|
||||
if player then
|
||||
player:set_hp(0)
|
||||
return true, "Killed " .. target
|
||||
else
|
||||
return false, "Unable to find " .. target
|
||||
end
|
||||
end)
|
||||
|
||||
cmd:sub("move :name to :pos:pos", function(name, target, pos)
|
||||
local player = minetest.get_player_by_name(target)
|
||||
if player then
|
||||
player:setpos(pos)
|
||||
return true, "Moved " .. target .. " to " ..
|
||||
minetest.pos_to_string(pos)
|
||||
else
|
||||
return false, "Unable to find " .. target
|
||||
end
|
||||
end)
|
||||
|
||||
cmd:sub("log :username", function(name, target)
|
||||
local log = admin_log[target]
|
||||
if log then
|
||||
return true, table.concat(log, "\n")
|
||||
else
|
||||
return false, "No entries for " .. target
|
||||
end
|
||||
end)
|
||||
|
||||
cmd:sub("log :username :message", function(name, target, message)
|
||||
local log = admin_log[target] or {}
|
||||
table.insert(log, message)
|
||||
admin_log[target] = log
|
||||
save()
|
||||
return true, "Logged"
|
||||
end)
|
||||
end, {
|
||||
description = "Admin tools",
|
||||
privs = {
|
||||
kick = true,
|
||||
ban = true
|
||||
}
|
||||
})
|
||||
```
|
@ -56,8 +56,6 @@ called real coordinates which aims to rectify this by introducing a consistent
|
||||
coordinate system. The use of real coordinates is highly recommended, and so
|
||||
this chapter will use them exclusively.
|
||||
|
||||
Using a formspec_version of 2 or above will enable real coordinates.
|
||||
|
||||
## Anatomy of a Formspec
|
||||
|
||||
### Elements
|
||||
@ -77,7 +75,7 @@ on multiple lines, like so:
|
||||
|
||||
Elements are items such as text boxes or buttons, or can be metadata such
|
||||
as size or background. You should refer to
|
||||
[lua_api.md](https://minetest.gitlab.io/minetest/formspec/)
|
||||
[lua_api.txt](../../lua_api.html#elements)
|
||||
for a list of all possible elements.
|
||||
|
||||
|
||||
@ -89,18 +87,18 @@ game-wide theme should be applied.
|
||||
|
||||
The elements in the header must be defined in a specific order, otherwise you
|
||||
will see an error. This order is given in the above paragraph, and, as always,
|
||||
documented in the Lua API reference.
|
||||
documented in [lua_api.txt](../../lua_api.html#sizewhfixed_size).
|
||||
|
||||
The size is in formspec slots - a unit of measurement which is roughly
|
||||
around 64 pixels, but varies based on the screen density and scaling
|
||||
settings of the client. Here's a formspec which is `2,2` in size:
|
||||
|
||||
formspec_version[4]
|
||||
formspec_version[3]
|
||||
size[2,2]
|
||||
|
||||
Notice how we explicitly defined the formspec language version.
|
||||
Without this, the legacy system will instead be used instead - which will
|
||||
prevent the use of consistent element positioning and other new features.
|
||||
prevent the use of consistent element positioning and other new feautures.
|
||||
|
||||
The position and anchor elements are used to place the formspec on the screen.
|
||||
The position sets where on the screen the formspec will be, and defaults to
|
||||
@ -108,8 +106,9 @@ the center (`0.5,0.5`). The anchor sets where on the formspec the position is,
|
||||
allowing you to line the formspec up with the edge of the screen. The formspec
|
||||
can be placed to the left of the screen like so:
|
||||
|
||||
formspec_version[4]
|
||||
formspec_version[3]
|
||||
size[2,2]
|
||||
real_coordinates[true]
|
||||
position[0,0.5]
|
||||
anchor[0,0.5]
|
||||
|
||||
@ -144,9 +143,9 @@ function guessing.get_formspec(name)
|
||||
local text = "I'm thinking of a number... Make a guess!"
|
||||
|
||||
local formspec = {
|
||||
"formspec_version[4]",
|
||||
"formspec_version[3]",
|
||||
"size[6,3.476]",
|
||||
"label[0.375,0.5;", core.formspec_escape(text), "]",
|
||||
"label[0.375,0.5;", minetest.formspec_escape(text), "]",
|
||||
"field[0.375,1.25;5.25,0.8;number;Number;]",
|
||||
"button[1.5,2.3;3,0.8;guess;Guess]"
|
||||
}
|
||||
@ -166,10 +165,10 @@ is using `show_formspec`:
|
||||
|
||||
```lua
|
||||
function guessing.show_to(name)
|
||||
core.show_formspec(name, "guessing:game", guessing.get_formspec(name))
|
||||
minetest.show_formspec(name, "guessing:game", guessing.get_formspec(name))
|
||||
end
|
||||
|
||||
core.register_chatcommand("game", {
|
||||
minetest.register_chatcommand("game", {
|
||||
func = function(name)
|
||||
guessing.show_to(name)
|
||||
end,
|
||||
@ -206,28 +205,28 @@ The method for this is called formspec field submission, and for `show_formspec`
|
||||
submission is received using a global callback:
|
||||
|
||||
```lua
|
||||
core.register_on_player_receive_fields(function(player, formname, fields)
|
||||
minetest.register_on_player_receive_fields(function(player, formname, fields)
|
||||
if formname ~= "guessing:game" then
|
||||
return
|
||||
end
|
||||
|
||||
if fields.guess then
|
||||
local pname = player:get_player_name()
|
||||
core.chat_send_all(pname .. " guessed " .. fields.number)
|
||||
minetest.chat_send_all(pname .. " guessed " .. fields.number)
|
||||
end
|
||||
end)
|
||||
```
|
||||
|
||||
The function given in `core.register_on_player_receive_fields` is called
|
||||
The function given in `minetest.register_on_player_receive_fields` is called
|
||||
every time a user submits a form. Most callbacks will need to check the formname given
|
||||
to the function, and exit if it is not the right form; however, some callbacks
|
||||
may need to work on multiple forms, or on all forms.
|
||||
|
||||
The `fields` parameter to the function is a table of the values submitted by the
|
||||
user, indexed by strings. Named elements will appear in the field under their own
|
||||
name, depending on the event. Some elements will only be submitted if they caused
|
||||
the event, such as buttons, and some elements will always appear in submissions,
|
||||
such as fields.
|
||||
name, but only if they are relevent for the event that caused the submission.
|
||||
For example, a button element will only appear in fields if that particular button
|
||||
was pressed.
|
||||
|
||||
{% include notice.html notice=page.submit_vuln %}
|
||||
|
||||
@ -239,7 +238,7 @@ the formspec based on guesses. The way to do this is using a concept called
|
||||
|
||||
### Contexts
|
||||
|
||||
In many cases you want core.show_formspec to give information
|
||||
In many cases you want minetest.show_formspec to give information
|
||||
to the callback which you don't want to send to the client. This might include
|
||||
what a chat command was called with, or what the dialog is about. In this case,
|
||||
the target value that needs to be remembered.
|
||||
@ -255,7 +254,7 @@ local function get_context(name)
|
||||
return context
|
||||
end
|
||||
|
||||
core.register_on_leaveplayer(function(player)
|
||||
minetest.register_on_leaveplayer(function(player)
|
||||
_contexts[player:get_player_name()] = nil
|
||||
end)
|
||||
```
|
||||
@ -269,7 +268,7 @@ function guessing.show_to(name)
|
||||
context.target = context.target or math.random(1, 10)
|
||||
|
||||
local fs = guessing.get_formspec(name, context)
|
||||
core.show_formspec(name, "guessing:game", fs)
|
||||
minetest.show_formspec(name, "guessing:game", fs)
|
||||
end
|
||||
```
|
||||
|
||||
@ -318,13 +317,13 @@ There are three different ways that a formspec can be delivered to the client:
|
||||
|
||||
### Node Meta Formspecs
|
||||
|
||||
`core.show_formspec` is not the only way to show a formspec; you can also
|
||||
add formspecs to a [node's metadata](../map/storage.html). For example,
|
||||
`minetest.show_formspec` is not the only way to show a formspec; you can also
|
||||
add formspecs to a [node's metadata](node_metadata.html). For example,
|
||||
this is used with chests to allow for faster opening times -
|
||||
you don't need to wait for the server to send the player the chest formspec.
|
||||
|
||||
```lua
|
||||
core.register_node("mymod:rightclick", {
|
||||
minetest.register_node("mymod:rightclick", {
|
||||
description = "Rightclick me!",
|
||||
tiles = {"mymod_rightclick.png"},
|
||||
groups = {cracky = 1},
|
||||
@ -333,9 +332,9 @@ core.register_node("mymod:rightclick", {
|
||||
-- The following code sets the formspec for chest.
|
||||
-- Meta is a way of storing data onto a node.
|
||||
|
||||
local meta = core.get_meta(pos)
|
||||
local meta = minetest.get_meta(pos)
|
||||
meta:set_string("formspec",
|
||||
"formspec_version[4]" ..
|
||||
"formspec_version[3]" ..
|
||||
"size[5,5]" ..
|
||||
"label[1,1;This is shown on right click]" ..
|
||||
"field[1,2;2,1;x;x;]")
|
||||
@ -355,7 +354,7 @@ receive form input for meta formspecs, you must include an
|
||||
`on_receive_fields` entry when registering the node.
|
||||
|
||||
This style of callback triggers when you press enter
|
||||
in a field, which is impossible with `core.show_formspec`;
|
||||
in a field, which is impossible with `minetest.show_formspec`;
|
||||
however, this kind of form can only be shown by right-clicking on a
|
||||
node. It cannot be triggered programmatically.
|
||||
|
||||
@ -365,9 +364,9 @@ The player inventory formspec is the one shown when the player presses i.
|
||||
The global callback is used to receive events from this formspec, and the
|
||||
formname is `""`.
|
||||
|
||||
There are a number of different mods which allow multiple mods to customise the
|
||||
player inventory. Minetest Game uses
|
||||
[SFINV](https://github.com/rubenwardy/sfinv/blob/master/Tutorial.md).
|
||||
There are a number of different mods which allow multiple mods to customise
|
||||
the player inventory. The officially recommended mod is
|
||||
[Simple Fast Inventory (sfinv)](sfinv.html), and is included in Minetest Game.
|
||||
|
||||
|
||||
### Your Turn
|
||||
|
@ -89,7 +89,7 @@ to the right of the window, but to resize without breaking.
|
||||
You can create a HUD element once you have a copy of the player object:
|
||||
|
||||
```lua
|
||||
local player = core.get_player_by_name("username")
|
||||
local player = minetest.get_player_by_name("username")
|
||||
local idx = player:hud_add({
|
||||
hud_elem_type = "text",
|
||||
position = {x = 0.5, y = 0.5},
|
||||
@ -281,9 +281,9 @@ function score.update_hud(player)
|
||||
end
|
||||
end
|
||||
|
||||
core.register_on_joinplayer(score.update_hud)
|
||||
minetest.register_on_joinplayer(score.update_hud)
|
||||
|
||||
core.register_on_leaveplayer(function(player)
|
||||
minetest.register_on_leaveplayer(function(player)
|
||||
saved_huds[player:get_player_name()] = nil
|
||||
end)
|
||||
```
|
||||
@ -291,4 +291,4 @@ end)
|
||||
|
||||
## Other Elements
|
||||
|
||||
Read [lua_api.md](https://minetest.gitlab.io/minetest/hud/) for a complete list of HUD elements.
|
||||
Read [lua_api.txt]({{ page.root }}/lua_api.html#hud-element-types) for a complete list of HUD elements.
|
||||
|
@ -28,9 +28,9 @@ Here is an example of how to add an antigravity command, which
|
||||
puts the caller in low G:
|
||||
|
||||
```lua
|
||||
core.register_chatcommand("antigravity", {
|
||||
minetest.register_chatcommand("antigravity", {
|
||||
func = function(name, param)
|
||||
local player = core.get_player_by_name(name)
|
||||
local player = minetest.get_player_by_name(name)
|
||||
player:set_physics_override({
|
||||
gravity = 0.1, -- set gravity to 10% of its original value
|
||||
-- (0.1 * 9.81)
|
||||
@ -42,7 +42,7 @@ core.register_chatcommand("antigravity", {
|
||||
## Available Overrides
|
||||
|
||||
`player:set_physics_override()` is given a table of overrides.\\
|
||||
According to [lua_api.md](https://minetest.gitlab.io/minetest/class-reference/#player-only-no-op-for-other-objects),
|
||||
According to [lua_api.txt]({{ page.root }}/lua_api.html#player-only-no-op-for-other-objects),
|
||||
these can be:
|
||||
|
||||
* speed: multiplier to default walking speed value (default: 1)
|
||||
|
@ -48,7 +48,7 @@ Privileges are **not** for indicating class or status.
|
||||
Use `register_privilege` to declare a new privilege:
|
||||
|
||||
```lua
|
||||
core.register_privilege("vote", {
|
||||
minetest.register_privilege("vote", {
|
||||
description = "Can vote on issues",
|
||||
give_to_singleplayer = true
|
||||
})
|
||||
@ -62,7 +62,7 @@ actually needed in the above definition.
|
||||
To quickly check whether a player has all the required privileges:
|
||||
|
||||
```lua
|
||||
local has, missing = core.check_player_privs(player_or_name, {
|
||||
local has, missing = minetest.check_player_privs(player_or_name, {
|
||||
interact = true,
|
||||
vote = true })
|
||||
```
|
||||
@ -72,7 +72,7 @@ If `has` is false, then `missing` will contain a key-value table
|
||||
of the missing privileges.
|
||||
|
||||
```lua
|
||||
local has, missing = core.check_player_privs(name, {
|
||||
local has, missing = minetest.check_player_privs(name, {
|
||||
interact = true,
|
||||
vote = true })
|
||||
|
||||
@ -87,7 +87,7 @@ If you don't need to check the missing privileges, you can put
|
||||
`check_player_privs` directly into the if statement.
|
||||
|
||||
```lua
|
||||
if not core.check_player_privs(name, { interact=true }) then
|
||||
if not minetest.check_player_privs(name, { interact=true }) then
|
||||
return false, "You need interact for this!"
|
||||
end
|
||||
```
|
||||
@ -99,11 +99,11 @@ being online.
|
||||
|
||||
|
||||
```lua
|
||||
local privs = core.get_player_privs(name)
|
||||
local privs = minetest.get_player_privs(name)
|
||||
print(dump(privs))
|
||||
|
||||
privs.vote = true
|
||||
core.set_player_privs(name, privs)
|
||||
minetest.set_player_privs(name, privs)
|
||||
```
|
||||
|
||||
Privileges are always specified as a key-value table with the key being
|
||||
|
@ -1,5 +1,242 @@
|
||||
---
|
||||
sitemap: false
|
||||
title: "SFINV: Inventory Formspec"
|
||||
layout: default
|
||||
root: ../..
|
||||
idx: 4.7
|
||||
redirect_from: /en/chapters/sfinv.html
|
||||
redirect_to: "https://github.com/rubenwardy/sfinv/blob/master/Tutorial.md"
|
||||
---
|
||||
|
||||
## Introduction <!-- omit in toc -->
|
||||
|
||||
Simple Fast Inventory (SFINV) is a mod found in Minetest Game that is used to
|
||||
create the player's inventory [formspec](formspecs.html). SFINV comes with
|
||||
an API that allows you to add and otherwise manage the pages shown.
|
||||
|
||||
Whilst SFINV by default shows pages as tabs, pages are called pages
|
||||
because it is entirely possible that a mod or game decides to show them in
|
||||
some other format instead.
|
||||
For example, multiple pages could be shown in one formspec.
|
||||
|
||||
- [Registering a Page](#registering-a-page)
|
||||
- [Receiving events](#receiving-events)
|
||||
- [Conditionally showing to players](#conditionally-showing-to-players)
|
||||
- [on_enter and on_leave callbacks](#onenter-and-onleave-callbacks)
|
||||
- [Adding to an existing page](#adding-to-an-existing-page)
|
||||
|
||||
## Registering a Page
|
||||
|
||||
SFINV provides the aptly named `sfinv.register_page` function to create pages.
|
||||
Simply call the function with the page's name and its definition:
|
||||
|
||||
```lua
|
||||
sfinv.register_page("mymod:hello", {
|
||||
title = "Hello!",
|
||||
get = function(self, player, context)
|
||||
return sfinv.make_formspec(player, context,
|
||||
"label[0.1,0.1;Hello world!]", true)
|
||||
end
|
||||
})
|
||||
```
|
||||
|
||||
The `make_formspec` function surrounds your formspec with SFINV's formspec code.
|
||||
The fourth parameter, currently set as `true`, determines whether the
|
||||
player's inventory is shown.
|
||||
|
||||
Let's make things more exciting; here is the code for the formspec generation
|
||||
part of a player admin tab. This tab will allow admins to kick or ban players by
|
||||
selecting them in a list and clicking a button.
|
||||
|
||||
```lua
|
||||
sfinv.register_page("myadmin:myadmin", {
|
||||
title = "Tab",
|
||||
get = function(self, player, context)
|
||||
local players = {}
|
||||
context.myadmin_players = players
|
||||
|
||||
-- Using an array to build a formspec is considerably faster
|
||||
local formspec = {
|
||||
"textlist[0.1,0.1;7.8,3;playerlist;"
|
||||
}
|
||||
|
||||
-- Add all players to the text list, and to the players list
|
||||
local is_first = true
|
||||
for _ , player in pairs(minetest.get_connected_players()) do
|
||||
local player_name = player:get_player_name()
|
||||
players[#players + 1] = player_name
|
||||
if not is_first then
|
||||
formspec[#formspec + 1] = ","
|
||||
end
|
||||
formspec[#formspec + 1] =
|
||||
minetest.formspec_escape(player_name)
|
||||
is_first = false
|
||||
end
|
||||
formspec[#formspec + 1] = "]"
|
||||
|
||||
-- Add buttons
|
||||
formspec[#formspec + 1] = "button[0.1,3.3;2,1;kick;Kick]"
|
||||
formspec[#formspec + 1] = "button[2.1,3.3;2,1;ban;Kick + Ban]"
|
||||
|
||||
-- Wrap the formspec in sfinv's layout
|
||||
-- (ie: adds the tabs and background)
|
||||
return sfinv.make_formspec(player, context,
|
||||
table.concat(formspec, ""), false)
|
||||
end,
|
||||
})
|
||||
```
|
||||
|
||||
There's nothing new about the above code; all the concepts are
|
||||
covered above and in previous chapters.
|
||||
|
||||
<figure>
|
||||
<img src="{{ page.root }}//static/sfinv_admin_fs.png" alt="Player Admin Page">
|
||||
</figure>
|
||||
|
||||
## Receiving events
|
||||
|
||||
You can receive formspec events by adding a `on_player_receive_fields` function
|
||||
to a sfinv definition.
|
||||
|
||||
```lua
|
||||
on_player_receive_fields = function(self, player, context, fields)
|
||||
-- TODO: implement this
|
||||
end,
|
||||
```
|
||||
|
||||
`on_player_receive_fields` works the same as
|
||||
`minetest.register_on_player_receive_fields`, except that `context` is
|
||||
given instead of `formname`.
|
||||
Please note that SFINV will consume events relevant to itself, such as
|
||||
navigation tab events, so you won't receive them in this callback.
|
||||
|
||||
Now let's implement the `on_player_receive_fields` for our admin mod:
|
||||
|
||||
```lua
|
||||
on_player_receive_fields = function(self, player, context, fields)
|
||||
-- text list event, check event type and set index if selection changed
|
||||
if fields.playerlist then
|
||||
local event = minetest.explode_textlist_event(fields.playerlist)
|
||||
if event.type == "CHG" then
|
||||
context.myadmin_selected_idx = event.index
|
||||
end
|
||||
|
||||
-- Kick button was pressed
|
||||
elseif fields.kick then
|
||||
local player_name =
|
||||
context.myadmin_players[context.myadmin_selected_idx]
|
||||
if player_name then
|
||||
minetest.chat_send_player(player:get_player_name(),
|
||||
"Kicked " .. player_name)
|
||||
minetest.kick_player(player_name)
|
||||
end
|
||||
|
||||
-- Ban button was pressed
|
||||
elseif fields.ban then
|
||||
local player_name =
|
||||
context.myadmin_players[context.myadmin_selected_idx]
|
||||
if player_name then
|
||||
minetest.chat_send_player(player:get_player_name(),
|
||||
"Banned " .. player_name)
|
||||
minetest.ban_player(player_name)
|
||||
minetest.kick_player(player_name, "Banned")
|
||||
end
|
||||
end
|
||||
end,
|
||||
```
|
||||
|
||||
There's a rather large problem with this, however. Anyone can kick or ban players! You
|
||||
need a way to only show this to players with the kick or ban privileges.
|
||||
Luckily SFINV allows you to do this!
|
||||
|
||||
## Conditionally showing to players
|
||||
|
||||
You can add an `is_in_nav` function to your page's definition if you'd like to
|
||||
control when the page is shown:
|
||||
|
||||
```lua
|
||||
is_in_nav = function(self, player, context)
|
||||
local privs = minetest.get_player_privs(player:get_player_name())
|
||||
return privs.kick or privs.ban
|
||||
end,
|
||||
```
|
||||
|
||||
If you only need to check one priv or want to perform an 'and', you should use
|
||||
`minetest.check_player_privs()` instead of `get_player_privs`.
|
||||
|
||||
Note that the `is_in_nav` is only called when the player's inventory formspec is
|
||||
generated. This happens when a player joins the game, switches tabs, or a mod
|
||||
requests for SFINV to regenerate.
|
||||
|
||||
This means that you need to manually request that SFINV regenerates the inventory
|
||||
formspec on any events that may change `is_in_nav`'s result. In our case,
|
||||
we need to do that whenever kick or ban is granted or revoked to a player:
|
||||
|
||||
```lua
|
||||
local function on_grant_revoke(grantee, granter, priv)
|
||||
if priv ~= "kick" and priv ~= "ban" then
|
||||
return
|
||||
end
|
||||
|
||||
local player = minetest.get_player_by_name(grantee)
|
||||
if not player then
|
||||
return
|
||||
end
|
||||
|
||||
local context = sfinv.get_or_create_context(player)
|
||||
if context.page ~= "myadmin:myadmin" then
|
||||
return
|
||||
end
|
||||
|
||||
sfinv.set_player_inventory_formspec(player, context)
|
||||
end
|
||||
|
||||
minetest.register_on_priv_grant(on_grant_revoke)
|
||||
minetest.register_on_priv_revoke(on_grant_revoke)
|
||||
```
|
||||
|
||||
## on_enter and on_leave callbacks
|
||||
|
||||
A player *enters* a tab when the tab is selected and *leaves* a
|
||||
tab when another tab is about to be selected.
|
||||
It's possible for multiple pages to be selected if a custom theme is
|
||||
used.
|
||||
|
||||
Note that these events may not be triggered by the player.
|
||||
The player may not even have the formspec open at that time.
|
||||
For example, on_enter is called for the home page when a player
|
||||
joins the game even before they open their inventory.
|
||||
|
||||
It's not possible to cancel a page change, as that would potentially
|
||||
confuse the player.
|
||||
|
||||
```lua
|
||||
on_enter = function(self, player, context)
|
||||
|
||||
end,
|
||||
|
||||
on_leave = function(self, player, context)
|
||||
|
||||
end,
|
||||
```
|
||||
|
||||
## Adding to an existing page
|
||||
|
||||
To add content to an existing page, you will need to override the page
|
||||
and modify the returned formspec.
|
||||
|
||||
```lua
|
||||
local old_func = sfinv.registered_pages["sfinv:crafting"].get
|
||||
sfinv.override_page("sfinv:crafting", {
|
||||
get = function(self, player, context, ...)
|
||||
local ret = old_func(self, player, context, ...)
|
||||
|
||||
if type(ret) == "table" then
|
||||
ret.formspec = ret.formspec .. "label[0,0;Hello]"
|
||||
else
|
||||
-- Backwards compatibility
|
||||
ret = ret .. "label[0,0;Hello]"
|
||||
end
|
||||
|
||||
return ret
|
||||
end
|
||||
})
|
||||
```
|
||||
|
@ -100,7 +100,7 @@ what is listening to something.
|
||||
|
||||
In the next chapter, we will discuss how to automatically test your
|
||||
code and one of the problems we will have is how to separate your logic
|
||||
(calculations, what should be done) from API calls (`core.*`, other mods)
|
||||
(calculations, what should be done) from API calls (`minetest.*`, other mods)
|
||||
as much as possible.
|
||||
|
||||
One way to do this is to think about:
|
||||
@ -173,14 +173,14 @@ function land.show_create_formspec(name)
|
||||
]]
|
||||
end
|
||||
|
||||
core.register_chatcommand("/land", {
|
||||
minetest.register_chatcommand("/land", {
|
||||
privs = { land = true },
|
||||
func = function(name)
|
||||
land.handle_creation_request(name)
|
||||
end,
|
||||
})
|
||||
|
||||
core.register_on_player_receive_fields(function(player,
|
||||
minetest.register_on_player_receive_fields(function(player,
|
||||
formname, fields)
|
||||
land.handle_create_submit(player:get_player_name(),
|
||||
fields.area_name)
|
||||
@ -227,7 +227,7 @@ this isn't the real world. A good compromise is to reduce the mod into two
|
||||
parts:
|
||||
|
||||
* **API** - This was the model and controller above. There should be no uses of
|
||||
`core.` here.
|
||||
`minetest.` here.
|
||||
* **View** - This was also the view above. It's a good idea to structure this into separate
|
||||
files for each type of event.
|
||||
|
||||
|
@ -10,27 +10,54 @@ redirect_from: /en/chapters/common_mistakes.html
|
||||
|
||||
This chapter details common mistakes, and how to avoid them.
|
||||
|
||||
- [Be Careful When Storing ObjectRefs (ie: players or entities)](#be-careful-when-storing-objectrefs-ie-players-or-entities)
|
||||
- [Never Store ObjectRefs (ie: players or entities)](#never-store-objectrefs-ie-players-or-entities)
|
||||
- [Don't Trust Formspec Submissions](#dont-trust-formspec-submissions)
|
||||
- [Set ItemStacks After Changing Them](#set-itemstacks-after-changing-them)
|
||||
|
||||
## Be Careful When Storing ObjectRefs (ie: players or entities)
|
||||
## Never Store ObjectRefs (ie: players or entities)
|
||||
|
||||
An ObjectRef is invalidated when the player or entity it represents leaves
|
||||
the game. This may happen when the player goes offline, or the entity is unloaded
|
||||
or removed.
|
||||
If the object an ObjectRef represents is deleted - for example, if the player goes
|
||||
offline or the entity is unloaded - then calling methods on that object
|
||||
will result in a crash.
|
||||
|
||||
The methods of ObjectRefs will always return nil when invalid, since Minetest 5.2.
|
||||
Any call will essentially be ignored.
|
||||
|
||||
You should avoid storing ObjectRefs where possible. If you do to store an
|
||||
ObjectRef, you should make sure you check it before use, like so:
|
||||
For example, don't do this:
|
||||
|
||||
```lua
|
||||
-- This only works in Minetest 5.2+
|
||||
if obj:get_pos() then
|
||||
-- is valid!
|
||||
minetest.register_on_joinplayer(function(player)
|
||||
local function func()
|
||||
local pos = player:get_pos() -- BAD!
|
||||
-- `player` is stored then accessed later.
|
||||
-- If the player leaves in that second, the server *will* crash.
|
||||
end
|
||||
|
||||
minetest.after(1, func)
|
||||
|
||||
foobar[player:get_player_name()] = player
|
||||
-- RISKY
|
||||
-- It's not recommended to do this.
|
||||
-- Use minetest.get_connected_players() and
|
||||
-- minetest.get_player_by_name() instead.
|
||||
end)
|
||||
```
|
||||
|
||||
Do this instead:
|
||||
|
||||
```lua
|
||||
minetest.register_on_joinplayer(function(player)
|
||||
local function func(name)
|
||||
-- Attempt to get the ref again
|
||||
local player = minetest.get_player_by_name(name)
|
||||
|
||||
-- Check that the player is still online
|
||||
if player then
|
||||
-- Yay! This is fine
|
||||
local pos = player:get_pos()
|
||||
end
|
||||
end
|
||||
|
||||
-- Pass the name into the function
|
||||
minetest.after(1, func, player:get_player_name())
|
||||
end)
|
||||
```
|
||||
|
||||
## Don't Trust Formspec Submissions
|
||||
@ -43,11 +70,11 @@ give themselves moderator privileges:
|
||||
|
||||
```lua
|
||||
local function show_formspec(name)
|
||||
if not core.check_player_privs(name, { privs = true }) then
|
||||
if not minetest.check_player_privs(name, { privs = true }) then
|
||||
return false
|
||||
end
|
||||
|
||||
core.show_formspec(name, "modman:modman", [[
|
||||
minetest.show_formspec(name, "modman:modman", [[
|
||||
size[3,2]
|
||||
field[0,0;3,1;target;Name;]
|
||||
button_exit[0,1;3,1;sub;Promote]
|
||||
@ -55,14 +82,14 @@ local function show_formspec(name)
|
||||
return true
|
||||
})
|
||||
|
||||
core.register_on_player_receive_fields(function(player,
|
||||
minetest.register_on_player_receive_fields(function(player,
|
||||
formname, fields)
|
||||
-- BAD! Missing privilege check here!
|
||||
|
||||
local privs = core.get_player_privs(fields.target)
|
||||
local privs = minetest.get_player_privs(fields.target)
|
||||
privs.kick = true
|
||||
privs.ban = true
|
||||
core.set_player_privs(fields.target, privs)
|
||||
minetest.set_player_privs(fields.target, privs)
|
||||
return true
|
||||
end)
|
||||
```
|
||||
@ -70,9 +97,9 @@ end)
|
||||
Add a privilege check to solve this:
|
||||
|
||||
```lua
|
||||
core.register_on_player_receive_fields(function(player,
|
||||
minetest.register_on_player_receive_fields(function(player,
|
||||
formname, fields)
|
||||
if not core.check_player_privs(name, { privs = true }) then
|
||||
if not minetest.check_player_privs(name, { privs = true }) then
|
||||
return false
|
||||
end
|
||||
|
||||
@ -111,7 +138,7 @@ are given will change it for the caller too, and any subsequent callbacks. Howev
|
||||
it will only be saved in the engine if the callback caller sets it.
|
||||
|
||||
```lua
|
||||
core.register_on_item_eat(function(hp_change, replace_with_item,
|
||||
minetest.register_on_item_eat(function(hp_change, replace_with_item,
|
||||
itemstack, user, pointed_thing)
|
||||
itemstack:get_meta():set_string("description", "Partially eaten")
|
||||
-- Almost correct! Data will be lost if another
|
||||
@ -125,7 +152,7 @@ but if a callback does cancel this, then the update may be lost.
|
||||
It's better to do this instead:
|
||||
|
||||
```lua
|
||||
core.register_on_item_eat(function(hp_change, replace_with_item,
|
||||
minetest.register_on_item_eat(function(hp_change, replace_with_item,
|
||||
itemstack, user, pointed_thing)
|
||||
itemstack:get_meta():set_string("description", "Partially eaten")
|
||||
user:get_inventory():set_stack("main", user:get_wield_index(),
|
||||
|
@ -20,6 +20,7 @@ editor to provide alerts to any mistakes.
|
||||
- [Configuring LuaCheck](#configuring-luacheck)
|
||||
- [Troubleshooting](#troubleshooting)
|
||||
- [Using with editor](#using-with-editor)
|
||||
- [Checking Commits with Travis](#checking-commits-with-travis)
|
||||
|
||||
## Installing LuaCheck
|
||||
|
||||
@ -101,7 +102,51 @@ It is highly recommended that you find and install a plugin for your editor of c
|
||||
to show you errors without running a command. Most editors will likely have a plugin
|
||||
available.
|
||||
|
||||
* **Atom** - `linter-luacheck`.
|
||||
* **VSCode** - Ctrl+P, then paste: `ext install dwenegar.vscode-luacheck`
|
||||
* **Sublime** - Install using package-control:
|
||||
[SublimeLinter](https://github.com/SublimeLinter/SublimeLinter),
|
||||
[SublimeLinter-luacheck](https://github.com/SublimeLinter/SublimeLinter-luacheck).
|
||||
|
||||
## Checking Commits with Travis
|
||||
|
||||
If your project is public and is on Github, you can use TravisCI - a free service
|
||||
to run jobs on commits to check them. This means that every commit you push will
|
||||
be checked against LuaCheck, and a green tick or red cross will be displayed next to them
|
||||
depending on whether LuaCheck finds any mistakes. This is especially helpful for
|
||||
when your project receives a pull request - you'll be able to see the LuaCheck output
|
||||
without downloading the code.
|
||||
|
||||
First, you should visit [travis-ci.org](https://travis-ci.org/) and sign in with
|
||||
your Github account. Then find your project's repo in your Travis profile,
|
||||
and enable Travis by flipping the switch.
|
||||
|
||||
Next, create a file called .travis.yml with the following content:
|
||||
|
||||
```yml
|
||||
language: generic
|
||||
sudo: false
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- luarocks
|
||||
before_install:
|
||||
- luarocks install --local luacheck
|
||||
script:
|
||||
- $HOME/.luarocks/bin/luacheck .
|
||||
notifications:
|
||||
email: false
|
||||
```
|
||||
|
||||
If your project is a game rather than a mod or mod pack,
|
||||
change the line after `script:` to:
|
||||
|
||||
```yml
|
||||
- $HOME/.luarocks/bin/luacheck mods/
|
||||
```
|
||||
|
||||
Now commit and push to Github. Go to your project's page on Github, and click
|
||||
'commits'. You should see an orange disc next to the commit you just made.
|
||||
After awhile it should change either into a green tick or a red cross depending on the
|
||||
outcome of LuaCheck. In either case, you can click the icon to see the build logs
|
||||
and the output of LuaCheck.
|
||||
|
@ -12,13 +12,15 @@ After you've read this book, take a look at the following.
|
||||
|
||||
### Minetest Modding
|
||||
|
||||
* Minetest's Lua API Reference - [multiple page version](https://minetest.gitlab.io/minetest/class-reference/#player-only-no-op-for-other-objects) |
|
||||
[single page version](https://github.com/minetest/minetest/blob/master/doc/lua_api.md).
|
||||
* Minetest's Lua API Reference - [HTML version]({{ page.root }}/lua_api.html) |
|
||||
[Text version](https://github.com/minetest/minetest/blob/master/doc/lua_api.txt).
|
||||
* Explore the [Developer Wiki](http://dev.minetest.net/Main_Page).
|
||||
* Look at [existing mods](https://forum.minetest.net/viewforum.php?f=11).
|
||||
|
||||
### Lua Programming
|
||||
|
||||
* [Programming in Lua (PIL)](http://www.lua.org/pil/).
|
||||
* [Lua Crash Course](http://luatut.com/crash_course.html).
|
||||
|
||||
### 3D Modelling
|
||||
|
||||
|
@ -11,20 +11,21 @@ redirect_from: /en/chapters/releasing.html
|
||||
Releasing, or publishing, a mod allows other people to make use of it. Once a mod has been
|
||||
released it might be used in singleplayer games or on servers, including public servers.
|
||||
|
||||
- [Choosing a License](#choosing-a-license)
|
||||
- [License Choices](#license-choices)
|
||||
- [LGPL and CC-BY-SA](#lgpl-and-cc-by-sa)
|
||||
- [CC0](#cc0)
|
||||
- [MIT](#mit)
|
||||
- [Packaging](#packaging)
|
||||
- [README.txt](#readmetxt)
|
||||
- [mod.conf / game.conf](#modconf--gameconf)
|
||||
- [description.txt](#descriptiontxt)
|
||||
- [screenshot.png](#screenshotpng)
|
||||
- [Uploading](#uploading)
|
||||
- [Version Control Systems](#version-control-systems)
|
||||
- [Releasing on ContentDB](#releasing-on-contentdb)
|
||||
- [Forum Attachments](#forum-attachments)
|
||||
- [Forum Topic](#forum-topic)
|
||||
- [Subject](#subject)
|
||||
|
||||
## Choosing a License
|
||||
## License Choices
|
||||
|
||||
You need to specify a license for your mod. This is important because it tells other
|
||||
people the ways in which they are allowed to use your work. If your mod doesn't have
|
||||
@ -42,17 +43,11 @@ able to modify it and release the modified version.)
|
||||
Please note that **public domain is not a valid licence**, because the definition varies
|
||||
in different countries.
|
||||
|
||||
It is important to note that WTFPL is
|
||||
[strongly discouraged](https://content.minetest.net/help/wtfpl/) and people may
|
||||
choose not to use your mod if it has this license.
|
||||
|
||||
### LGPL and CC-BY-SA
|
||||
|
||||
This is a common license combination in the Minetest community, and is what
|
||||
Minetest and Minetest Game use.
|
||||
|
||||
You license your code under LGPL 2.1 and your art under CC-BY-SA.
|
||||
|
||||
This means that:
|
||||
|
||||
* Anyone can modify, redistribute and sell modified or unmodified versions.
|
||||
@ -61,51 +56,55 @@ This means that:
|
||||
|
||||
### CC0
|
||||
|
||||
This license can be used for both code and art, and allows anyone to do what
|
||||
they want with your work. This means they can modify, redistribute, sell, or
|
||||
leave-out attribution.
|
||||
These licenses allow anyone to do what they want with your mod,
|
||||
which means they can modify, redistribute, sell, or leave-out attribution.
|
||||
These licenses can be used for both code and art.
|
||||
|
||||
It is important to note that WTFPL is strongly discouraged and people may
|
||||
choose not to use your mod if it has this license.
|
||||
|
||||
### MIT
|
||||
|
||||
This is a common license for code. The only restriction it places on users
|
||||
of your code is that they must include the same copyright notice and license
|
||||
in any copies of the code or of substantial parts of the code.
|
||||
This is a common license for mod code. The only restriction it places on users
|
||||
of your mod is that they must include the same copyright notice and license
|
||||
in any copies of the mod or of substantial parts of the mod.
|
||||
|
||||
## Packaging
|
||||
|
||||
There are some files that are recommended to include in your mod or game
|
||||
There are some files that are recommended to include in your mod
|
||||
before you release it.
|
||||
|
||||
### README.txt
|
||||
|
||||
The README file should state:
|
||||
|
||||
* What the mod/game does, how to use it.
|
||||
* What the mod does.
|
||||
* What the license is.
|
||||
* Optionally:
|
||||
* where to report problems or get help.
|
||||
* credits
|
||||
* What dependencies there are.
|
||||
* How to install the mod.
|
||||
* Current version of the mod.
|
||||
* Optionally, the where to report problems or get help.
|
||||
|
||||
### mod.conf / game.conf
|
||||
### description.txt
|
||||
|
||||
Make sure you add a description key to explain what your mod or game does. Be
|
||||
concise without being vague. It should be short because it will be displayed in
|
||||
the content installer which has limited space.
|
||||
This should explain what your mod does. Be concise without being vague.
|
||||
It should be short because it will be displayed in the content installer which has
|
||||
limited space.
|
||||
|
||||
Good example:
|
||||
|
||||
description = Adds soup, cakes, bakes and juices.
|
||||
Adds soup, cakes, bakes and juices.
|
||||
|
||||
Avoid this:
|
||||
|
||||
description = The food mod for Minetest. (<-- BAD! It's vague)
|
||||
(BAD) The food mod for Minetest.
|
||||
|
||||
### screenshot.png
|
||||
|
||||
Screenshots should be 3:2 (3 pixels of width for every 2 pixels of height)
|
||||
and have a minimum size of 300 x 200px.
|
||||
|
||||
The screenshot is displayed inside of Minetest as a thumbnail for the content.
|
||||
The screenshot is displayed in the mod store.
|
||||
|
||||
## Uploading
|
||||
|
||||
@ -115,39 +114,54 @@ approach that works best for you, as long as it meets these requirements, and an
|
||||
others which may be added by forum moderators:
|
||||
|
||||
* **Stable** - The hosting website should be unlikely to shut down without warning.
|
||||
* **Direct link** - You should be able to click a link and download the file
|
||||
* **Direct link** - You should be able to click a link on the forum and download the file
|
||||
without having to view another page.
|
||||
* **Virus Free** - Scammy upload hosts may contain insecure adverts.
|
||||
|
||||
ContentDB allows you to upload zip files, and meets these criteria.
|
||||
* **Virus Free** - Mods with malicious content will be removed from the forum.
|
||||
|
||||
### Version Control Systems
|
||||
|
||||
A Version Control System (VCS) is software that manages changes to software,
|
||||
often making it easier to distribute and receive contributed changes.
|
||||
It is recommended that you use a version control system which:
|
||||
|
||||
The majority of Minetest modders use Git and a website like GitHub to distribute
|
||||
their code.
|
||||
* Allows other developers to easily submit changes.
|
||||
* Allows the code to be previewed before downloading.
|
||||
* Allows users to submit bug reports.
|
||||
|
||||
Using git can be difficult at first. If you need help with this please see:
|
||||
The majority of Minetest modders use GitHub as a website to host their code,
|
||||
but alternatives are possible.
|
||||
|
||||
Using a GitHub can be difficult at first. If you need help with this, for
|
||||
information on using GitHub, please see:
|
||||
|
||||
* [Pro Git book](http://git-scm.com/book/en/v1/Getting-Started) - Free to read online.
|
||||
* [GitHub for Windows app](https://help.github.com/articles/getting-started-with-github-for-windows/) -
|
||||
Using a graphical interface on Windows to upload your code.
|
||||
|
||||
## Releasing on ContentDB
|
||||
### Forum Attachments
|
||||
|
||||
ContentDB is the official place to find and distribute content such as mods,
|
||||
games, and texture packs. Users can find content using the website, or download
|
||||
and install using the integration built into the Minetest main menu.
|
||||
As an alternative to using a version management system, you can use forum attachments to share
|
||||
your mods. This can be done when creating a mod's forum topic (covered below).
|
||||
|
||||
Sign up to [ContentDB](https://content.minetest.net) and add your content.
|
||||
Make sure to read the guidance given in the Help section.
|
||||
You need to zip the files for the mod into a single file. How to do this varies from
|
||||
operating system to operating system.
|
||||
This is nearly always done using the right click menu after selecting all files.
|
||||
|
||||
When making a forum topic, on the "Create a Topic" page (see below), go to the
|
||||
"Upload Attachment" tab at the bottom.
|
||||
Click "Browse" and select the zipped file. It is recommended that you
|
||||
enter the version of your mod in the comment field.
|
||||
|
||||
<figure>
|
||||
<img src="{{ page.root }}/static/releasing_attachments.png" alt="Upload Attachment">
|
||||
<figcaption>
|
||||
Upload Attachment tab.
|
||||
</figcaption>
|
||||
</figure>
|
||||
|
||||
## Forum Topic
|
||||
|
||||
You can also create a forum topic to let users discuss your creation.
|
||||
|
||||
Mod topics should be created in ["WIP Mods"](https://forum.minetest.net/viewforum.php?f=9) (Work In Progress)
|
||||
forum, and Game topics in the ["WIP Games"](https://forum.minetest.net/viewforum.php?f=50) forum.
|
||||
You can now create a forum topic. You should create it in
|
||||
the ["WIP Mods"](https://forum.minetest.net/viewforum.php?f=9) (Work In Progress)
|
||||
forum.\\
|
||||
When you no longer consider your mod a work in progress, you can
|
||||
[request that it be moved](https://forum.minetest.net/viewtopic.php?f=11&t=10418)
|
||||
to "Mod Releases."
|
||||
@ -156,6 +170,40 @@ The forum topic should contain similar content to the README, but should
|
||||
be more promotional and also include a link to download the mod.
|
||||
It's a good idea to include screenshots of your mod in action, if possible.
|
||||
|
||||
The Minetest forum uses bbcode for formatting. Here is an example for a
|
||||
mod named superspecial:
|
||||
|
||||
|
||||
Adds magic, rainbows and other special things.
|
||||
|
||||
See download attached.
|
||||
|
||||
[b]Version:[/b] 1.1
|
||||
[b]License:[/b] LGPL 2.1 or later
|
||||
|
||||
Dependencies: default mod (found in minetest_game)
|
||||
|
||||
Report bugs or request help on the forum topic.
|
||||
|
||||
[h]Installation[/h]
|
||||
|
||||
Unzip the archive, rename the folder to superspecial and
|
||||
place it in minetest/mods/
|
||||
|
||||
( GNU/Linux: If you use a system-wide installation place
|
||||
it in ~/.minetest/mods/. )
|
||||
|
||||
( If you only want this to be used in a single world, place
|
||||
the folder in worldmods/ in your world directory. )
|
||||
|
||||
For further information or help see:
|
||||
[url]https://wiki.minetest.net/Installing_Mods[/url]
|
||||
|
||||
If you modify the above example for your mod topic, remember to
|
||||
change "superspecial" to the name of your mod.
|
||||
|
||||
### Subject
|
||||
|
||||
The subject of topic must be in one of these formats:
|
||||
|
||||
* [Mod] Mod Title [modname]
|
||||
|
@ -40,7 +40,7 @@ Any users can submit almost any formspec with any values at any time.
|
||||
Here's some real code found in a mod:
|
||||
|
||||
```lua
|
||||
core.register_on_player_receive_fields(function(player,
|
||||
minetest.register_on_player_receive_fields(function(player,
|
||||
formname, fields)
|
||||
for key, field in pairs(fields) do
|
||||
local x,y,z = string.match(key,
|
||||
@ -87,7 +87,7 @@ to the full Lua API.
|
||||
Can you spot the vulnerability in the following?
|
||||
|
||||
```lua
|
||||
local ie = core.request_insecure_environment()
|
||||
local ie = minetest.request_insecure_environment()
|
||||
ie.os.execute(("path/to/prog %d"):format(3))
|
||||
```
|
||||
|
||||
|
@ -1,198 +0,0 @@
|
||||
---
|
||||
title: Translation (i18n / l10n)
|
||||
layout: default
|
||||
root: ../..
|
||||
idx: 8.05
|
||||
marked_text_encoding:
|
||||
level: info
|
||||
title: Marked Text Encoding
|
||||
message: |
|
||||
You don't need to know the exact format of marked text, but it might help
|
||||
you understand.
|
||||
|
||||
```
|
||||
"\27(T@mymod)Hello everyone!\27E"
|
||||
```
|
||||
|
||||
* `\27` is the escape character - it's used to tell Minetest to pay attention as
|
||||
something special is coming up. This is used for both translations and text
|
||||
colorisation.
|
||||
* `(T@mymod)` says that the following text is translatable using the `mymod`
|
||||
textdomain.
|
||||
* `Hello everyone!` is the translatable text in English, as passed to the
|
||||
translator function.
|
||||
* `\27E` is the escape character again and `E`, used to signal that the end has
|
||||
been reached.
|
||||
---
|
||||
|
||||
## Introduction <!-- omit in toc -->
|
||||
|
||||
Adding support for translation to your mods and games allows more people to
|
||||
enjoy them. According to Google Play, 64% of Minetest Android users don't have
|
||||
English as their primary language. Minetest doesn't track stats for user
|
||||
languages across all platforms, but there's likely to be a high proportion of
|
||||
non-English speaking users.
|
||||
|
||||
Minetest allows you to translate your mods and games into different languages by
|
||||
writing your text in English, and using translation files to map into other
|
||||
languages. Translation is done on each player's client, allowing each player to
|
||||
see a different language.
|
||||
|
||||
|
||||
- [How does client-side translation work?](#how-does-client-side-translation-work)
|
||||
- [Marked up text](#marked-up-text)
|
||||
- [Translation files](#translation-files)
|
||||
- [Format strings](#format-strings)
|
||||
- [Best practices and Common Falsehoods about Translation](#best-practices-and-common-falsehoods-about-translation)
|
||||
- [Server-side translations](#server-side-translations)
|
||||
- [Conclusion](#conclusion)
|
||||
|
||||
|
||||
## How does client-side translation work?
|
||||
|
||||
### Marked up text
|
||||
|
||||
The server needs to tell clients how to translate text. This is done by placing
|
||||
control characters in text, telling Minetest where and how to translate
|
||||
text. This is referred to as marked up text, and will be discussed more later.
|
||||
|
||||
To mark text as translatable, use a translator function (`S()`), obtained using
|
||||
`core.get_translator(textdomain)`:
|
||||
|
||||
```lua
|
||||
local S = core.get_translator("mymod")
|
||||
|
||||
core.register_craftitem("mymod:item", {
|
||||
description = S("My Item"),
|
||||
})
|
||||
```
|
||||
|
||||
The first argument of `get_translator` is the `textdomain`, which acts as a
|
||||
namespace. Rather than having all translations for a language stored in the same
|
||||
file, translations are separated into textdomains, with a file per textdomain
|
||||
per language. The textdomain should be the same as the mod name, as it helps
|
||||
avoid mod conflicts.
|
||||
|
||||
Marked up text can be used in most places where human-readable text is accepted,
|
||||
including formspecs, item def fields, infotext, and more. When including marked
|
||||
text in formspecs, you need to escape the text using `core.formspec_escape`.
|
||||
|
||||
When the client encounters translatable text, such as that passed to
|
||||
`description`, it looks it up in the player's language's translation file. If a
|
||||
translation cannot be found, it falls back to the English translation.
|
||||
|
||||
Translatable marked up text contains the English source text, the textdomain,
|
||||
and any additional arguments passed to `S()`. It's essentially a text encoding
|
||||
of the `S` call, containing all the required information.
|
||||
|
||||
Another type of marked up text is that returned by `core.colorize`.
|
||||
|
||||
{% include notice.html notice=page.marked_text_encoding %}
|
||||
|
||||
|
||||
### Translation files
|
||||
|
||||
Translation files are media files that can be found in the `locale` folder for
|
||||
each mod. Currently, the only supported format is `.tr`, but support for more
|
||||
common formats is likely in the future. Translation files must be named
|
||||
in the following way: `[textdomain].[lang].tr`.
|
||||
|
||||
Files in the `.tr` start with a comment specifying the textdomain, and then
|
||||
further lines mapping from the English source text to the translation.
|
||||
|
||||
For example, `mymod.fr.tr`:
|
||||
|
||||
```
|
||||
# textdomain: mymod
|
||||
Hello everyone!=Bonjour à tous !
|
||||
I like grapefruit=J'aime le pamplemousse
|
||||
```
|
||||
|
||||
You should create translation files based on your mod/game's source code,
|
||||
using a tool like
|
||||
[update_translations](https://github.com/minetest-tools/update_translations).
|
||||
This tool will look for `S(` in your Lua code, and automatically create a
|
||||
template that translators can use to translate into their language.
|
||||
It also handles updating the translation files when your source changes.
|
||||
|
||||
You should always put literal text (`"`) inside S rather than using a variable,
|
||||
as it helps tools find translations.
|
||||
|
||||
|
||||
## Format strings
|
||||
|
||||
It's common to need to include variable information within a translation
|
||||
string. It's important that text isn't just concatenated, as that prevents
|
||||
translators from changing the order of variables within a sentence. Instead,
|
||||
you should use the translation system's format/arguments system:
|
||||
|
||||
```lua
|
||||
core.register_on_joinplayer(function(player)
|
||||
core.chat_send_all(S("Everyone, say hi to @1!", player:get_player_name()))
|
||||
end)
|
||||
```
|
||||
|
||||
If you want to include a literal `@` in your translation, you'll need to escape
|
||||
by writing `@@`.
|
||||
|
||||
You should avoid concatenation *within* a sentence, but it's recommended that
|
||||
you join multiple sentences using concatenation. This helps translators by
|
||||
keeping strings smaller.
|
||||
|
||||
```lua
|
||||
S("Hello @1!", player_name) .. " " .. S("You have @1 new messages.", #msgs)
|
||||
```
|
||||
|
||||
|
||||
## Best practices and Common Falsehoods about Translation
|
||||
|
||||
* Avoid concatenating text and use format arguments instead. This gives
|
||||
translators full control over changing the order of things.
|
||||
* Create translation files automatically, using
|
||||
[update_translations](https://github.com/minetest-tools/update_translations).
|
||||
* It's common for variables to change the surrounding text, for example, with
|
||||
gender and pluralisation. This is often hard to deal with, so is
|
||||
frequently glossed over or worked around with gender neutral phrasings.
|
||||
* Translations may be much longer or much smaller than the English text. Make
|
||||
sure to leave plenty of space.
|
||||
* Other languages may write numbers in a different way, for example, with commas
|
||||
as decimal points. `1.000,23`, `1'000'000,32`
|
||||
* Don't assume that other languages use capitalisation in the same way.
|
||||
|
||||
|
||||
## Server-side translations
|
||||
|
||||
Sometimes you need to know the translation of text on the server, for example,
|
||||
to sort or search text. You can use `get_player_information` to get a player's
|
||||
language and `get_translated_string` to translate marked text.
|
||||
|
||||
```lua
|
||||
local list = {
|
||||
S("Hello world!"),
|
||||
S("Potato")
|
||||
}
|
||||
|
||||
core.register_chatcommand("find", {
|
||||
func = function(name, param)
|
||||
local info = core.get_player_information(name)
|
||||
local language = info and info.language or "en"
|
||||
|
||||
for _, line in ipairs(list) do
|
||||
local trans = core.get_translated_string(language, line)
|
||||
if trans:contains(query) then
|
||||
return line
|
||||
end
|
||||
end
|
||||
end,
|
||||
})
|
||||
```
|
||||
|
||||
## Conclusion
|
||||
|
||||
The translation API allows making mods and games more accessible, but care is
|
||||
needed in order to use it correctly.
|
||||
|
||||
Minetest is continuously improving, and the translation API is likely to be
|
||||
extended in the future. For example, support for gettext translation files will
|
||||
allow common translator tools and platforms (like weblate) to be used, and
|
||||
there's likely to be support for pluralisation and gender added.
|
@ -17,8 +17,9 @@ we discussed how to structure your code avoid this.
|
||||
- [Your First Test](#your-first-test)
|
||||
- [init.lua](#initlua)
|
||||
- [api.lua](#apilua)
|
||||
- [tests/api_spec.lua](#testsapi_speclua)
|
||||
- [tests/api_spec.lua](#testsapispeclua)
|
||||
- [Mocking: Using External Functions](#mocking-using-external-functions)
|
||||
- [Checking Commits with Travis](#checking-commits-with-travis)
|
||||
- [Conclusion](#conclusion)
|
||||
|
||||
## Installing Busted
|
||||
@ -54,7 +55,7 @@ names ending in `_spec`, and then executes them in a standalone Lua environment.
|
||||
```lua
|
||||
mymod = {}
|
||||
|
||||
dofile(core.get_modpath("mymod") .. "/api.lua")
|
||||
dofile(minetest.get_modpath("mymod") .. "/api.lua")
|
||||
```
|
||||
|
||||
|
||||
@ -122,7 +123,7 @@ _G.minetest = {}
|
||||
|
||||
-- Define the mock function
|
||||
local chat_send_all_calls = {}
|
||||
function core.chat_send_all(name, message)
|
||||
function minetest.chat_send_all(name, message)
|
||||
table.insert(chat_send_all_calls, { name = name, message = message })
|
||||
end
|
||||
|
||||
@ -163,6 +164,28 @@ end)
|
||||
```
|
||||
|
||||
|
||||
## Checking Commits with Travis
|
||||
|
||||
The Travis script from the [Automatic Error Checking](luacheck.html)
|
||||
chapter can be modified to also run Busted.
|
||||
|
||||
```yml
|
||||
language: generic
|
||||
sudo: false
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- luarocks
|
||||
before_install:
|
||||
- luarocks install --local luacheck && luarocks install --local busted
|
||||
script:
|
||||
- $HOME/.luarocks/bin/luacheck .
|
||||
- $HOME/.luarocks/bin/busted .
|
||||
notifications:
|
||||
email: false
|
||||
```
|
||||
|
||||
|
||||
## Conclusion
|
||||
|
||||
Unit tests will greatly increase the quality and reliability of your project if used
|
||||
|
@ -14,7 +14,7 @@ Questo capitolo mostra come registrare nuovi biomi, come controllarne la distrib
|
||||
|
||||
- [Cosa sono i biomi?](#cosa-sono-i-biomi)
|
||||
- [Collocare un bioma](#collocare-un-bioma)
|
||||
- [Calore e umidità](#calore-e-umidità)
|
||||
- [Calore e umidità](#calore-e-umidita)
|
||||
- [Visualizzare i confini usando i diagrammi di Voronoi](#visualizzare-i-confini-usando-i-diagrammi-di-voronoi)
|
||||
- [Creare un diagramma di Voronoi usando Geogebra](#creare-un-diagramma-di-voronoi-usando-geogebra)
|
||||
- [Registrare un bioma](#registrare-un-bioma)
|
||||
@ -107,7 +107,7 @@ Oltre che farli a mano, per creare dei diagrammi di Voronoi si possono usare pro
|
||||
Il seguente codice registra un semplice bioma chiamato "distesa_erbosa":
|
||||
|
||||
```lua
|
||||
core.register_biome({
|
||||
minetest.register_biome({
|
||||
name = "distesa_erbosa",
|
||||
node_top = "default:dirt_with_grass",
|
||||
depth_top = 1,
|
||||
@ -144,7 +144,7 @@ Ricordati che devi specificare il nodo che vuoi usare in quanto decorazione, i d
|
||||
Per esempio:
|
||||
|
||||
```lua
|
||||
core.register_decoration({
|
||||
minetest.register_decoration({
|
||||
deco_type = "simple",
|
||||
place_on = {"base:dirt_with_grass"},
|
||||
sidelen = 16,
|
||||
@ -167,7 +167,7 @@ Le schematic sono molto simili alle decorazioni semplici, solo che piazzano più
|
||||
Per esempio:
|
||||
|
||||
```lua
|
||||
core.register_decoration({
|
||||
minetest.register_decoration({
|
||||
deco_type = "schematic",
|
||||
place_on = {"base:desert_sand"},
|
||||
sidelen = 16,
|
||||
@ -175,7 +175,7 @@ core.register_decoration({
|
||||
biomes = {"desert"},
|
||||
y_max = 200,
|
||||
y_min = 1,
|
||||
schematic = core.get_modpath("plants") .. "/schematics/cactus.mts",
|
||||
schematic = minetest.get_modpath("plants") .. "/schematics/cactus.mts",
|
||||
flags = "place_center_x, place_center_z",
|
||||
rotation = "random",
|
||||
})
|
||||
@ -195,7 +195,7 @@ I giochi disponibili dovrebbero già includere un alias del generatore mappa (*m
|
||||
Gli alias del generatore mappa forniscono informazioni al generatore principale, e possono essere registrati secondo lo schema:
|
||||
|
||||
```lua
|
||||
core.register_alias("mapgen_stone", "base:smoke_stone")
|
||||
minetest.register_alias("mapgen_stone", "base:smoke_stone")
|
||||
```
|
||||
|
||||
Almeno almeno dovresti registrare:
|
||||
|
@ -10,13 +10,13 @@ redirect_from:
|
||||
mapgen_object:
|
||||
level: warning
|
||||
title: LVM e generatore mappa
|
||||
message: Non usare `core.get_voxel_manip()` con il generatore mappa, in quanto può causare glitch.
|
||||
Usa invece `core.get_mapgen_object("voxelmanip")`.
|
||||
message: Non usare `minetest.get_voxel_manip()` con il generatore mappa, in quanto può causare glitch.
|
||||
Usa invece `minetest.get_mapgen_object("voxelmanip")`.
|
||||
---
|
||||
|
||||
## Introduzione <!-- omit in toc -->
|
||||
|
||||
Le funzioni introdotte nel capitolo [Mappa: operazioni base](../map/environment.html) sono comode e facili da usare, ma per le grandi aree non sono efficienti.
|
||||
Le funzioni introdotte nel capitolo [Mappa: operazioni base](environment.html) sono comode e facili da usare, ma per le grandi aree non sono efficienti.
|
||||
Ogni volta che `set_node` e `get_node` vengono chiamati da una mod, la mod deve comunicare con il motore di gioco.
|
||||
Ciò risulta in una costante copia individuale dei singoli nodi, che è lenta e abbasserà notevolmente le performance del gioco.
|
||||
Usare un Manipolatore di Voxel Lua (*Lua Voxel Manipulator*, da qui LVM) può essere un'alternativa migliore.
|
||||
@ -39,7 +39,7 @@ Si possono caricare solamente aree cubiche negli LVM, quindi devi capire da te q
|
||||
Fatto ciò, puoi creare l'LVM:
|
||||
|
||||
```lua
|
||||
local vm = core.get_voxel_manip()
|
||||
local vm = minetest.get_voxel_manip()
|
||||
local emin, emax = vm:read_from_map(pos1, pos2)
|
||||
```
|
||||
|
||||
@ -82,7 +82,7 @@ Per scoprire qual è l'ID assegnato a un tipo di nodo, si usa `get_content_id()`
|
||||
Per esempio:
|
||||
|
||||
```lua
|
||||
local c_pietra = core.get_content_id("default:stone")
|
||||
local c_pietra = minetest.get_content_id("default:stone")
|
||||
```
|
||||
|
||||
Si può ora controllare se un nodo è effettivamente di pietra:
|
||||
@ -94,7 +94,7 @@ if data[idx] == c_pietra then
|
||||
end
|
||||
```
|
||||
|
||||
Gli ID di contenuto di un nodo potrebbero cambiare durante la fase di caricamento, quindi è consigliato non tentare di ottenerli durante tale fase.
|
||||
Si consiglia di ottenere e salvare (in una variabile locale) gli ID di contenuto al caricare della mod in quanto questi non possono cambiare.
|
||||
|
||||
Le coordinate dei nodi nell'array di un LVM sono salvate in ordine inverso (`z, y, x`), quindi se le si vuole iterare, si tenga presente che si inizierà dalla Z:
|
||||
|
||||
@ -143,16 +143,18 @@ vm:write_to_map(true)
|
||||
Per la luce e param2, invece si usano `set_light_data()` e `set_param2_data()`.
|
||||
|
||||
`write_to_map()` richiede un booleano che è `true` se si vuole che venga calcolata anche la luce.
|
||||
Se si passa `false` invece, ci sarà bisogno di ricalcolarla in un secondo tempo usando `core.fix_light`.
|
||||
Se si passa `false` invece, ci sarà bisogno di ricalcolarla in un secondo tempo usando `minetest.fix_light`.
|
||||
|
||||
## Esempio
|
||||
|
||||
```lua
|
||||
-- ottiene l'ID di contenuto al caricare della mod, salvandolo in variabili locali
|
||||
local c_terra = minetest.get_content_id("default:dirt")
|
||||
local c_erba = minetest.get_content_id("default:dirt_with_grass")
|
||||
|
||||
local function da_erba_a_terra(pos1, pos2)
|
||||
local c_terra = core.get_content_id("default:dirt")
|
||||
local c_erba = core.get_content_id("default:dirt_with_grass")
|
||||
-- legge i dati nella LVM
|
||||
local vm = core.get_voxel_manip()
|
||||
local vm = minetest.get_voxel_manip()
|
||||
local emin, emax = vm:read_from_map(pos1, pos2)
|
||||
local a = VoxelArea:new{
|
||||
MinEdge = emin,
|
||||
|
@ -11,64 +11,80 @@ redirect_from:
|
||||
|
||||
## Introduzione <!-- omit in toc -->
|
||||
|
||||
Capire la struttura base della cartella di una mod è un requisito essenziale per creare qualsivoglia contenuto.
|
||||
Capire la struttura base della cartella di una mod è un requisito essenziale per creare mod.
|
||||
|
||||
- [Cosa sono i giochi e le mod?](#cosa-sono-i-giochi-e-le-mod)
|
||||
- [Dove vengono salvate le mod?](#dove-vengono-salvate-le-mod)
|
||||
- [Cartella mod](#cartella-mod)
|
||||
- [mod.conf](#modconf)
|
||||
- [Dipendenze](#dipendenze)
|
||||
- [mod.conf](#modconf)
|
||||
- [depends.txt](#dependstxt)
|
||||
- [Pacchetti mod](#pacchetti-mod-mod-pack)
|
||||
- [Esempio](#esempio)
|
||||
- [Cartella mod](#cartella-mod-1)
|
||||
- [depends.txt](#dependstxt-1)
|
||||
- [init.lua](#initlua)
|
||||
- [mod.conf](#modconf-1)
|
||||
|
||||
|
||||
## Cosa sono i giochi e le mod?
|
||||
|
||||
Il punto forte di Minetest è l'abilità di sviluppare facilmente giochi senza il bisogno di crearsi da zero il motore grafico, gli algoritmi voxel o tutta la parte di rete.
|
||||
Il punto forte di Minetest è l'abilità di sviluppare facilmente giochi senza il bisogno
|
||||
di crearti da zero il motore grafico, gli algoritmi voxel o tutta la parte network.
|
||||
|
||||
In Minetest, un gioco è un insieme di moduli che lavorano fianco a fianco per fornire il contenuto e il comportamento di un gioco.
|
||||
Un modulo, solitamente conosciuto come "mod", è una collezione di script e risorse, e in teoria ne potrebbe bastare uno per creare un intero gioco.
|
||||
Tuttavia, questo non accade spesso, perché ridurrebbe la comodità di poter sostituire o calibrare alcune parti in maniera indipendente dalle altre.
|
||||
In Minetest, un gioco è una collezione di moduli che lavorano insieme per fornire il contenuto
|
||||
e il comportamento di un gioco.
|
||||
Un modulo, solitamente conosciuto come "mod" (femminile), è una collezione di script e risorse.
|
||||
È possibile creare un gioco usando semplicemente una mod, ma questo non accade spesso perché
|
||||
riduce la comodità di poter sostituire o calibrare alcune parti del gioco in maniera indipendente
|
||||
dalle altre.
|
||||
|
||||
È poi anche possibile distribuire singolarmente le varie mod, che diventano mod nel senso più tradizionale del termine: modifiche, per calibrano o espandere le proprietà di un gioco.
|
||||
È anche possibile distribuire le mod al di fuori di un gioco, nel qual caso sono sempre mod
|
||||
nel senso più tradizionale del termine: modifiche. Queste mod calibrano o espandono le proprietà
|
||||
di un gioco.
|
||||
|
||||
Indipendentemente da come le si voglia usare (specifiche per un gioco o come estensioni generiche) usano la stessa API.
|
||||
Sia le mod presenti in un gioco che quelle a sé stanti usano la stessa API.
|
||||
|
||||
Questo libro coprirà le parti principali dell'API di Minetest, ed è pensato sia per chi sviluppa il motore di gioco (Minetest, in C++) che per chi crea mod.
|
||||
Questo libro coprirà le parti principali della API di Minetest,
|
||||
ed è applicabile sia per gli sviluppatori che per i creatori di mod.
|
||||
|
||||
|
||||
## Dove vengono salvate le mod?
|
||||
|
||||
<a name="mod-locations"></a>
|
||||
|
||||
Ogni mod ha la sua cartella personale dove viene messo il suo codice in Lua, le sue texture, i suoi modelli e i suoi file audio.
|
||||
Minetest esegue controlli in più posti e questi posti sono generalmente chiamati *percorsi di caricamento mod* (*mod load paths*).
|
||||
Ogni mod ha la sua cartella personale dove viene messo il suo codice in Lua, le sue texture,
|
||||
i suoi modelli e i suoi file audio. Minetest fa un check in più punti per le mod. Questi punti
|
||||
sono generalmente chiamati *percorsi di caricamento mod* (in inglese *mod load paths*).
|
||||
|
||||
Per un dato mondo/salvataggio, vengono controllati tre percorsi.
|
||||
Per un dato mondo/salvataggio, vengono controllati tre punti.
|
||||
Essi sono, in ordine:
|
||||
|
||||
1. Mod di gioco. Queste sono le mod che compongono il gioco che il mondo sta eseguendo.
|
||||
Es: `minetest/games/minetest_game/mods/`, `/usr/share/minetest/games/minetest/`
|
||||
2. Mod globali. Il luogo dove le mod vengono quasi sempre installate. Se si è in dubbio, le si metta qui.
|
||||
2. Mod globali. Il luogo dove le mod vengono quasi sempre installate. Se si è in dubbio,
|
||||
le si metta qui.
|
||||
Es: `minetest/mods/`
|
||||
3. Mod del mondo. Il luogo dove mettere le mod che sono specifiche di un dato mondo.
|
||||
Es: `minetest/worlds/world/worldmods/`
|
||||
|
||||
Minetest controllerà questi percorsi nell'ordine sopraelencato.
|
||||
In caso dovesse incontrare una mod con lo stesso nome di una incontrata in precedenza, l'ultima verrebbe caricata al posto della prima.
|
||||
Ciò significa, per esempio, che è possibile sovrascriverne una di gioco se ve n'è una omonima nelle globali.
|
||||
Minetest controllerà questi punti nell'ordine sopraelencato. Se incontra una mod con lo
|
||||
stesso nome di una incontrata in precedenza, l'ultima verrà caricata al posto della prima.
|
||||
Questo significa che si può sovrascrivere le mod di gioco piazzando una mod con lo stesso
|
||||
nome nella cartella delle mod globali.
|
||||
|
||||
La posizione di ogni percorso dipende da quale sistema operativo si sta usando, e da come è stato installato Minetest.
|
||||
La posizione di ogni percorso di caricamento mod dipende da quale sistema operativo si sta
|
||||
usando, e come è stato installato Minetest.
|
||||
|
||||
* **Windows:**
|
||||
* Per le versioni portatili, per esempio da un file .zip, vai dove hai estratto lo zip e cerca le cartelle `games`, `mods` e `worlds`.
|
||||
* Per le versioni installate, per esempio da un setup.exe, guarda in C:\\\\Minetest o C:\\\\Games\\Minetest.
|
||||
* Per le build portatili, per esempio da un file .zip, vai dove hai estratto lo zip e
|
||||
cerca le cartelle `games`, `mods` e `worlds`.
|
||||
* Per le build installate, per esempio da un setup.exe,
|
||||
guarda in C:\\\\Minetest o C:\\\\Games\\Minetest.
|
||||
* **GNU/Linux:**
|
||||
* Per le installazioni di sistema, guarda in `~/.minetest`.
|
||||
Attenzione che `~` equivale alla cartella home dell'utente, e che i file e le cartelle che iniziano con un punto (`.`) sono nascosti di default.
|
||||
Attenzione che `~` equivale alla cartella home dell'utente, e che i file e le cartelle
|
||||
che iniziano con un punto (`.`) sono nascosti di default.
|
||||
* Per le installazioni portatili, guarda nella cartella di build.
|
||||
* Per installazioni Flatpak, guarda in `~/.var/app/net.minetest.Minetest/.minetest/mods/`.
|
||||
* **MacOS**
|
||||
@ -79,13 +95,15 @@ La posizione di ogni percorso dipende da quale sistema operativo si sta usando,
|
||||
|
||||
![Find the mod's directory]({{ page.root }}/static/folder_modfolder.jpg)
|
||||
|
||||
Il *nome mod* è usato per riferirsi a una mod e ognuna di esse dovrebbe averne uno unico.
|
||||
Questi possono includere lettere, numeri e trattini bassi, e un buon nome dovrebbe descrivere brevemente cosa fa la mod (è anche consigliato rinominare la cartella della mod con il nome di quest'ultima).
|
||||
Per scoprire se un nome è disponibile, prova a cercarlo su
|
||||
Il *nome mod* è usato per riferirsi a una mod. Ogni mod dovrebbe avere un nome unico.
|
||||
I nomi mod possono includere lettere, numeri e trattini bassi. Un buon nome dovrebbe
|
||||
descrivere cosa fa la mod, e la cartella che contiene i componenti di una mod deve avere
|
||||
lo stesso nome del nome mod.
|
||||
Per scoprire se un nome mod è disponibile, prova a cercarlo su
|
||||
[content.minetest.net](https://content.minetest.net).
|
||||
|
||||
|
||||
lamiamod
|
||||
mymod
|
||||
├── init.lua (necessario) - Viene eseguito al lancio del gioco.
|
||||
├── mod.conf (consigliato) - Contiene la descrizione e le dipendneze.
|
||||
├── textures (opzionale)
|
||||
@ -94,37 +112,53 @@ Per scoprire se un nome è disponibile, prova a cercarlo su
|
||||
│ └── ... qualsiasi file audio
|
||||
└── ... qualsiasi altro tipo di file o cartelle
|
||||
|
||||
Solo il file init.lua è necessario in una mod per eseguirla quando si avvia un gioco;
|
||||
tuttavia è consigliato anche mod.conf, e altri componenti potrebbero essere richiesti a
|
||||
seconda di quello che si vuole fare.
|
||||
Solo il file init.lua è necessario in una mod per eseguirla al lanciare un gioco;
|
||||
tuttavia, mod.conf è consigliato e altri componenti potrebbero essere richiesti a
|
||||
seconda della funzione della mod.
|
||||
|
||||
## mod.conf
|
||||
## Dipendenze
|
||||
|
||||
Questo file è utilizzato per i metadati della mod, che includono il suo nome, la descrizione e altre informazioni.
|
||||
|
||||
Per esempio:
|
||||
|
||||
name = lamiamod
|
||||
description = Aggiunge X, Y, e Z
|
||||
depends = mod1, mod2
|
||||
|
||||
### Dipendenze
|
||||
|
||||
Una dipendenza è quando (all'avvio) una o più mod vengono richieste da un'altra mod.
|
||||
I motivi sono vari: potrebbe per esempio aver bisogno di parti del loro codice, degli oggetti, o in generale di risorse che queste forniscono.
|
||||
Una dipendenza avviene quando una mod richiede che un'altra mod sia avviata prima di essa.
|
||||
Una mod potrebbe infatti richiedere il codice di quest'ultima, i suoi oggetti o altre risorse.
|
||||
|
||||
Ci sono due tipi di dipendenze: forti e opzionali.
|
||||
Entrambe richiedono che la mod richiesta venga caricata prima, con la differenza che se la dipendenza è forte e la mod non viene trovata, l'altra non verrà caricata, mentre se è opzionale, verranno semplicemente caricate meno funzionalità.
|
||||
Entrambe richiedono che la mod richiesta venga caricata prima, con la differenza che se la
|
||||
dipendenza è forte e la mod non viene trovata, l'altra fallirà nel caricare, mentre se è opzionale,
|
||||
verranno semplicemente caricate meno feature.
|
||||
|
||||
Le dipendenze sono specificate in un elenco separato da virgole in mod.conf.
|
||||
Una dipendenza opzionale è utile se si vuole integrare opzionalmente un'altra mod; può abilitare
|
||||
contenuti extra se l'utente desidera usare entrambe le mod in contemporanea.
|
||||
|
||||
Le dipendenze vanno elencate in mod.conf.
|
||||
|
||||
### mod.conf
|
||||
|
||||
Questo file è utilizzato per i metadati della mod, che includono il suo nome, la descrizione e
|
||||
altre informazioni. Per esempio:
|
||||
|
||||
name = lamiamod
|
||||
description = Aggiunge X, Y, e Z.
|
||||
depends = mod1, mod2
|
||||
optional_depends = mod3
|
||||
|
||||
### depends.txt
|
||||
|
||||
Per questioni di compatibilità con le versioni 0.4.x di Minetest, al posto di specificare le
|
||||
dipendenze solamente in mod.conf, c'è bisogno di fornire un file depends.txt nel quale vanno
|
||||
elencate tutte le dipendenze:
|
||||
|
||||
mod1
|
||||
mod2
|
||||
mod3?
|
||||
|
||||
Ogni nome mod occupa una riga, e i nomi mod seguiti da un punto di domanda indicano una dipendenza
|
||||
opzionale.
|
||||
|
||||
## Pacchetti mod (mod pack)
|
||||
|
||||
Le mod possono essere raggruppate in pacchetti che permettono di confezionarne e spostarne più alla volta.
|
||||
Sono comodi se si vogliono fornire più mod a chi gioca, ma non si vuole al tempo stesso fargliele scaricare una per una.
|
||||
Le mod possono essere raggruppate in pacchetti che permettono a più mod di essere confezionate
|
||||
e spostate insieme. Sono comodi se si vogliono fornire più mod al giocatore, ma non si vuole al
|
||||
tempo stesso fargliele scaricare una per una.
|
||||
|
||||
pacchettomod1
|
||||
├── modpack.lua (necessario) - segnala che è un pacchetto mod
|
||||
@ -133,7 +167,8 @@ Sono comodi se si vogliono fornire più mod a chi gioca, ma non si vuole al temp
|
||||
└── mymod (opzionale)
|
||||
└── ... file mod
|
||||
|
||||
Attenzione che un pacchetto mod non equivale a un *gioco*. I giochi hanno una propria struttura organizzativa che verrà spiegata nel loro apposito capitolo.
|
||||
Attenzione che un pacchetto mod non equivale a un *gioco*.
|
||||
I giochi hanno una propria struttura organizzativa che verrà spiegata nel loro apposito capitolo.
|
||||
|
||||
## Esempio
|
||||
|
||||
@ -143,14 +178,18 @@ Segue un esempio che mette insieme tutto ciò discusso finora:
|
||||
lamiamod
|
||||
├── textures
|
||||
│ └── lamiamod_nodo.png
|
||||
├── depends.txt
|
||||
├── init.lua
|
||||
└── mod.conf
|
||||
|
||||
### depends.txt
|
||||
default
|
||||
|
||||
### init.lua
|
||||
```lua
|
||||
print("Questo file parte all'avvio!")
|
||||
print("Questo file parte al caricamento!")
|
||||
|
||||
core.register_node("lamiamod:nodo", {
|
||||
minetest.register_node("lamiamod:nodo", {
|
||||
description = "Questo è un nodo",
|
||||
tiles = {"lamiamod_nodo.png"},
|
||||
groups = {cracky = 1}
|
||||
@ -162,8 +201,8 @@ core.register_node("lamiamod:nodo", {
|
||||
descriptions = Aggiunge un nodo
|
||||
depends = default
|
||||
|
||||
Questa mod ha come nome "lamiamod". Ha due file di testo: init.lua e mod.conf.\\
|
||||
Questa mod ha il nome "lamiamod". Ha tre file di testo: init.lua, mod.conf e depends.txt.\\
|
||||
Lo script stampa un messaggio e poi registra un nodo – che sarà spiegato nel prossimo capitolo.\\
|
||||
C'è una sola dipendenza, la [mod default](https://content.minetest.net/metapackages/default/), che
|
||||
si trova solitamente in Minetest Game.\\
|
||||
si trova solitamente nel Minetest Game.\\
|
||||
C'è anche una texture in textures/ per il nodo.
|
||||
|
@ -9,40 +9,26 @@ redirect_from: /it/chapters/lua.html
|
||||
|
||||
## Introduzione <!-- omit in toc -->
|
||||
|
||||
In questo capitolo parleremo della programmazione in Lua, degli strumenti necessari, e tratteremo alcune tecniche che troverai probabilmente utili.
|
||||
In questo capitolo parleremo della programmazione in Lua, degli strumenti necessari,
|
||||
e tratteremo alcune tecniche che troverai probabilmente utili.
|
||||
|
||||
- [Programmare](#programmare)
|
||||
- [Programmare in Lua](#programmare-in-lua)
|
||||
- [Editor di codice](#editor-di-codice)
|
||||
- [Programmare in Lua](#programmare-in-lua)
|
||||
- [Flusso del programma](#flusso-del-programma)
|
||||
- [Tipi di variabili](#tipi-di-variabili)
|
||||
- [Operatori matematici](#operatori-matematici)
|
||||
- [Selezione](#selezione)
|
||||
- [Operatori logici](#operatori-logici)
|
||||
- [Programmare](#programmare)
|
||||
- [Portata locale e globale](#portata-locale-e-globale)
|
||||
- [Precedenza alla portata locale](#precedenza-alla-portata-locale)
|
||||
- [Local dovrebbe essere usato il più possibile](#local-dovrebbe-essere-usato-il-piu-possibile)
|
||||
- [Inclusione di altri script Lua](#inclusione-di-altri-script-lua)
|
||||
|
||||
|
||||
## Programmare
|
||||
|
||||
Programmare è l'azione di prendere un problema, come ordinare una lista di oggetti, e tramutarlo in dei passaggi che il computer può comprendere.
|
||||
|
||||
Insegnarti i processi logici della programmazione non rientra nell'ambito di questo libro; tuttavia, i seguenti siti sono alquanto utili per approfondire l'argomento:
|
||||
|
||||
* [Codecademy](http://www.codecademy.com/) è una delle migliori risorse per imparare come scrivere codice; offre un'esperienza guidata interattiva.
|
||||
* [Scratch](https://scratch.mit.edu) è una buona risorsa quando si comincia dalle basi assolute, imparando le tecniche di problem solving necessarie per la programmazione.\\
|
||||
Scratch è *ideato per insegnare ai bambini* e non è un linguaggio serio di programmazione.
|
||||
* [Programming with Mosh](https://www.youtube.com/user/programmingwithmosh) is
|
||||
a good YouTube series to learn programming.
|
||||
|
||||
### Programmare in Lua
|
||||
|
||||
Neanche insegnarti come programmare in lua rientra nell'ambito di questo libro.
|
||||
Tuttavia, se mastichi l'inglese puoi rifarti a quest'altro libro, ["Programming in Lua"](https://www.lua.org/pil/contents.html), per un'eccellente infarinatura sull'argomento. Se invece l'inglese non è il tuo forte, troverai comunque svariate guide in italiano in giro per la rete.
|
||||
|
||||
|
||||
## Editor di codice
|
||||
|
||||
Un editor di codice con evidenziamento delle parole chiave è sufficiente per scrivere script in Lua.
|
||||
L'evidenziamento assegna colori diversi a parole e caratteri diversi, a seconda del loro significato, permettendo quindi di individuare più facilmente eventuali errori e inconsistenze.
|
||||
|
||||
Per esempio:
|
||||
L'evidenziamento assegna colori diversi a parole e caratteri diversi a seconda del loro significato.
|
||||
Questo ti permette di individuare più facilmente eventuali errori.
|
||||
|
||||
```lua
|
||||
function ctf.post(team,msg)
|
||||
@ -60,16 +46,146 @@ function ctf.post(team,msg)
|
||||
end
|
||||
```
|
||||
|
||||
Nel passaggio qui sopra, le parole chiave `if`, `then`, `end` e `return` sono evidenziate.
|
||||
E Lo stesso vale per le funzioni interne di Lua come `table.insert`.
|
||||
Per esempio, parole chiave come if, then, end e return sono evidenziate nel passaggio qui sopra.
|
||||
table.insert è invece una funzione base che deriva direttamente da Lua.
|
||||
|
||||
Tra gli editor più famosi che ben si prestano a lavorare in Lua, troviamo:
|
||||
Segue una lista di editor noti che si prestano bene per programmare in Lua.
|
||||
Non sono, ovviamente, gli unici esisteneti.
|
||||
|
||||
* [VSCode](https://code.visualstudio.com/) - software libero (come Code-OSS e VSCodium), rinomato, e che dispone di [estensioni per il modding su Minetest](https://marketplace.visualstudio.com/items?itemName=GreenXenith.minetest-tools).
|
||||
* [Notepad++](http://notepad-plus-plus.org/) - Solo per Windows
|
||||
* Windows: [Notepad++](http://notepad-plus-plus.org/), [Atom](http://atom.io/), [VS Code](https://code.visualstudio.com/)
|
||||
* Linux: Kate, Gedit, [Atom](http://atom.io/), [VS Code](https://code.visualstudio.com/)
|
||||
* OSX: [Atom](http://atom.io/), [VS Code](https://code.visualstudio.com/)
|
||||
|
||||
(ne esistono ovviamente anche altri)
|
||||
## Programmare in Lua
|
||||
|
||||
### Flusso del programma
|
||||
|
||||
I programmi sono una serie di comandi che vengono eseguiti uno dopo l'altro.
|
||||
Chiamiamo questi comandi "istruzioni".
|
||||
Il flusso del programma è il come queste istruzioni vengono eseguite.
|
||||
Differenti tipi di flusso ti permettono di saltare o meno serie di comandi.
|
||||
Ci sono tre tipi di flusso:
|
||||
|
||||
* Sequenziale: esegue un'istruzione dopo l'altra, senza salti.
|
||||
* Selettivo: salta alcune sequenze a seconda delle condizioni.
|
||||
* Iterante: ripete ciclicamente. Continua a eseguire le stesse istruzioni
|
||||
finché una condizione non è soddisfatta.
|
||||
|
||||
Quindi, come vengono rappresentate le istruzioni in Lua?
|
||||
|
||||
```lua
|
||||
local a = 2 -- Imposta 'a' a 2
|
||||
local b = 2 -- Imposta 'b' a 2
|
||||
local risultato = a + b -- Imposta 'risultato' ad a + b, cioè 4
|
||||
a = a + 10
|
||||
print("La somma è ".. risultato)
|
||||
```
|
||||
|
||||
Whoa, cos'è appena successo?
|
||||
|
||||
a, b, e risultato sono *variabili*. Le variabili locali si dichiarano
|
||||
tramite l'uso della parola chiave local, e assegnando loro un valore iniziale.
|
||||
Local sarà discussa in un attimo, in quanto parte di un concetto molto importante
|
||||
chiamato *portata*.
|
||||
|
||||
Il simbolo `=` significa *assegnazione*, quindi `risultato = a + b` significa impostare "risultato" ad a + b.
|
||||
I nomi delle variabili possono essere più lunghi di un carattere, al contrario che in matematica, come
|
||||
visto nella variabile "risultato".
|
||||
Vale anche la pena notare che Lua è *case-sensitive* (differenzia maiscuole da minuscole);
|
||||
A è una variabile diversa da a.
|
||||
|
||||
### Tipi di variabili
|
||||
|
||||
Una variabile può equivalere solo a uno dei seguenti tipi e può cambiare tipo dopo l'assegnazione.
|
||||
È buona pratica assicurarsi che una variabile sia sempre solo o nil o diversa da nil.
|
||||
|
||||
| Tipo | Descrizione | Esempio |
|
||||
|----------|---------------------------------|----------------|
|
||||
| Nil | Non inizializzata. La variabile è vuota, non ha valore | `local A`, `D = nil` |
|
||||
| Numero | Un numero intero o decimale | `local A = 4` |
|
||||
| Stringa | Una porzione di testo | `local D = "one two three"` |
|
||||
| Booleano | Vero o falso (true, false) | `local is_true = false`, `local E = (1 == 1)` |
|
||||
| Tabella | Liste | Spiegate sotto |
|
||||
| Funzione | Può essere eseguita. Può richiedere input e ritornare un valore | `local result = func(1, 2, 3)` |
|
||||
|
||||
### Operatori matematici
|
||||
|
||||
Lista non esaustiva, ce ne sono altri
|
||||
|
||||
| Simbolo | Scopo | Esempio |
|
||||
|---------|--------------------|---------------------------|
|
||||
| A + B | Addizione | 2 + 2 = 4 |
|
||||
| A - B | Sottrazione | 2 - 10 = -8 |
|
||||
| A * B | Moltiplicazione | 2 * 2 = 4 |
|
||||
| A / B | Divisione | 100 / 50 = 2 |
|
||||
| A ^ B | Potenze | 2 ^ 2 = 2<sup>2</sup> = 4 |
|
||||
| A .. B | Concatena stringhe | "foo" .. "bar" = "foobar" |
|
||||
|
||||
### Selezione
|
||||
|
||||
La selezione più basica è il costrutto if. Si presenta così:
|
||||
|
||||
```lua
|
||||
local random_number = math.random(1, 100) -- Tra 1 e 100.
|
||||
if random_number > 50 then
|
||||
print("Woohoo!")
|
||||
else
|
||||
print("No!")
|
||||
end
|
||||
```
|
||||
|
||||
Questo esempio genera un numero casuale tra 1 e 100. Stampa poi
|
||||
"Woohoo!" se il numero è superiore a 50, altrimenti stampa "No!".
|
||||
Cos'altro puoi usare oltre a '>'?
|
||||
|
||||
### Operatori logici
|
||||
|
||||
| Simbolo | Scopo | Esempio |
|
||||
|---------|--------------------------------------|-------------------------------------------------------------|
|
||||
| A == B | Uguale a | 1 == 1 (true), 1 == 2 (false) |
|
||||
| A ~= B | Non uguale a (diverso da) | 1 ~= 1 (false), 1 ~= 2 (true) |
|
||||
| A > B | Maggiore di | 5 > 2 (true), 1 > 2 (false), 1 > 1 (false) |
|
||||
| A < B | Minore di | 1 < 3 (true), 3 < 1 (false), 1 < 1 (false) |
|
||||
| A >= B | Maggiore o uguale a | 5 >= 5 (true), 5 >= 3 (true), 5 >= 6 (false) |
|
||||
| A <= B | Minore o uguale a | 3 <= 6 (true), 3 <= 3 (true) |
|
||||
| A and B | E (entrambi devono essere veri) | (2 > 1) and (1 == 1) (true), (2 > 3) and (1 == 1) (false) |
|
||||
| A or B | O (almeno uno dei due vero) | (2 > 1) or (1 == 2) (true), (2 > 4) or (1 == 3) (false) |
|
||||
| not A | non vero | not (1 == 2) (true), not (1 == 1) (false) |
|
||||
|
||||
La lista non è esaustiva, e puoi inoltre combinare gli operatori in questo modo:
|
||||
|
||||
```lua
|
||||
if not A and B then
|
||||
print("Yay!")
|
||||
end
|
||||
```
|
||||
|
||||
Che stampa "Yay!" se A è falso e B vero.
|
||||
|
||||
Gli operatori logici e matematici funzionano esattamente allo stesso modo;
|
||||
entrambi accettano input e ritornano un valore che può essere immagazzinato.
|
||||
|
||||
```lua
|
||||
local A = 5
|
||||
local is_equal = (A == 5)
|
||||
if is_equal then
|
||||
print("È equivalente!")
|
||||
end
|
||||
```
|
||||
|
||||
## Programmare
|
||||
|
||||
Programmare è l'azione di prendere un problema, come ordinare una lista di oggetti,
|
||||
e tramutarlo in dei passaggi che il computer può comprendere.
|
||||
|
||||
Insegnarti i processi logici della programmazione non rientra nell'ambito di questo libro;
|
||||
tuttavia, i seguenti siti sono alquanto utili per approfondire l'argomento:
|
||||
|
||||
* [Codecademy](http://www.codecademy.com/) è una delle migliori risorse per
|
||||
imparare a 'programmare'; offre un'esperienza guidata interattiva.
|
||||
* [Scratch](https://scratch.mit.edu) è una buona risorsa quando si comincia
|
||||
dalle basi assolute, imparando le tecniche di problem solving necessarie per programmare.\\
|
||||
Scratch è **ideato per insegnare ai bambini** e non è un linguaggio serio di programmazione.
|
||||
|
||||
## Portata locale e globale
|
||||
|
||||
@ -93,6 +209,24 @@ end
|
||||
|
||||
Mentre le variabili globali sono accessibili da qualsiasi script di qualsiasi mod.
|
||||
|
||||
```lua
|
||||
my_global_variable = "ciao"
|
||||
|
||||
function one()
|
||||
my_global_variable = "hey"
|
||||
end
|
||||
|
||||
print(my_global_variable) -- Output: "ciao"
|
||||
one()
|
||||
print(my_global_variable) -- Output: "hey"
|
||||
```
|
||||
|
||||
|
||||
### Local dovrebbe essere usato il più possibile
|
||||
|
||||
Lua è globale di default (a differenza di molti altri linguaggi di programmazione).
|
||||
Le variabili locali devono essere identificate come tali.
|
||||
|
||||
```lua
|
||||
function one()
|
||||
foo = "bar"
|
||||
@ -106,15 +240,15 @@ one()
|
||||
two()
|
||||
```
|
||||
|
||||
dump() è una funzione che può trasformare qualsiasi variabile in una stringa, cosicché
|
||||
il programmatore possa vedere cosa rappresenta. La variabile foo sarà stampata come
|
||||
"bar", virgolette incluse (che dimostrano che è una stringa).
|
||||
|
||||
### Precedenza alla portata locale
|
||||
|
||||
Le variabili locali dovrebbero venire usate il più possibile, con le mod che creano al massimo una globale corrispondente al nome della mod.
|
||||
Crearne di ulteriori è considerato cattiva programmazione, e Minetest ci avviserà di ciò:
|
||||
L'esempio precedente non è buona programmazione e Minetest, infatti, avviserà di ciò:
|
||||
|
||||
Assignment to undeclared global 'foo' inside function at init.lua:2
|
||||
|
||||
Per ovviare, usa `local`:
|
||||
Per ovviare, usa "local":
|
||||
|
||||
```lua
|
||||
function one()
|
||||
@ -129,10 +263,13 @@ one()
|
||||
two()
|
||||
```
|
||||
|
||||
Ricorda che `nil` significa **non inizializzato**.
|
||||
Ovvero la variabile non è stata ancora assegnata a un valore, non esiste o è stata deinizializzata (cioè impostata a `nil`)
|
||||
Ricorda che nil significa **non inizializzato**.
|
||||
Ovvero la variabile non è stata ancora assegnata a un valore,
|
||||
non esiste o è stata deinizializzata (cioè impostata a nil)
|
||||
|
||||
La stessa cosa vale per le funzioni: esse sono variabili di tipo speciale, e dovrebbero essere dichiarate locali, in quanto altre mod potrebbero sennò avere funzioni con lo stesso nome.
|
||||
La stessa cosa vale per le funzioni. Le funzioni sono variabili di tipo speciale,
|
||||
e dovrebbero essere dichiarate locali, in quanto altre mod potrebbero sennò avere funzioni
|
||||
con lo stesso nome.
|
||||
|
||||
```lua
|
||||
local function foo(bar)
|
||||
@ -140,8 +277,7 @@ local function foo(bar)
|
||||
end
|
||||
```
|
||||
|
||||
Per permettere alle mod di richiamare le tue funzioni, dovresti creare una tabella con lo stesso nome della mod e aggiungercele all'interno.
|
||||
Questa tabella è spesso chiamata una API.
|
||||
Le tabelle API dovrebbero essere usate per permettere ad altre mod di chiamare le funzioni, come in:
|
||||
|
||||
```lua
|
||||
mymod = {}
|
||||
@ -159,7 +295,7 @@ mymod.foo("foobar")
|
||||
Il metodo consigliato per includere in una mod altri script Lua è usare *dofile*.
|
||||
|
||||
```lua
|
||||
dofile(core.get_modpath("modname") .. "/script.lua")
|
||||
dofile(minetest.get_modpath("modname") .. "/script.lua")
|
||||
```
|
||||
|
||||
Uno script può ritornare un valore, che è utile per condividere variabili locali private:
|
||||
@ -169,8 +305,10 @@ Uno script può ritornare un valore, che è utile per condividere variabili loca
|
||||
return "Hello world!"
|
||||
|
||||
-- init.lua
|
||||
local ret = dofile(core.get_modpath("modname") .. "/script.lua")
|
||||
local ret = dofile(minetest.get_modpath("modname") .. "/script.lua")
|
||||
print(ret) -- Hello world!
|
||||
```
|
||||
|
||||
Nei [capitoli seguenti](../quality/clean_arch.html) si parlerà nel dettaglio di come suddividere il codice di una mod.
|
||||
Nei capitoli seguenti si parlerà nel dettaglio di come suddividere il codice di una mod.
|
||||
Tuttavia, per ora l'approccio semplicistico è di avere file differenti per diversi tipi di cose
|
||||
— nodi.lua, craft.lua, oggetti.lua ecc.
|
||||
|
@ -9,10 +9,10 @@ idx: 7.1
|
||||
|
||||
Il punto forte di Minetest è quello di poter sviluppare giochi con facilità senza il bisogno di costruirsi il proprio motore grafico voxel, i propri algoritmi voxel, o la propria parte network.
|
||||
|
||||
- [Cos'è un gioco?](#cosè-un-gioco)
|
||||
- [Cos'è un gioco?](#cose-un-gioco)
|
||||
- [Cartella di un gioco](#cartella-di-un-gioco)
|
||||
- [Compatibilità tra giochi](#compatibilità-tra-giochi)
|
||||
- [Compatibilità delle API](#compatibilità-delle-api)
|
||||
- [Compatibilità tra giochi](#compatibilita-tra-giochi)
|
||||
- [Compatibilità delle API](#compatibilita-delle-api)
|
||||
- [Gruppi e alias](#gruppi-e-alias)
|
||||
- [Il tuo turno](#il-tuo-turno)
|
||||
|
||||
|
21
_it/index.md
21
_it/index.md
@ -1,6 +1,5 @@
|
||||
---
|
||||
title: Copertina
|
||||
description: An easy guide to learn how to create mods for Minetest
|
||||
title: Front Cover
|
||||
layout: default
|
||||
homepage: true
|
||||
no_header: true
|
||||
@ -9,7 +8,7 @@ idx: 0.1
|
||||
---
|
||||
|
||||
<header>
|
||||
<h1>Minetest: Libro del Moddaggio</h1>
|
||||
<h1>Minetest: Libro del Modding</h1>
|
||||
|
||||
<span>di <a href="https://rubenwardy.com" rel="author">rubenwardy</a></span>
|
||||
<span>con modifiche di <a href="http://rc.minetest.tv/">Shara</a></span>
|
||||
@ -18,18 +17,20 @@ idx: 0.1
|
||||
|
||||
## Introduzione
|
||||
|
||||
Il moddaggio su Minetest è supportato grazie a script in Lua.
|
||||
Questo libro mira a insegnarti come si crea una mod, iniziando dalle basi: ogni capitolo si concentra su un aspetto specifico dell'API, così da arrivare in breve tempo a farti creare i tuoi contenuti.
|
||||
Il modding su Minetest è supportato grazie a script in Lua.
|
||||
Questo libro mira a insegnarti come creare le tue mod, iniziando dalle basi.
|
||||
Ogni capitolo si concentra su un punto specifico dell'API, e ti porterà presto
|
||||
a fare le tue mod.
|
||||
|
||||
Oltre che [leggere questo libro su internet](https://rubenwardy.com/minetest_modding_book),
|
||||
Oltre che [leggere questo libro online](https://rubenwardy.com/minetest_modding_book),
|
||||
puoi anche [scaricarlo in HTML](https://github.com/rubenwardy/minetest_modding_book/releases).
|
||||
|
||||
### Riscontri e Contributi
|
||||
### Feedback e Contributi
|
||||
|
||||
Hai notato un errore o vuoi dirmi la tua? Assicurati di farmelo presente.
|
||||
Hai notato un errore o vuoi dare un feedback? Assicurati di farmelo presente.
|
||||
|
||||
* Apri una [Segnalazione su GitLab](https://gitlab.com/rubenwardy/minetest_modding_book/-/issues).
|
||||
* Rispondi alla [Discussione sul Forum](https://forum.minetest.net/viewtopic.php?f=14&t=10729).
|
||||
* Crea una [Issue su GitLab](https://gitlab.com/rubenwardy/minetest_modding_book/-/issues).
|
||||
* Posta nel [Topic sul Forum](https://forum.minetest.net/viewtopic.php?f=14&t=10729).
|
||||
* [Contattami (in inglese)](https://rubenwardy.com/contact/).
|
||||
* Voglia di contribuire?
|
||||
[Leggi il README](https://gitlab.com/rubenwardy/minetest_modding_book/-/blob/master/README.md).
|
||||
|
@ -1,184 +0,0 @@
|
||||
---
|
||||
title: Richiami dei nodi e degli oggetti
|
||||
layout: default
|
||||
root: ../..
|
||||
idx: 2.15
|
||||
description: Scopri i richiami, le azioni e gli eventi, come on_use, on_punch, on_place e on_rightclick
|
||||
---
|
||||
|
||||
## Introduction <!-- omit in toc -->
|
||||
|
||||
Minetest usa una struttura di moddaggio estensivamente incentrata sui richiami. Un richiamo è una funzione che si dà a un'API e che viene chiamata quando l'evento registrato si verifica.
|
||||
Per esempio, puoi aggiungere una funzione `on_punch` nella definizione di un nodo, che verrà chiamata quando questo viene colpito.
|
||||
Ci sono poi anche dei richiami globali, come `core.register_on_punchnode`, che in questo caso verrà invocato al colpire qualsiasi nodo.
|
||||
|
||||
- [Richiami degli oggetti](#richiami-degli-oggetti)
|
||||
- [on_use](#on_use)
|
||||
- [on_place e on_secondary_use](#on_place-e-on_secondary_use)
|
||||
- [on_drop](#on_drop)
|
||||
- [after_use](#after_use)
|
||||
- [item_place contro place_item](#item_place-contro-place_item)
|
||||
- [Richiami dei nodi](#richiami-dei-nodi)
|
||||
- [Tasto destro e nodi piazzati](#tasto-destro-e-nodi-piazzati)
|
||||
- [Colpire e scavare](#colpire-e-scavare)
|
||||
- [...e altro!](#e-altro)
|
||||
|
||||
|
||||
## Richiami degli oggetti
|
||||
|
||||
Quando un giocatore ha un nodo, un oggetto fabbricabile o uno strumento nel proprio inventario, questi potrebbero innescare degli eventi:
|
||||
|
||||
| Richiamo | Assegnazione base | Valore base |
|
||||
|------------------|---------------------------|----------------------------------------------|
|
||||
| on_use | clic sinistro | nil |
|
||||
| on_place | clic destro su un nodo | `core.item_place` |
|
||||
| on_secondary_use | clic destro a vuoto | `core.item_secondary_use` (non fa nulla) |
|
||||
| on_drop | Q | `core.item_drop` |
|
||||
| after_use | allo scavare un nodo | nil |
|
||||
|
||||
|
||||
### on_use
|
||||
|
||||
Sovrascrivere l'uso dell'oggetto impedisce che quest'ultimo possa essere usato per scavare nodi.
|
||||
Un impiego comune di questo richiamo lo si trova nel cibo:
|
||||
|
||||
```lua
|
||||
core.register_craftitem("miamod:fangotorta", {
|
||||
description = "Torta aliena di fango",
|
||||
inventory_image = "miamod_fangotorta.png",
|
||||
on_use = core.item_eat(20),
|
||||
})
|
||||
```
|
||||
|
||||
Il numero fornito alla funzione core.item_eat è il numero di punti salute ripristinati al consumare il cibo.
|
||||
In gioco ogni cuore equivale a due punti.
|
||||
Un giocatore ha solitamente un massimo di 10 cuori, ovvero 20 punti salute, e quest'ultimi non devono per forza essere interi - bensì anche decimali.
|
||||
|
||||
`core.item_eat()` è una funzione che ritorna un'altra funzione, in questo caso quindi impostandola come richiamo di on_use.
|
||||
Ciò significa che il codice in alto è alquanto simile al seguente:
|
||||
|
||||
```lua
|
||||
core.register_craftitem("miamod:fangotorta", {
|
||||
description = "Torta aliena di fango",
|
||||
inventory_image = "miamod_fangotorta.png",
|
||||
on_use = function(...)
|
||||
return core.do_item_eat(20, nil, ...)
|
||||
end,
|
||||
})
|
||||
```
|
||||
|
||||
Capendo come funziona item_eat, è possibile modificarlo per operazioni più complesse
|
||||
come per esempio riprodurre un suono personalizzato.
|
||||
|
||||
|
||||
### on_place e on_secondary_use
|
||||
|
||||
La differenza tra `on_place` e `on_secondary_use` consiste nel fatto che `on_place` viene chiamato quando il giocatore sta puntando un nodo, mentre `on_secondary_use` quando non ne punta uno.
|
||||
|
||||
Entrambi i richiami sono invocati per tutti i tipi di oggetti.
|
||||
`on_place` risponde alla funzione `core.item_place`, la quale o gestisce la chiamata a `on_rightclick` del nodo puntato, o piazza l'oggetto in mano se questo è un nodo.
|
||||
|
||||
|
||||
### on_drop
|
||||
|
||||
`on_drop` viene chiamato quando il giocatore fa richiesta per buttare un oggetto, per esempio usando il tasto apposito (Q) o trascinando l'oggetto fuori dall'inventario.
|
||||
Risponde alla funzione `core.item_drop`, la quale gestisce il buttare l'oggetto.
|
||||
|
||||
### after_use
|
||||
|
||||
`after_use` viene chiamato quando si scava un nodo, e permette di personalizzare come viene applicata l'usura a uno strumento.
|
||||
Se `after_use` non esiste, è come se ci fosse scritto:
|
||||
|
||||
```lua
|
||||
after_use = function(itemstack, user, node, digparams)
|
||||
itemstack:add_wear(digparams.wear)
|
||||
return itemstack
|
||||
end
|
||||
```
|
||||
|
||||
|
||||
## item_place contro place_item
|
||||
|
||||
L'API di Minetest include varie implementazioni già pronte di richiami.
|
||||
Queste seguono la nomenclatura "tipodioggetto_azione", per esempio `core.item_place` e `core.node_dig`.
|
||||
Alcune sono usate direttamente, mentre altre sono funzioni che ritornano il richiamo vero e proprio:
|
||||
|
||||
```lua
|
||||
core.register_item("miamod:esempio", {
|
||||
on_place = core.item_place,
|
||||
on_use = core.item_eat(10),
|
||||
})
|
||||
```
|
||||
|
||||
Inoltre, l'API di Minetest include funzioni già pronte che _fanno_ qualcosa.
|
||||
Queste sono spesso chiamate con nomi che rischiano di farle confondere con le implementazioni dei richiami, tuttavia hanno un verbo all'inizio (per esempio `core.place_item` e `core.dig_node`, che permettono rispettivamente di scavare e piazzare nodi come se lo stesse facendo un giocatore).
|
||||
|
||||
|
||||
## Richiami dei nodi
|
||||
|
||||
Quando un nodo si trova in un inventario, vengono invocati i richiami degli oggetti discussi poc'anzi.
|
||||
Al contrario, quando un nodo è situato nel mondo, vengono invocati i richiami dei nodi.
|
||||
Ce ne sono di svariati tipi, troppi per essere discussi in questo libro, tuttavia alcuni di questi verranno trattati nei capitoli successivi.
|
||||
|
||||
Molti richiami dei nodi sono collegati alle operazioni effettuate - appunto - sui nodi, come piazzarli e rimuoverli dal mondo.
|
||||
È importante però sottolineare che, per motivi di prestazioni, operazioni come queste non vengono chiamate da modifiche in blocco (quelle che cambiano un grande numero di nodi in un colpo solo).
|
||||
È meglio quindi non fare affidamento su un'esecuzione sicura al 100%.
|
||||
|
||||
|
||||
### Tasto destro e nodi piazzati
|
||||
|
||||
Quando un utente preme col tasto destro un nodo mentre ha un oggetto in mano, viene invocato il richiamo `on_place` dell'oggetto.
|
||||
Di base, questo è impostato a `core.item_place`.
|
||||
Se il nodo puntato ha un richiamo `on_rightclick` e il tasto accovacciati (shift) è tenuto premuto, allora verrà chiamato `on_rightclick`.
|
||||
Diversamente, `core.item_place` piazzerà il nodo.
|
||||
|
||||
Piazzare un nodo invocherà simultaneamente `on_construct` e `after_place_node`: il primo è chiamato da ogni evento che cambia i singoli nodi (quindi non in blocco) e ritorna la posizione e il valore del nodo.
|
||||
`after_place_node` viene invece chiamato solamente al piazzare un nodo, contenendo di conseguenza più informazioni - come chi l'ha piazzato e l'ItemStack.
|
||||
|
||||
È importante notare che i giocatori non sono le uniche realtà che possono piazzare nodi; anche le entità e le mod possono farlo.
|
||||
Per via di ciò, `place` potrebbe essere un giocatore, ma anche un'entità o `nil`.
|
||||
|
||||
```lua
|
||||
core.register_node("miamod:mionodo", {
|
||||
on_rightclick = function(pos, node, clicker, itemstack, pointed_thing)
|
||||
if clicker:is_player() then
|
||||
core.chat_send_player(clicker:get_player_name(), "Ciao mondo!")
|
||||
end
|
||||
end,
|
||||
on_construct = function(pos, node)
|
||||
local meta = core.get_meta(pos)
|
||||
meta:set_string("infotext", "Il mio nodo!")
|
||||
end,
|
||||
after_place_node = function(pos, placer, itemstack, pointed_thing)
|
||||
-- controlla chi sta piazzando
|
||||
if placer and placer:is_player() then
|
||||
local meta = core.get_meta(pos)
|
||||
meta:set_string("proprietario", placer:get_player_name())
|
||||
end
|
||||
end,
|
||||
})
|
||||
```
|
||||
|
||||
### Colpire e scavare
|
||||
|
||||
Si ha un colpo quando un giocatore preme col tasto sinistro per un breve periodo.
|
||||
Se l'oggetto in mano possiede un richiamo `on_use`, questo verrà chiamato.
|
||||
Diversamente, verrà chiamato il richiamo `on_punch` sul nodo selezionato.
|
||||
|
||||
Quando il giocatore tenta di scavare un nodo, viene eseguito il richiamo `on_dig` del nodo.
|
||||
Di base, ciò equivale a `core.node_dig`, che controlla eventuali protezioni dell'area, usura l'oggetto, rimuove il nodo, e ne esegue il richiamo `after_dig_node`.
|
||||
|
||||
|
||||
```lua
|
||||
core.register_node("miamod:mionodo", {
|
||||
on_punch = function(pos, node, puncher, pointed_thing)
|
||||
if puncher:is_player() then
|
||||
core.chat_send_player(puncher:get_player_name(), "Ahia!")
|
||||
end
|
||||
end,
|
||||
})
|
||||
```
|
||||
|
||||
### ...e altro!
|
||||
|
||||
Dài un occhio alla API Lua di Minetest per una lista di tutti i richiami, e per avere più informazioni riguardo quelli vista qui sopra.
|
@ -9,10 +9,13 @@ redirect_from: /it/chapters/creating_textures.html
|
||||
|
||||
## Introduzione <!-- omit in toc -->
|
||||
|
||||
Essere in grado di creare e ottimizare le texture è un'abilità alquanto utile quando si sviluppa per Minetest.
|
||||
Ci sono molti approcci sul come creare texture in pixel art, e capire questi approcci migliorerà nettamente la qualità dei tuoi lavori.
|
||||
Essere in grado di creare e ottimizare le texture è un'abilità alquanto utile quando si sviluppa
|
||||
per Minetest.
|
||||
Ci sono molti approcci sul come creare texture in pixel art, e capire questi approcci
|
||||
migliorerà nettamente la qualità dei tuoi lavori.
|
||||
|
||||
Fornire spiegazioni dettagliate non rientra tuttavia nell'ambito di questo libro: verranno quindi trattate solo le tecniche più semplici.
|
||||
Fornire spiegazioni dettagliate non rientra tuttavia nell'ambito di questo libro:
|
||||
verranno quindi trattate solo le tecniche più semplici.
|
||||
Se si vuole approfondire, ci sono comunque molti [buoni tutorial online](http://www.photonstorm.com/art/tutorials-art/16x16-pixel-art-tutorial) disponibili, che si occupano di pixel art in modo molto più dettagliato.
|
||||
|
||||
- [Tecniche](#tecniche)
|
||||
@ -28,14 +31,17 @@ Se si vuole approfondire, ci sono comunque molti [buoni tutorial online](http://
|
||||
### Usare la matita
|
||||
|
||||
Lo strumento matita è disponibile nella maggior parte dei programmi di disegno.
|
||||
Quando viene impostato alla dimensione minima, permette di disegnare un pixel alla volta senza alterare le atre parti dell'immagine.
|
||||
Manipolando i singoli pixel si possono creare texture chiare e nette senza alcuna sfocatura non voluta, dando inoltre un alto livello di precisione e controllo.
|
||||
Quando viene impostato alla dimensione minima, ti permette di disegnare un pixel alla volta
|
||||
senza alterare le atre parti dell'immagine.
|
||||
Manipolando i singoli pixel si possono creare texture chiare e nette senza alcuna
|
||||
sfocatura non voluta, dando inoltre un alto livello di precisione e controllo.
|
||||
|
||||
### Piastrellatura (tiling)
|
||||
|
||||
Le texture usate per i nodi dovrebbero generalmente essere progettate per ripetersi come
|
||||
delle piastrelle.
|
||||
Questo significa che quando piazzi più nodi con la stessa texture vicini, i bordi dovranno allinearsi correttamente creando un effetto di continuità.
|
||||
Questo significa che quando piazzi più nodi con la stessa texture vicini, i bordi dovranno
|
||||
allinearsi correttamente creando un effetto di continuità.
|
||||
|
||||
<!-- IMAGE NEEDED - cobblestone that tiles correctly -->
|
||||
|
||||
@ -46,8 +52,10 @@ gradevole da vedere.
|
||||
|
||||
### Trasparenza
|
||||
|
||||
La trasparenza è importante quando si creano texture per pressoché tutti gli oggetti fabbricabili e per alcuni nodi, come il vetro.
|
||||
Non tutti i programmi supportano la trasparenza, perciò assicurati di sceglierne uno adatto ai tipi di texture che vuoi creare.
|
||||
La trasparenza è importante quando si creano texture per pressoché tutti gli
|
||||
oggetti fabbricabili e per alcuni nodi, come il vetro.
|
||||
Non tutti i programmi supportano la trasparenza, perciò assicurati di sceglierne
|
||||
uno adatto ai tipi di texture che vuoi creare.
|
||||
|
||||
## Programmi
|
||||
|
||||
@ -61,7 +69,8 @@ tuttavia se la trasparenza è un requisito nelle tue texture dovresti guardare o
|
||||
### GIMP
|
||||
|
||||
GIMP viene impiegato spesso nella comunità di Minetest.
|
||||
Ha una curva di apprendimento alquanto alta, dato che molte delle sue funzioni non risultano ovvie nell'immediato.
|
||||
Ha una curva di apprendimento alquanto alta, dato che molte delle sue funzioni
|
||||
non risultano ovvie nell'immediato.
|
||||
|
||||
Quando usi GIMP, puoi selezionare la matita dalla Barra degli Strumenti:
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
---
|
||||
title: ItemStack e inventari
|
||||
title: ItemStack e Inventari
|
||||
layout: default
|
||||
root: ../..
|
||||
idx: 2.4
|
||||
@ -13,7 +13,8 @@ redirect_from:
|
||||
|
||||
## Introduzione <!-- omit in toc -->
|
||||
|
||||
In questo capitolo, imparerai come usare e manipolare gli inventari, siano essi quelli di un giocatore, di un nodo o a sé stanti.
|
||||
In questo capitolo, imparerai come usare e manipolare gli inventari, siano essi quelli
|
||||
di un giocatore, di un nodo o a sé stanti.
|
||||
|
||||
- [Cosa sono gli ItemStack e gli inventari?](#cosa-sono-gli-itemstack-e-gli-inventari)
|
||||
- [ItemStack](#itemstack)
|
||||
@ -30,17 +31,21 @@ In questo capitolo, imparerai come usare e manipolare gli inventari, siano essi
|
||||
|
||||
## Cosa sono gli ItemStack e gli inventari?
|
||||
|
||||
Un ItemStack ( lett. "pila di oggetti") è il dato dietro una singola cella di un inventario.
|
||||
Un ItemStack (*pila di oggetti*) è il dato dietro una singola cella di un inventario.
|
||||
|
||||
Un *inventario* è una collezione di *liste* apposite, ognuna delle quali è una griglia 2D di ItemStack.
|
||||
Lo scopo di un inventario è quello di raggruppare più liste in un singolo oggetto (l'inventario appunto), in quanto a ogni giocatore e a ogni nodo ne può essere associato massimo uno.
|
||||
Un *inventario* è una collezione di *liste* apposite, ognuna delle quali è una griglia
|
||||
2D di ItemStack.
|
||||
Lo scopo di un inventario è quello di raggruppare più liste in un singolo oggetto (l'inventario appunto),
|
||||
in quanto a ogni giocatore e a ogni nodo può essere associato massimo un inventario.
|
||||
|
||||
## ItemStack
|
||||
|
||||
Gli ItemStack sono composti da quattro parametri: nome, quantità, durabilità e metadati.
|
||||
|
||||
Il nome dell'oggetto può essere il nome di un oggetto registrato, di uno sconosciuto (non registrato) o un alias.
|
||||
Gli oggetti sconosciuti sono tipici di quando si disinstallano le mod, o quando le mod rimuovono degli oggetti senza nessun accorgimento, tipo senza registrarne un alias.
|
||||
Il nome dell'oggetto può essere il nome di un oggetto registrato, di uno sconosciuto (non registrato)
|
||||
o un alias.
|
||||
Gli oggetti sconosciuti sono tipici di quando si disinstallano le mod, o quando le mod rimuovono
|
||||
degli oggetti senza nessun accorgimento, tipo senza registrarne un alias.
|
||||
|
||||
```lua
|
||||
print(stack:get_name())
|
||||
@ -52,13 +57,18 @@ end
|
||||
```
|
||||
|
||||
La quantità sarà sempre 0 o maggiore.
|
||||
Durante una normale sessione di gioco, la quantità non dovrebbe mai essere maggiore della dimensione massima della pila dell'oggetto - `stack_max`.
|
||||
Tuttavia, comandi da amministratore e mod fallate potrebbero portare a oggetti impilati che superano la grandezza massima.
|
||||
Durante una normale sessione di gioco, la quantità non dovrebbe mai essere maggiore della dimensione
|
||||
massima della pila dell'oggetto - `stack_max`.
|
||||
Tuttavia, comandi da amministratore e mod fallate potrebbero portare a oggetti impilati che superano
|
||||
la grandezza massima.
|
||||
|
||||
```lua
|
||||
print(stack:get_stack_max())
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
Un ItemStack può essere vuoto, nel qual caso avrà come quantità 0.
|
||||
|
||||
```lua
|
||||
@ -66,7 +76,7 @@ print(stack:get_count())
|
||||
stack:set_count(10)
|
||||
```
|
||||
|
||||
Gli ItemStack possono poi essere creati in diversi modi usando l'omonima funzione.
|
||||
Gli ItemStack possono essere creati in diversi modi usando la funzione ItemStack.
|
||||
|
||||
```lua
|
||||
ItemStack() -- name="", count=0
|
||||
@ -77,7 +87,8 @@ ItemStack({ name = "default:wood", count = 10 })
|
||||
|
||||
I metadati di un oggetto sono una o più coppie chiave-valore custodite in esso.
|
||||
Chiave-valore significa che si usa un nome (la chiave) per accedere al dato corrispettivo (il valore).
|
||||
Alcune chiavi hanno significati predefiniti, come `description` che è usato per specificare la descrizione di una pila di oggetti.
|
||||
Alcune chiavi hanno significati predefiniti, come `description` che è usato per specificare la descrizione
|
||||
di una pila di oggetti.
|
||||
Questo sarà trattato più in dettaglio nel capitolo Storaggio e Metadati.
|
||||
|
||||
## Collocazione inventari
|
||||
@ -90,11 +101,12 @@ Gli inventari collocati nei nodi sono associati alle coordinate di un nodo speci
|
||||
Il nodo deve essere stato caricato perché viene salvato [nei suoi metadati](../map/storage.html#metadata).
|
||||
|
||||
```lua
|
||||
local inv = core.get_inventory({ type="node", pos={x=1, y=2, z=3} })
|
||||
local inv = minetest.get_inventory({ type="node", pos={x=1, y=2, z=3} })
|
||||
```
|
||||
|
||||
L'esempio in alto ottiene il *riferimento a un inventario*, comunemente definito *InvRef*.
|
||||
Questi riferimenti sono usati per manipolare l'inventario, e son chiamati così perché i dati non sono davvero salvati dentro all'oggetto (in questo caso "inv"), bensì *puntano* a quei dati.
|
||||
Questi riferimenti sono usati per manipolare l'inventario, e son chiamati così perché i dati
|
||||
non sono davvero salvati dentro all'oggetto (in questo caso "inv"), bensì *puntano* a quei dati.
|
||||
In questo modo, modificando "inv", stiamo in verità modificando l'inventario.
|
||||
|
||||
La collocazione di tali riferimenti può essere ottenuta nel seguente modo:
|
||||
@ -103,34 +115,36 @@ La collocazione di tali riferimenti può essere ottenuta nel seguente modo:
|
||||
local location = inv:get_location()
|
||||
```
|
||||
|
||||
Gli inventari dei giocatori si ottengono in maniera simile, oppure usando il riferimento a un giocatore (*PlayerRef*).
|
||||
In entrambi casi, il giocatore deve essere connesso.
|
||||
Gli inventari dei giocatori si ottengono in maniera simile, oppure usando il riferimento
|
||||
a un giocatore (*PlayerRef*). In entrambi casi, il giocatore deve essere connesso.
|
||||
|
||||
```lua
|
||||
local inv = core.get_inventory({ type="player", name="player1" })
|
||||
local inv = minetest.get_inventory({ type="player", name="player1" })
|
||||
-- oppure
|
||||
local inv = player:get_inventory()
|
||||
```
|
||||
|
||||
Gli inventari separati, infine, sono quelli non collegati né a nodi né a giocatori, e al contrario degli altri, vengono persi dopo un riavvio.
|
||||
Gli inventari separati, infine, sono quelli non collegati né a nodi né a giocatori,
|
||||
e al contrario degli altri, vengono persi dopo un riavvio.
|
||||
|
||||
```lua
|
||||
local inv = core.get_inventory({
|
||||
local inv = minetest.get_inventory({
|
||||
type="detached", name="nome_inventario" })
|
||||
```
|
||||
|
||||
Un'ulteriore differenza, è che gli inventari separati devono essere creati prima di poterci accedere:
|
||||
|
||||
```lua
|
||||
core.create_detached_inventory("inventory_name")
|
||||
minetest.create_detached_inventory("inventory_name")
|
||||
```
|
||||
|
||||
La funzione `create_detached_inventory` accetta 3 parametri, di cui solo il primo - il nome - è necessario.
|
||||
Il secondo parametro prende una tabella di callback, che possono essere utilizzati per controllare come i giocatori interagiscono con l'inventario:
|
||||
La funzione create_detached_inventory accetta 3 parametri, di cui solo il primo - il nome - è necessario.
|
||||
Il secondo parametro prende una tabella di callback, che possono essere utilizzati
|
||||
per controllare come i giocatori interagiscono con l'inventario:
|
||||
|
||||
```lua
|
||||
-- Input only detached inventory
|
||||
core.create_detached_inventory("inventory_name", {
|
||||
minetest.create_detached_inventory("inventory_name", {
|
||||
allow_move = function(inv, from_list, from_index, to_list, to_index, count, player)
|
||||
return count -- permette di spostare gli oggetti
|
||||
end,
|
||||
@ -140,29 +154,33 @@ core.create_detached_inventory("inventory_name", {
|
||||
end,
|
||||
|
||||
allow_take = function(inv, listname, index, stack, player)
|
||||
return 0 -- non permette di rimuoverli
|
||||
return -1 -- non permette di rimuoverli
|
||||
end,
|
||||
|
||||
on_put = function(inv, listname, index, stack, player)
|
||||
core.chat_send_all(player:get_player_name() ..
|
||||
minetest.chat_send_all(player:get_player_name() ..
|
||||
" ha messo " .. stack:to_string() ..
|
||||
" nella cassa delle donazioni da " .. core.pos_to_string(player:get_pos()))
|
||||
" nella cassa delle donazioni da " .. minetest.pos_to_string(player:get_pos()))
|
||||
end,
|
||||
})
|
||||
```
|
||||
|
||||
I callback dei permessi - quelle che iniziano con `allow_` - ritornano il numero degli oggetti da trasferire, e si usa 0 per impedirne del tutto l'azione.
|
||||
I callback dei permessi - quelle che iniziano con `allow_` - ritornano il numero
|
||||
degli oggetti da trasferire, e si usa -1 per impedirne del tutto l'azione.
|
||||
|
||||
I callback delle azioni - quelle che iniziano con `on_` - non ritornano invece alcun valore.
|
||||
|
||||
## Liste
|
||||
|
||||
Le liste negli inventari permettono di disporre più griglie nello stesso luogo (l'inventario).
|
||||
Esse sono particolarmente utili per il giocatore, e infatti di base ogni gioco possiede già delle liste come *main* per il corpo principale dell'inventario e *craft* per l'area di fabbricazione.
|
||||
Esse sono particolarmente utili per il giocatore, e infatti di base ogni gioco possiede già
|
||||
delle liste come *main* per il corpo principale dell'inventario e *craft* per l'area di fabbricazione.
|
||||
|
||||
### Dimensione e ampiezza
|
||||
|
||||
Le liste hanno una dimensione, equivalente al numero totale di celle nella griglia, e un'ampiezza, che è usata esclusivamente dentro il motore di gioco: quando viene disegnato un inventario in una finestra, infatti, il codice dietro di essa già determina che ampiezza usare.
|
||||
Le liste hanno una dimensione, equivalente al numero totale di celle nella griglia, e un'ampiezza,
|
||||
che è usata esclusivamente dentro il motore di gioco: quando viene disegnato un inventario in una finestra,
|
||||
infatti, il codice dietro di essa già determina che ampiezza usare.
|
||||
|
||||
```lua
|
||||
if inv:set_size("main", 32) then
|
||||
@ -174,8 +192,10 @@ else
|
||||
end
|
||||
```
|
||||
|
||||
`set_size` non andrà in porto e ritornerà "false" se il nome della lista o la dimensione dichiarata non risultano valide.
|
||||
Per esempio, la nuova dimensione potrebbe essere troppo piccola per contenere gli oggetti attualmente presenti nell'inventario.
|
||||
`set_size` non andrà in porto e ritornerà "false" se il nome della lista o la dimensione dichiarata
|
||||
non risultano valide.
|
||||
Per esempio, la nuova dimensione potrebbe essere troppo piccola per contenere gli oggetti attualmente
|
||||
presenti nell'inventario.
|
||||
|
||||
### Controllare il contenuto
|
||||
|
||||
@ -199,8 +219,8 @@ end
|
||||
|
||||
### Aggiungere a una lista
|
||||
|
||||
Per aggiungere degli oggetti a una lista (in questo caso "main") usiamo `add_item`.
|
||||
Nell'esempio sottostante ci accertiamo anche di rispettare la dimensione:
|
||||
Per aggiungere degli oggetti a una lista (in questo caso "main") usiamo `add_item`. Nell'esempio
|
||||
sottostante ci accertiamo anche di rispettare la dimensione:
|
||||
|
||||
```lua
|
||||
local stack = ItemStack("default:stone 99")
|
||||
@ -228,7 +248,8 @@ Puoi modificare le singole pile prima ottenendole:
|
||||
local stack = inv:get_stack(listname, 0)
|
||||
```
|
||||
|
||||
E poi modificandole impostando le nuove proprietà o usando i metodi che rispettano `stack_size`:
|
||||
E poi modificandole impostando le nuove proprietà o usando i metodi che
|
||||
rispettano `stack_size`:
|
||||
|
||||
```lua
|
||||
local pila = ItemStack("default:stone 50")
|
||||
@ -246,7 +267,8 @@ print("Hai " .. pila:get_count() .. " oggetti")
|
||||
```
|
||||
|
||||
`add_item` aggiungerà gli oggetti all'ItemStack e ritornerà quelli in eccesso.
|
||||
`take_item` rimuoverà gli oggetti indicati (o meno se ce ne sono meno), e ritornerà l'ammontare rimosso.
|
||||
`take_item` rimuoverà gli oggetti indicati (o meno se ce ne sono meno), e ritornerà
|
||||
l'ammontare rimosso.
|
||||
|
||||
Infine, si imposta la pila modificata:
|
||||
|
||||
@ -256,8 +278,9 @@ inv:set_stack(listname, 0, pila)
|
||||
|
||||
## Usura
|
||||
|
||||
Gli strumenti possono avere un livello di usura; essa è rappresentata da un barra progressiva e fa rompere lo strumento quando completamente logorato.
|
||||
Nello specifico, l'usura è un numero da 0 a 65535: più è alto, più è consumato l'oggetto.
|
||||
Gli strumenti possono avere un livello di usura; l'usura è rappresentata da un barra progressiva
|
||||
e fa rompere lo strumento quando completamente logorato.
|
||||
L'usura è un numero da 0 a 65535; più è alto, più è consumato l'oggetto.
|
||||
|
||||
Il livello di usura può essere manipolato usando `add_wear()`, `get_wear()`, e `set_wear(wear)`.
|
||||
|
||||
@ -270,7 +293,7 @@ local usi_massimi = 10
|
||||
pila:add_wear(65535 / (usi_massimi - 1))
|
||||
```
|
||||
|
||||
Quando si scava un nodo, l'incremento di usura di uno strumento dipende da che tipo di nodo è.
|
||||
Quando scavi un nodo, l'incremento di usura di uno strumento dipende da che tipo di nodo è.
|
||||
Di conseguenza, `usi_massimi` varia a seconda di cos'è stato scavato.
|
||||
|
||||
## Tabelle Lua
|
||||
@ -311,8 +334,10 @@ La tabella di liste ritornata da `get_lists()` sarà nel seguente formato:
|
||||
|
||||
`get_list()` ritornerà una lista singola fatta di ItemStack.
|
||||
|
||||
Una cosa importante da sottolineare è che i metodi `set` qui in alto non cambiano la dimensione delle liste.
|
||||
Questo significa che si può svuotare una lista dichiarandola uguale a una tabella vuota, e la sua dimensione tuttavia non cambierà:
|
||||
Una cosa importante da sottolineare è che i metodi `set` qui in alto non cambiano
|
||||
la dimensione delle liste.
|
||||
Questo significa che si può svuotare una lista dichiarandola uguale a una tabella vuota,
|
||||
e la sua dimensione tuttavia non cambierà:
|
||||
|
||||
```lua
|
||||
inv:set_list("main", {})
|
||||
|
@ -10,14 +10,21 @@ redirect_from: /it/chapters/node_drawtypes.html
|
||||
## Introduzione <!-- omit in toc -->
|
||||
|
||||
Il metodo col quale un nodo viene disegnato in gioco è chiamato *drawtype*.
|
||||
Ci sono diversi tipi di drawtype: il loro comportamento è determinato dalle proprietà impostate durante la definizione del tipo di nodo.
|
||||
Queste proprietà sono fisse, uguali per tutte le istanze, tuttavia è possibile manipolarne alcune per singolo nodo usando una cosa chiamata `param2`.
|
||||
Ci sono diversi tipi di drawtype: il loro comportamento è determinato dalle proprietà
|
||||
impostate durante la definizione del tipo di nodo.
|
||||
Queste proprietà sono fisse, uguali per tutte le istanze, tuttavia
|
||||
è possibile manipolarne alcune per singolo nodo usando una cosa chiamata `param2`.
|
||||
|
||||
Il concetto di nodo è stato introdotto nello scorso capitolo, ma non è mai stata data una definizione completa.
|
||||
Il mondo di Minetest è una griglia 3D: un nodo è un punto di quella griglia ed è composto da un tipo (`name`) e due parametri (`param1` e `param2`).
|
||||
Non farti inoltre ingannare dalla funzione `core.register_node`, in quanto è un po' fuorviante: essa non registra infatti un nuovo nodo (c'è solo una definizione di nodo), bensì un nuovo *tipo* di nodo.
|
||||
Il concetto di nodo è stato introdotto nello scorso capitolo, ma non è mai stata
|
||||
data una definizione completa.
|
||||
Il mondo di Minetest è una griglia 3D: un nodo è un punto di quella griglia ed è composto
|
||||
da un tipo (name) e due parametri (param1 e param2).
|
||||
Non farti inoltre ingannare dalla funzione `minetest.register_node`, in quanto è un po' fuorviante:
|
||||
essa non registra infatti un nuovo nodo (c'è solo una definizione di nodo), bensì un nuovo *tipo* di nodo.
|
||||
|
||||
I parametri sono infine usati per controllare come un nodo viene renderizzato individualmente: `param1` immagazzina le proprietà di luce, mentre il ruolo di `param2` dipende dalla proprietà `paramtype2`, la quale è situata nella definizione dei singoli tipi.
|
||||
I parametri sono infine usati per controllare come un nodo viene renderizzato individualmente:
|
||||
`param1` immagazzina le proprietà di luce, mentre il ruolo di `param2` dipende dalla
|
||||
proprietà `paramtype2`, la quale è situata nella definizione dei singoli tipi di nodi.
|
||||
|
||||
- [Nodi cubici: normali e a facciate piene](#nodi-cubici-normali-e-a-facciate-piene)
|
||||
- [Nodi vitrei](#nodi-vitrei)
|
||||
@ -43,21 +50,23 @@ I parametri sono infine usati per controllare come un nodo viene renderizzato in
|
||||
</figcaption>
|
||||
</figure>
|
||||
|
||||
Il *drawtype* normale è tipicamente usato per renderizzare un nodo cubico.
|
||||
Se il lato di uno di questi nodi tocca un nodo solido, allora quel lato non sarà renderizzato, risultando in un grande guadagno sulle prestazioni.
|
||||
Il drawtype normale è tipicamente usato per renderizzare un nodo cubico.
|
||||
Se il lato di uno di questi nodi tocca un nodo solido, allora quel lato non sarà renderizzato,
|
||||
risultando in un grande guadagno sulle prestazioni.
|
||||
|
||||
Al contrario, i *drawtype* a facciate piene (*allfaces*) renderizzeranno comunque il lato interno quando è contro un nodo solido.
|
||||
Ciò è buono per quei nodi con facce in parte trasparenti come le foglie.
|
||||
Puoi inoltre usare il drawtype `allfaces_optional` per permettere agli utenti di fare opt-out dal rendering più pesante, facendo comportare il nodo come se fosse di tipo normale.
|
||||
Al contrario, i drawtype a facciate piene (allfaces) renderizzeranno comunque il lato interno quando
|
||||
è contro un nodo solido. Ciò è buono per quei nodi con facce in parte trasparenti come
|
||||
le foglie. Puoi inoltre usare il drawtype `allfaces_optional` per permettere agli utenti
|
||||
di fare opt-out dal rendering più pesante, facendo comportare il nodo come se fosse di tipo normale.
|
||||
|
||||
```lua
|
||||
core.register_node("miamod:diamante", {
|
||||
minetest.register_node("miamod:diamante", {
|
||||
description = "Diamante alieno",
|
||||
tiles = {"miamod_diamante.png"},
|
||||
groups = {cracky = 3},
|
||||
})
|
||||
|
||||
core.register_node("default:foglie", {
|
||||
minetest.register_node("default:foglie", {
|
||||
description = "Foglie",
|
||||
drawtype = "allfaces_optional",
|
||||
tiles = {"default_foglie.png"}
|
||||
@ -68,8 +77,10 @@ Attenzione: il drawtype normale è quello predefinito, quindi non c'è bisogno d
|
||||
|
||||
## Nodi vitrei
|
||||
|
||||
La differenza tra i nodi vitrei (*glasslike*) e quelli normali è che piazzando i primi vicino a un nodo normale, non nasconderanno il lato di quest'ultimo.
|
||||
Questo è utile in quanto i nodi vitrei tendono a essere trasparenti, perciò permettono di vedere attraverso.
|
||||
La differenza tra i nodi vitrei (glasslike) e quelli normali è che piazzando i primi vicino a un
|
||||
nodo normale, non nasconderanno il lato di quest'ultimo.
|
||||
Questo è utile in quanto i nodi vitrei tendono a essere trasparenti, perciò permettono
|
||||
di vedere attraverso.
|
||||
|
||||
<figure>
|
||||
<img src="{{ page.root }}//static/drawtype_glasslike_edges.png" alt="Bordi vitrei">
|
||||
@ -79,7 +90,7 @@ Questo è utile in quanto i nodi vitrei tendono a essere trasparenti, perciò pe
|
||||
</figure>
|
||||
|
||||
```lua
|
||||
core.register_node("default:obsidian_glass", {
|
||||
minetest.register_node("default:obsidian_glass", {
|
||||
description = "Vetro d'ossidiana",
|
||||
drawtype = "glasslike",
|
||||
tiles = {"default_obsidian_glass.png"},
|
||||
@ -93,7 +104,8 @@ core.register_node("default:obsidian_glass", {
|
||||
|
||||
### Vitreo incorniciato
|
||||
|
||||
Questa opzione crea un solo bordo lungo tutto l'insieme di nodi, al posto di crearne più per singolo nodo.
|
||||
Questa opzione crea un solo bordo lungo tutto l'insieme di nodi, al posto di
|
||||
crearne più per singolo nodo.
|
||||
|
||||
<figure>
|
||||
<img src="{{ page.root }}//static/drawtype_glasslike_framed.png" alt="Bordi vitrei incorniciati">
|
||||
@ -103,11 +115,11 @@ Questa opzione crea un solo bordo lungo tutto l'insieme di nodi, al posto di cre
|
||||
</figure>
|
||||
|
||||
```lua
|
||||
core.register_node("default:glass", {
|
||||
minetest.register_node("default:glass", {
|
||||
description = "Vetro",
|
||||
drawtype = "glasslike_framed",
|
||||
tiles = {"default_glass.png", "default_glass_detail.png"},
|
||||
inventory_image = core.inventorycube("default_glass.png"),
|
||||
inventory_image = minetest.inventorycube("default_glass.png"),
|
||||
paramtype = "light",
|
||||
sunlight_propagates = true, -- Sunlight can shine through block
|
||||
groups = {cracky = 3, oddly_breakable_by_hand = 3},
|
||||
@ -115,14 +127,14 @@ core.register_node("default:glass", {
|
||||
})
|
||||
```
|
||||
|
||||
Puoi inoltre usare il *drawtype* `glasslike_framed_optional` per permettere un opt-in all'utente.
|
||||
Puoi inoltre usare il drawtype glasslike_framed_optional per permettere un opt-in all'utente.
|
||||
|
||||
## Nodi d'aria
|
||||
|
||||
I nodi d'aria (*airlike*) non sono renderizzati e perciò non hanno texture.
|
||||
I nodi d'aria (airlike) non sono renderizzati e perciò non hanno texture.
|
||||
|
||||
```lua
|
||||
core.register_node("miaaria:aria", {
|
||||
minetest.register_node("miaaria:aria", {
|
||||
description = "Mia Aria",
|
||||
drawtype = "airlike",
|
||||
paramtype = "light",
|
||||
@ -143,20 +155,21 @@ core.register_node("miaaria:aria", {
|
||||
|
||||
## Luce e propagazione solare
|
||||
|
||||
La luce di un nodo è salvata in `param1`.
|
||||
Per capire come ombreggiare il lato di un nodo, viene utilizzato il valore di luminosità dei nodi adiacenti.
|
||||
La luce di un nodo è salvata in param1. Per capire come ombreggiare
|
||||
il lato di un nodo, viene utilizzato il valore di luminosità dei nodi adiacenti.
|
||||
Questo comporta un blocco della luce da parte dei nodi solidi.
|
||||
|
||||
Di base, non viene salvata la luce in nessun nodo né nelle sue istanze.
|
||||
È invece solitamente preferibile farla passare in tipi quali quelli d'aria e vitrei.
|
||||
Per fare ciò, ci sono due proprietà che devono essere definite:
|
||||
È invece solitamente preferibile farla passare in tipi quali quelli d'aria e
|
||||
vitrei. Per fare ciò, ci sono due proprietà che devono essere definite:
|
||||
|
||||
```lua
|
||||
paramtype = "light",
|
||||
sunlight_propagates = true,
|
||||
```
|
||||
|
||||
La prima riga dice a `param1` di immagazzinare l'indice di luminosità, mentre la seconda permette alla luce del sole di propagarsi attraverso il nodo senza diminuire il proprio valore.
|
||||
La prima riga dice a param1 di immagazzinare l'indice di luminosità.
|
||||
La seconda riga invece permette alla luce del sole di propagarsi attraverso il nodo senza diminuire il suo valore.
|
||||
|
||||
## Nodi liquidi
|
||||
|
||||
@ -167,16 +180,17 @@ La prima riga dice a `param1` di immagazzinare l'indice di luminosità, mentre l
|
||||
</figcaption>
|
||||
</figure>
|
||||
|
||||
Ogni tipo di liquido richiede due definizioni di nodi: una per la sorgente e l'altra per il liquido che scorre.
|
||||
Ogni tipo di liquido richiede due definizioni di nodi: una per la sorgente e l'altra
|
||||
per il liquido che scorre.
|
||||
|
||||
```lua
|
||||
-- Alcune proprietà sono state rimosse perché non
|
||||
-- rilevanti per questo capitolo
|
||||
core.register_node("default:water_source", {
|
||||
minetest.register_node("default:water_source", {
|
||||
drawtype = "liquid",
|
||||
paramtype = "light",
|
||||
|
||||
inventory_image = core.inventorycube("default_water.png"),
|
||||
inventory_image = minetest.inventorycube("default_water.png"),
|
||||
-- ^ questo è necessario per impedire che l'immagine nell'inventario sia animata
|
||||
|
||||
tiles = {
|
||||
@ -247,10 +261,11 @@ Guarda default:water_flowing nella mod default di minetest_game per un esempio c
|
||||
</figcaption>
|
||||
</figure>
|
||||
|
||||
I nodi complessi (*nodebox*) ti permettono di creare un nodo che non è cubico, bensì un insieme di più cuboidi.
|
||||
I nodi complessi (nodebox) ti permettono di creare un nodo che non è cubico, bensì un insieme
|
||||
di più cuboidi.
|
||||
|
||||
```lua
|
||||
core.register_node("stairs:stair_stone", {
|
||||
minetest.register_node("stairs:stair_stone", {
|
||||
drawtype = "nodebox",
|
||||
paramtype = "light",
|
||||
node_box = {
|
||||
@ -263,25 +278,30 @@ core.register_node("stairs:stair_stone", {
|
||||
})
|
||||
```
|
||||
|
||||
La parte più importante è la tabella `node_box`:
|
||||
La parte più importable è la tabella node_box:
|
||||
|
||||
```lua
|
||||
{-0.5, -0.5, -0.5, 0.5, 0, 0.5},
|
||||
{-0.5, 0, 0, 0.5, 0.5, 0.5}
|
||||
```
|
||||
|
||||
Ogni riga corrisponde a un cuboide e l'insieme delle righe forma il nodo complesso: i primi tre numeri sono le coordinate (da -0.5 a 0.5) dell'angolo davanti in basso a sinistra, mentre gli altri tre equivalgono all'angolo opposto.
|
||||
Ogni riga corrisponde a un cuboide e l'insieme delle righe forma il nodo complesso:
|
||||
i primi tre numeri sono le coordinate (da -0.5 a 0.5) dell'angolo davanti in basso a sinistra,
|
||||
mentre gli altri tre equivalgono all'angolo opposto.
|
||||
Essi sono in formato X, Y, Z, dove Y indica il sopra.
|
||||
|
||||
|
||||
Puoi usare [NodeBoxEditor](https://forum.minetest.net/viewtopic.php?f=14&t=2840) per creare nodi complessi più facilmente, in quanto permette di vedere in tempo reale le modifiche sul nodo che si sta modellando.
|
||||
Puoi usare [NodeBoxEditor](https://forum.minetest.net/viewtopic.php?f=14&t=2840) per
|
||||
creare nodi complessi più facilmente, in quanto permette di vedere in tempo reale le modifiche
|
||||
sul nodo che si sta modellando.
|
||||
|
||||
### Nodi complessi a muro
|
||||
|
||||
Certe volte si vogliono avere nodi complessi che cambiano a seconda della loro posizione sul pavimento, sul muro e sul soffitto, come le torce.
|
||||
Certe volte si vogliono avere nodi complessi che cambiano a seconda della loro posizione
|
||||
sul pavimento, sul muro e sul soffitto, come le torce.
|
||||
|
||||
```lua
|
||||
core.register_node("default:sign_wall", {
|
||||
minetest.register_node("default:sign_wall", {
|
||||
drawtype = "nodebox",
|
||||
node_box = {
|
||||
type = "wallmounted",
|
||||
@ -306,16 +326,18 @@ core.register_node("default:sign_wall", {
|
||||
|
||||
## Nodi mesh
|
||||
|
||||
Mentre i nodi complessi sono generalmente più semplici da fare, essi sono limitati in quanto possono essere composti solo da cuboidi.
|
||||
I nodi complessi sono anche non ottimizzati: le facce interne, infatti, saranno comunque renderizzate, anche quando completamente nascoste.
|
||||
Mentre i nodi complessi sono generalmente più semplici da fare, essi sono limitati
|
||||
in quanto possono essere composti solo da cuboidi. I nodi complessi sono anche non ottimizzati:
|
||||
le facce interne, infatti, saranno comunque renderizzate, anche quando completamente nascoste.
|
||||
|
||||
Una faccia è una superficie piatta di una mesh.
|
||||
Una faccia interna appare quando le facce di due nodi complessi si sovrappongono, rendendo invisibili parti del modello ma renderizzandole comunque.
|
||||
Una faccia è una superficie piatta di una mesh. Una faccia interna appare quando le
|
||||
facce di due nodi complessi si sovrappongono, rendendo invisibili parti del modello
|
||||
ma renderizzandole comunque.
|
||||
|
||||
Puoi registrare un nodo mesh come segue:
|
||||
|
||||
```lua
|
||||
core.register_node("miamod:meshy", {
|
||||
minetest.register_node("miamod:meshy", {
|
||||
drawtype = "mesh",
|
||||
|
||||
-- Contiene le texture di ogni materiale
|
||||
@ -329,18 +351,20 @@ core.register_node("miamod:meshy", {
|
||||
```
|
||||
|
||||
Assicurati che la mesh sia presente nella cartella `models`.
|
||||
La maggior parte delle volte la mesh dovrebbe essere nella cartella della tua mod, tuttavia è ok condividere una mesh fornita da un'altra mod dalla quale dipendi.
|
||||
Per esempio, una mod che aggiunge più tipi di mobili potrebbe usfruire di un modello fornito da una mod di mobili base.
|
||||
La maggior parte delle volte la mesh dovrebbe essere nella cartella della tua mod, tuttavia
|
||||
è ok condividere una mesh fornita da un'altra mod dalla quale dipendi.
|
||||
Per esempio, una mod che aggiunge più tipi di mobili potrebbe usfruire di un modello
|
||||
fornito da una mod di mobili base.
|
||||
|
||||
## Nodi insegna
|
||||
|
||||
I nodi insegna (*signlike*) sono nodi piatti che possono essere affissi sulle facce di altri nodi.
|
||||
I nodi insegna (signlike) sono nodi piatti che possono essere affissi sulle facce di altri nodi.
|
||||
|
||||
Al contrario del loro nome, i cartelli non rientrano nei nodi insegna bensì in quelli complessi, per fornire un effetto 3D.
|
||||
I tipi insegna tuttavia, sono comunemente usati dalle scale a pioli.
|
||||
Al contrario del loro nome, i cartelli non rientrano nei nodi insegna bensì nei nodi complessi,
|
||||
per fornire un effetto 3D. I tipi insegna tuttavia, sono comunemente usati dalle scale a pioli.
|
||||
|
||||
```lua
|
||||
core.register_node("default:ladder_wood", {
|
||||
minetest.register_node("default:ladder_wood", {
|
||||
drawtype = "signlike",
|
||||
|
||||
tiles = {"default_ladder_wood.png"},
|
||||
@ -363,10 +387,10 @@ core.register_node("default:ladder_wood", {
|
||||
</figcaption>
|
||||
</figure>
|
||||
|
||||
I nodi pianta (*plantlike*) raffigurano la loro texture in un pattern a forma di X.
|
||||
I nodi pianta raffigurano la loro texture in un pattern a forma di X.
|
||||
|
||||
```lua
|
||||
core.register_node("default:papyrus", {
|
||||
minetest.register_node("default:papyrus", {
|
||||
drawtype = "plantlike",
|
||||
|
||||
-- Viene usata solo una texture
|
||||
@ -381,7 +405,8 @@ core.register_node("default:papyrus", {
|
||||
|
||||
## Nodi fiamma
|
||||
|
||||
I nodi fiamma (*firelike*) sono simili ai pianta, ad eccezione del fatto che sono ideati per avvinghiarsi ai muri e ai soffitti.
|
||||
I nodi fiamma (firelike) sono simili ai pianta, ad eccezione del fatto che sono
|
||||
ideati per "avvinghiarsi" ai muri e ai soffitti.
|
||||
|
||||
<figure>
|
||||
<img src="{{ page.root }}//static/drawtype_firelike.png" alt="Drawtype fiamma">
|
||||
@ -391,7 +416,7 @@ I nodi fiamma (*firelike*) sono simili ai pianta, ad eccezione del fatto che son
|
||||
</figure>
|
||||
|
||||
```lua
|
||||
core.register_node("miamod:avvinghiatutto", {
|
||||
minetest.register_node("miamod:avvinghiatutto", {
|
||||
drawtype = "firelike",
|
||||
|
||||
-- Viene usata solo una texture
|
||||
@ -401,7 +426,7 @@ core.register_node("miamod:avvinghiatutto", {
|
||||
|
||||
## Altri drawtype
|
||||
|
||||
Questa non è una lista esaustiva, in quanto ci sono infatti altri tipi di nodi come:
|
||||
Questa non è una lista definitiva, ci sono infatti altri tipi di nodi come:
|
||||
|
||||
* Nodi staccionata
|
||||
* Nodi pianta radicata - per quelle acquatiche
|
||||
@ -410,4 +435,5 @@ Questa non è una lista esaustiva, in quanto ci sono infatti altri tipi di nodi
|
||||
Le torce in Minetest Game usano in verità due diverse definizioni dei
|
||||
nodi mesh (default:torch e default:torch_wall).
|
||||
|
||||
Come al solito, consulta la [documentazione sull'API Lua](https://minetest.gitlab.io/minetest/nodes/#node-drawtypes) per l'elenco completo.
|
||||
Come al solito, consulta la [documentazione sull'API Lua](../../lua_api.html#node-drawtypes)
|
||||
per l'elenco completo.
|
||||
|
@ -13,29 +13,34 @@ Saper registrare nuovi nodi, oggetti fabbricabili e conseguenti ricette, è un r
|
||||
|
||||
- [Cosa sono i nodi e gli oggetti?](#cosa-sono-i-nodi-e-gli-oggetti)
|
||||
- [Registrare gli oggetti](#registrare-gli-oggetti)
|
||||
- [Nomi oggetto](#nomi-oggetto)
|
||||
- [Alias](#alias)
|
||||
- [Nomi oggetto e alias](#nomi-oggetto-e-alias)
|
||||
- [Texture](#texture)
|
||||
- [Registrare un nodo base](#registrare-un-nodo-base)
|
||||
- [Azioni e callback](#azioni-e-callback)
|
||||
- [on_use](#onuse)
|
||||
- [Fabbricazione](#fabbricazione)
|
||||
- [Fisse (shaped)](#fisse-shaped)
|
||||
- [Informi (shapeless)](#informi-shapeless)
|
||||
- [Cottura (cooking) e Carburante (fuel)](#cottura-cooking-e-carburante-fuel)
|
||||
- [Gruppi](#gruppi)
|
||||
- [Strumenti, Capacità e Friabilità](#strumenti-capacità-e-friabilità)
|
||||
- [Strumenti, Capacità e Friabilità](#strumenti-capacita-e-friabilita)
|
||||
|
||||
## Cosa sono i nodi e gli oggetti?
|
||||
|
||||
Nodi, oggetti fabbricabili e strumenti sono tutti oggetti.
|
||||
Un oggetto è qualcosa che può essere trovato in un inventario — anche se potrebbe non risultare possibile durante una normale sessione di gioco.
|
||||
Un oggetto è qualcosa che può essere trovato in un inventario —
|
||||
anche se potrebbe non risultare possibile durante una normale sessione di gioco.
|
||||
|
||||
Un nodo è un oggetto che può essere piazzato o trovato nel mondo.
|
||||
Ogni coordinata nel mondo deve essere occupata da un unico nodo — ciò che appare vuoto è solitamente un nodo d'aria.
|
||||
Ogni coordinata nel mondo deve essere occupata da un unico nodo —
|
||||
ciò che appare vuoto è solitamente un nodo d'aria.
|
||||
|
||||
Un oggetto fabbricabile (*craftitem*) non può essere invece piazzato, potendo apparire solo negli inventari o come oggetto rilasciato nel mondo.
|
||||
Un oggetto fabbricabile (*craftitem*) non può essere invece piazzato, potendo apparire solo negli inventari
|
||||
o come oggetto rilasciato nel mondo.
|
||||
|
||||
Uno strumento (*tool*) può usurarsi e solitamente non possiede la capacità di scavare.
|
||||
In futuro, è probabile che gli oggetti fabbricabili e gli strumenti verranno fusi in un unico tipo, in quanto la distinzione fra di essi è alquanto artificiosa.
|
||||
In futuro, è probabile che gli oggetti fabbricabili e gli strumenti verranno fusi in un unico tipo,
|
||||
in quanto la distinzione fra di essi è alquanto artificiosa.
|
||||
|
||||
## Registrare gli oggetti
|
||||
|
||||
@ -43,62 +48,67 @@ Le definizioni degli oggetti consistono in un *nome oggetto* e una *tabella di d
|
||||
La tabella di definizioni contiene attributi che influenzano il comportamento dell'oggetto.
|
||||
|
||||
```lua
|
||||
core.register_craftitem("nomemod:nomeoggetto", {
|
||||
minetest.register_craftitem("nomemod:nomeoggetto", {
|
||||
description = "Il Mio Super Oggetto",
|
||||
inventory_image = "nomemod_nomeoggetto.png"
|
||||
})
|
||||
```
|
||||
|
||||
### Nomi oggetto
|
||||
### Nomi oggetto e alias
|
||||
|
||||
Ogni oggetto ha un nome usato per riferirsi a esso, che dovrebbe seguire la seguente struttura:
|
||||
|
||||
nomemod:nomeoggetto
|
||||
|
||||
`nomemod` equivale appunto al nome della mod che registra l'oggetto, e `nomeoggetto` è il nome che si vuole assegnare a quest'ultimo.
|
||||
Esso dovrebbe essere inerente a quello che rappresenta e deve essere unico nella mod.
|
||||
|
||||
### Alias
|
||||
Il nomemod equivale appunto al nome della mod che registra l'oggetto, e nomeoggetto è
|
||||
il nome che si vuole assegnare a quest'ultimo.
|
||||
Il nome dell'oggetto dovrebbe essere inerente a quello che rappresenta e deve essere unico nella mod.
|
||||
|
||||
Gli oggetti possono anche avere degli *alias* che puntano al loro nome.
|
||||
Un *alias* è uno pseudonimo che dice al motore di gioco di trattarlo come se fosse il nome a cui punta.
|
||||
Ciò è comunemente usato in due casi:
|
||||
|
||||
* Rinominare gli oggetti rimossi in qualcos'altro.
|
||||
Ci potrebbero essere nodi sconosciuti nel mondo e negli inventari se un oggetto viene rimosso da una mod senza nessun codice per gestirlo.
|
||||
* Aggiungere una scorciatoia.
|
||||
`/giveme dirt` è più semplice di `/giveme default:dirt`.
|
||||
Ci potrebbero essere nodi sconosciuti nel mondo e negli inventari se un oggetto
|
||||
viene rimosso da una mod senza nessun codice per gestirlo.
|
||||
È importante evitare di assegnare come alias nomi di nodi inottenibili già esistenti
|
||||
al nodo rimosso, se quest'ultimo poteva essere ottenuto.
|
||||
* Aggiungere una scorciatoia. `/giveme dirt` è più semplice di `/giveme default:dirt`.
|
||||
|
||||
Registrare un alias è alquanto semplice.
|
||||
|
||||
```lua
|
||||
core.register_alias("dirt", "default:dirt")
|
||||
```
|
||||
|
||||
Un buon modo per ricordarne il funzionamento è `da → a`, dove *da*
|
||||
è l'alias e *a* è il nome dell'oggetto a cui punta.
|
||||
|
||||
Le mod devono inoltre assicurarsi di elaborare gli alias prima di occuparsi direttamente del nome dell'oggeto, in quanto l'engine non lo fa di suo.
|
||||
Anche in questo caso non è difficile:
|
||||
```lua
|
||||
minetest.register_alias("dirt", "default:dirt")
|
||||
```
|
||||
|
||||
Le mod devono assicurarsi di elaborare gli alias prima di occuparsi direttamente
|
||||
del nome dell'oggeto, in quanto l'engine non lo fa di suo.
|
||||
Ciò è comunque molto semplice:
|
||||
|
||||
```lua
|
||||
itemname = core.registered_aliases[itemname] or itemname
|
||||
itemname = minetest.registered_aliases[itemname] or itemname
|
||||
```
|
||||
|
||||
### Texture
|
||||
|
||||
Per convenzione le texture andrebbero messe nella cartella textures/ con nomi che seguono la struttura `nomemod_nomeoggetto.png`.\\
|
||||
Le immagini in JPEG sono supportate, ma non supportano la trasparenza e sono generalmente di cattiva qualità nelle basse risoluzioni.
|
||||
Per convenzione le texture andrebbero messe nella cartella textures/ con nomi che seguono la struttura
|
||||
`nomemod_nomeoggetto.png`.\\
|
||||
Le immagini in JPEG sono supportate, ma non supportano la trasparenza e sono generalmente
|
||||
di cattiva qualità nelle basse risoluzioni.
|
||||
Si consiglia quindi il formato PNG.
|
||||
|
||||
Le texture su Minetest sono generalmente 16x16 pixel.
|
||||
Possono essere di qualsiasi dimensione, ma è buona norma che rientrino nelle potenze di 2, per esempio 16, 32, 64 o 128.
|
||||
Questo perché dimensioni differenti potrebbero non essere supportate dai vecchi dispositivi, comportando una diminuzione delle performance.
|
||||
Possono essere di qualsiasi dimensione, ma è buona norma che rientrino nelle potenze di 2,
|
||||
per esempio 16, 32, 64 o 128.
|
||||
Questo perché dimensioni differenti potrebbero non essere supportate dai vecchi dispositivi,
|
||||
comportando una diminuzione delle performance.
|
||||
|
||||
## Registrare un nodo base
|
||||
|
||||
```lua
|
||||
core.register_node("miamod:diamante", {
|
||||
minetest.register_node("miamod:diamante", {
|
||||
description = "Diamante alieno",
|
||||
tiles = {"miamod_diamante.png"},
|
||||
is_ground_content = true,
|
||||
@ -116,7 +126,7 @@ Per assegnarne invece di diverse, bisogna fornire il nome di 6 texture in quest'
|
||||
Ricorda che su Minetest, come nella convenzione della computer grafica 3D, +Y punta verso l'alto.
|
||||
|
||||
```lua
|
||||
core.register_node("miamod:diamante", {
|
||||
minetest.register_node("miamod:diamante", {
|
||||
description = "Diamante alieno",
|
||||
tiles = {
|
||||
"miamod_diamante_up.png", -- y+
|
||||
@ -129,13 +139,57 @@ core.register_node("miamod:diamante", {
|
||||
is_ground_content = true,
|
||||
groups = {cracky = 3},
|
||||
drop = "miamod:diamante_frammenti"
|
||||
-- ^ Al posto di far cadere diamanti, fa cadere miamod:diamante_frammenti
|
||||
-- ^ Al posto di rilasciare diamanti, rilascia miamod:diamante_frammenti
|
||||
})
|
||||
```
|
||||
|
||||
L'attributo is_ground_content è essenziale per ogni nodo che si vuole far apparire sottoterra durante la generazione della mappa.
|
||||
L'attributo is_ground_content è essenziale per ogni nodo che si vuole far apparire sottoterra
|
||||
durante la generazione della mappa.
|
||||
Le caverne vengono scavate nel mondo dopo che tutti gli altri nodi nell'area sono stati generati.
|
||||
|
||||
## Azioni e callback
|
||||
|
||||
Minetest usa ampiamente una struttura per il modding incentrata sui callback (richiami).
|
||||
I callback possono essere inseriti nella tabella di definizioni dell'oggetto per permettere una
|
||||
risposta a vari tipi di eventi generati dall'utente.
|
||||
|
||||
### on_use
|
||||
|
||||
Di base, il callback on_use scatta quando un giocatore clicca col tasto sinistro con l'oggetto in mano.
|
||||
Avere un callback sull'uso previene che l'oggetto venga utilizzato per scavare nodi.
|
||||
Un utilizzo comune di questo callback è per il cibo:
|
||||
|
||||
```lua
|
||||
minetest.register_craftitem("miamod:fangotorta", {
|
||||
description = "Torta aliena di fango",
|
||||
inventory_image = "miamod_fangotorta.png",
|
||||
on_use = minetest.item_eat(20),
|
||||
})
|
||||
```
|
||||
|
||||
Il numero fornito alla funzione minetest.item_eat è il numero di punti salute ripristinati al
|
||||
consumare il cibo.
|
||||
In gioco ogni cuore equivale a due punti.
|
||||
Un giocatore ha solitamente un massimo di 10 cuori, ovvero 20 punti salute.
|
||||
Quest'ultimi non devono per forza essere interi, bensì possono anche essere decimali.
|
||||
|
||||
minetest.item_eat() è una funzione che ritorna un'altra funzione, in questo caso
|
||||
quindi impostandola come callback di on_use.
|
||||
Ciò significa che il codice in alto è alquanto simile al seguente:
|
||||
|
||||
```lua
|
||||
minetest.register_craftitem("miamod:fangotorta", {
|
||||
description = "Torta aliena di fango",
|
||||
inventory_image = "miamod_fangotorta.png",
|
||||
on_use = function(...)
|
||||
return minetest.do_item_eat(20, nil, ...)
|
||||
end,
|
||||
})
|
||||
```
|
||||
|
||||
Capendo come funziona item_eat, è possibile modificarlo per operazioni più complesse
|
||||
come per esempio riprodurre un suono personalizzato.
|
||||
|
||||
## Fabbricazione
|
||||
|
||||
Ci sono diversi tipi di ricette di fabbricazione disponibili, indicate dalla proprietà `type`.
|
||||
@ -146,15 +200,17 @@ Ci sono diversi tipi di ricette di fabbricazione disponibili, indicate dalla pro
|
||||
* fuel - Definisce gli oggetti che possono alimentare il fuoco nella fornace.
|
||||
* tool_repair - Definisce gli oggetti che possono essere riparati.
|
||||
|
||||
Le ricette di fabbricazione non sono oggetti, perciò non usano nomi oggetto per identificare in maniera univoca se stesse.
|
||||
Le ricette di fabbricazione non sono oggetti, perciò non usano nomi oggetto per
|
||||
identificare in maniera univoca se stesse
|
||||
|
||||
### Fisse (shaped)
|
||||
|
||||
Le ricette fisse avvengono quando gli ingredienti devono essere nella forma o sequenza corretta per funzionare.
|
||||
Nell'esempio sotto, i frammenti necessitano di essere in una figura a forma di sedia per poter fabbricare appunto 99 sedie.
|
||||
Le ricette fisse avvengono quando gli ingredienti devono essere nella forma o sequenza
|
||||
corretta per funzionare. Nell'esempio sotto, i frammenti necessitano di essere in
|
||||
in una figura a forma di sedia per poter fabbricare appunto 99 sedie.
|
||||
|
||||
```lua
|
||||
core.register_craft({
|
||||
minetest.register_craft({
|
||||
type = "shaped",
|
||||
output = "miamod:diamante_sedia 99",
|
||||
recipe = {
|
||||
@ -166,11 +222,12 @@ core.register_craft({
|
||||
```
|
||||
|
||||
Una cosa da tener presente è la colonna vuota sulla parte destra.
|
||||
Questo significa che ci *deve* essere una colonna vuota a destra della forma, altrimenti ciò non funzionerà.
|
||||
Questo significa che ci *deve* essere una colonna vuota a destra della forma, altrimenti
|
||||
ciò non funzionerà.
|
||||
Se invece la colonna non dovesse servire, basta ometterla in questo modo:
|
||||
|
||||
```lua
|
||||
core.register_craft({
|
||||
minetest.register_craft({
|
||||
output = "miamod:diamante_sedia 99",
|
||||
recipe = {
|
||||
{"miamod:diamante_frammenti", "" },
|
||||
@ -180,14 +237,16 @@ core.register_craft({
|
||||
})
|
||||
```
|
||||
|
||||
Il campo type non è davvero necessario per le ricette fisse, in quanto sono il tipo di base.
|
||||
Il campo type non è davvero necessario per le ricette fisse, in quanto sono
|
||||
il tipo di base.
|
||||
|
||||
### Informi (shapeless)
|
||||
|
||||
Le ricette informi sono ricette che vengono usate quando non importa dove sono posizionati gli ingredienti, ma solo che ci siano.
|
||||
Le ricette informi sono ricette che vengono usate quando non importa
|
||||
dove sono posizionati gli ingredienti, ma solo che ci siano.
|
||||
|
||||
```lua
|
||||
core.register_craft({
|
||||
minetest.register_craft({
|
||||
type = "shapeless",
|
||||
output = "miamod:diamante 3",
|
||||
recipe = {
|
||||
@ -200,10 +259,12 @@ core.register_craft({
|
||||
|
||||
### Cottura (cooking) e carburante (fuel)
|
||||
|
||||
Le ricette di tipo "cottura" non vengono elaborate nella griglia di fabbricazione, bensì nelle fornaci o in qualsivoglia altro strumento di cottura che può essere trovato nelle mod.
|
||||
Le ricette di tipo "cottura" non vengono elaborate nella griglia di fabbricazione,
|
||||
bensì nelle fornaci o in qualsivoglia altro strumento di cottura che può essere trovato
|
||||
nelle mod.
|
||||
|
||||
```lua
|
||||
core.register_craft({
|
||||
minetest.register_craft({
|
||||
type = "cooking",
|
||||
output = "miamod_diamante_frammenti",
|
||||
recipe = "default:coalblock",
|
||||
@ -211,29 +272,34 @@ core.register_craft({
|
||||
})
|
||||
```
|
||||
|
||||
L'unica vera differenza nel codice è che in questo la ricetta non è una tabella (tra parentesi graffe), bensì un singolo oggetto.
|
||||
Le ricette di cottura dispongono anche di un parametro aggiuntivo "cooktime" che indica in secondi quanto tempo ci impiega l'oggetto a cuocersi.
|
||||
L'unica vera differenza nel codice è che in questo la ricetta non è una tabella (tra parentesi graffe),
|
||||
bensì un singolo oggetto.
|
||||
Le ricette di cottura dispongono anche di un parametro aggiuntivo "cooktime"
|
||||
che indica in secondi quanto tempo ci impiega l'oggetto a cuocersi.
|
||||
Se non è impostato, di base è 3.
|
||||
|
||||
La ricetta qui sopra genera un'unità di frammenti di diamante dopo 10 secondi quando il blocco di carbone (`coalblock`) è nello slot di input, con un qualche tipo di carburante sotto di esso.
|
||||
La ricetta qui sopra genera un'unità di frammenti di diamante dopo 10 secondi quando
|
||||
il blocco di carbone (coalblock) è nello slot di input, con un qualche tipo di carburante sotto di esso.
|
||||
|
||||
Il tipo "carburante" invece funge da accompagnamento alle ricette di cottura, in quanto definisce cosa può alimentare il fuoco.
|
||||
Il tipo "carburante" invece funge da accompagnamento alle ricette di cottura,
|
||||
in quanto definisce cosa può alimentare il fuoco.
|
||||
|
||||
```lua
|
||||
core.register_craft({
|
||||
minetest.register_craft({
|
||||
type = "fuel",
|
||||
recipe = "miamod:diamante",
|
||||
burntime = 300,
|
||||
})
|
||||
```
|
||||
|
||||
Esso non ha un output come le altre ricette, e possiede un tempo di arsura (`burntime`) che definisce in secondi per quanto alimenterà la fiamma.
|
||||
In questo caso, 300 secondi!
|
||||
Esso non ha un output come le altre ricette, e possiede un tempo di arsura (burntime)
|
||||
che definisce in secondi per quanto alimenterà la fiamma. In questo caso, 300 secondi!
|
||||
|
||||
## Gruppi
|
||||
|
||||
Gli oggetti possono essere membri di più gruppi, e i gruppi possono avere più membri.
|
||||
Essi sono definiti usando la proprietà `groups` nella tabella di definizione, e possiedono un valore associato.
|
||||
Essi sono definiti usando la proprietà `groups` nella tabella di definizione,
|
||||
e possiedono un valore associato.
|
||||
|
||||
```lua
|
||||
groups = {cracky = 3, wood = 1}
|
||||
@ -241,10 +307,11 @@ groups = {cracky = 3, wood = 1}
|
||||
|
||||
Ci sono diverse ragioni per cui usare i gruppi.
|
||||
In primis, vengono utilizzati per descrivere proprietà come friabilità e infiammabilità.
|
||||
In secundis, possono essere usati in una ricetta al posto di un nome oggetto per permettere a qualsiasi oggetto nel gruppo di essere utilizzato.
|
||||
In secundis, possono essere usati in una ricetta al posto di un nome oggetto per permettere
|
||||
a qualsiasi oggetto nel gruppo di essere utilizzato.
|
||||
|
||||
```lua
|
||||
core.register_craft({
|
||||
minetest.register_craft({
|
||||
type = "shapeless",
|
||||
output = "miamod:diamante_qualcosa 3",
|
||||
recipe = {"group:wood", "miamod:diamante"}
|
||||
@ -253,9 +320,11 @@ core.register_craft({
|
||||
|
||||
## Strumenti, Capacità e Friabilità
|
||||
|
||||
Le friabilità sono dei gruppi particolari utilizzati per definire la resistenza di un nodo quando scavato con un determinato strumento.
|
||||
Le friabilità sono dei gruppi particolari utilizzati per definire la resistenza di un nodo
|
||||
quando scavato con un determinato strumento.
|
||||
Una friabilità elevata equivale a una maggior facilità e velocità nel romperlo.
|
||||
È possibile combinarne di più tipi per permettere al nodo di essere distrutto da più tipi di strumento, mentre un nodo senza friabilità non può essere distrutto da nessuno strumento.
|
||||
È possibile combinarne di più tipi per permettere al nodo di essere distrutto da più tipi di strumento.
|
||||
Un nodo senza friabilità non può essere distrutto da nessuno strumento.
|
||||
|
||||
| Gruppo | Miglior strumento | Descrizione |
|
||||
|---------|-------------------|-------------|
|
||||
@ -268,14 +337,18 @@ Una friabilità elevata equivale a una maggior facilità e velocità nel romperl
|
||||
| oddly_breakable_by_hand | *qualsiasi* | Torce e simili — molto veloci da rompere |
|
||||
|
||||
|
||||
Ogni strumento possiede poi delle capacità (*capability*).
|
||||
Una capacità include una lista di friabilità supportate, e proprietà associate per ognuna di esse come la velocità di scavata e il livello di usura.
|
||||
Gli strumenti possono anche avere una durezza massima supportata per ogni tipo; ciò serve a prevenire che strumenti più deboli possano rompere nodi meno friabili.
|
||||
È poi molto comune che uno strumento includa tutte le friabilità nelle sue capacità, con quelle meno adatte equivalenti a proprietà inefficienti.
|
||||
Se l'oggetto impugnato dal giocatore non ha una capacità esplicitata, verrà allora usata quella della mano.
|
||||
Ogni strumento possiede poi delle capacità (capability).
|
||||
Una capacità include una lista di friabilità supportate, e proprietà associate
|
||||
per ognuna di esse come la velocità di scavata e il livello di usura.
|
||||
Gli strumenti possono anche avere una durezza massima supportata per ogni tipo;
|
||||
ciò serve a prevenire che strumenti più deboli possano rompere nodi meno friabili.
|
||||
È poi molto comune che uno strumento includa tutte le friabilità nelle sue capacità,
|
||||
con quelle meno adatte equivalenti a proprietà inefficienti.
|
||||
Se l'oggetto impugnato dal giocatore non ha una capacità esplicitata,
|
||||
verrà allora usata quella della mano.
|
||||
|
||||
```lua
|
||||
core.register_tool("miamod:strumento", {
|
||||
minetest.register_tool("miamod:strumento", {
|
||||
description = "Il mio strumento",
|
||||
inventory_image = "miamod_strumento.png",
|
||||
tool_capabilities = {
|
||||
@ -292,5 +365,6 @@ core.register_tool("miamod:strumento", {
|
||||
},
|
||||
})
|
||||
```
|
||||
I gruppi limite (`groupcaps`) sono una lista delle friabilità supportate dallo strumento.
|
||||
I gruppi di danno invece (`damage_groups`) servono a controllare come uno strumento (esterno) danneggia quell'oggetto. Quest'ultimi verranno discussi in seguito nel capitolo Oggetti, Giocatori e Entità.
|
||||
I gruppi limite (groupcaps) sono una lista delle friabilità supportate dallo strumento.
|
||||
I gruppi di danno invece (damage_groups) servono a controllare come uno strumento (esterno) danneggia
|
||||
quell'oggetto. Quest'ultimi verranno discussi in seguito nel capitolo Oggetti, Giocatori e Entità.
|
||||
|
@ -7,7 +7,7 @@ description: Operazioni base come set_node e get_node
|
||||
redirect_from: /it/chapters/environment.html
|
||||
---
|
||||
|
||||
## Introduzione <!-- omit in toc -->
|
||||
## Introduction <!-- omit in toc -->
|
||||
|
||||
In questo capitolo imparerai come eseguire semplici azioni sulla mappa.
|
||||
|
||||
@ -44,7 +44,7 @@ I Blocchi Mappa esistenti, tuttavia, ignorano questo limite quando caricati dal
|
||||
Un nodo può essere letto da un mondo fornendone la posizione:
|
||||
|
||||
```lua
|
||||
local nodo = core.get_node({ x = 1, y = 3, z = 4 })
|
||||
local nodo = minetest.get_node({ x = 1, y = 3, z = 4 })
|
||||
print(dump(nodo)) --> { name=.., param1=.., param2=.. }
|
||||
```
|
||||
|
||||
@ -55,7 +55,7 @@ Se la posizione è un decimale, verrà arrotondata alle coordinate del nodo.
|
||||
* `param1` - Guarda la definizione dei nodi. È solitamente associato alla luce.
|
||||
* `param2` - Guarda la definizione dei nodi.
|
||||
|
||||
Per vedere se un nodo è caricato si può utilizzare `core.get_node_or_nil`, che ritornerà `nil` se il nome del nodo risulta `ignore`
|
||||
Per vedere se un nodo è caricato si può utilizzare `minetest.get_node_or_nil`, che ritornerà `nil` se il nome del nodo risulta `ignore`
|
||||
(la funzione non caricherà comunque il nodo).
|
||||
Potrebbe comunque ritornare `ignore` se un blocco contiene effettivamente `ignore`: questo succede ai limiti della mappa.
|
||||
|
||||
@ -67,14 +67,11 @@ Le più frequenti sono quelle per trovare i nodi.
|
||||
Per esempio, mettiamo che si voglia creare un certo tipo di pianta che cresce più velocemente vicino alla pietra;
|
||||
si dovrebbe controllare che ogni nodo nei pressi della pianta sia pietra, e modificarne il suo indice di crescita di conseguenza.
|
||||
|
||||
`core.find_node_near` ritornerà il primo nodo trovato in un dato raggio, combaciante con le informazioni passategli (nomi di nodi o gruppi).
|
||||
Nell'esempio che segue, andiamo alla ricerca di un nodo di mese nel raggio di 5 nodi:
|
||||
|
||||
```lua
|
||||
local vel_crescita = 1
|
||||
local pos_nodo = core.find_node_near(pos, 5, { "default:stone" })
|
||||
local pos_nodo = minetest.find_node_near(pos, 5, { "default:stone" })
|
||||
if pos_nodo then
|
||||
core.chat_send_all("Nodo trovato a: " .. dump(pos_nodo))
|
||||
minetest.chat_send_all("Nodo trovato a: " .. dump(pos_nodo))
|
||||
vel_crescita = 2
|
||||
end
|
||||
```
|
||||
@ -86,32 +83,30 @@ Si dovrebbe quindi usare una funzione in grado di trovare più nodi in un'area:
|
||||
local pos1 = vector.subtract(pos, { x = 5, y = 5, z = 5 })
|
||||
local pos2 = vector.add(pos, { x = 5, y = 5, z = 5 })
|
||||
local lista_pos =
|
||||
core.find_nodes_in_area(pos1, pos2, { "default:stone" })
|
||||
minetest.find_nodes_in_area(pos1, pos2, { "default:stone" })
|
||||
local vel_crescita = 1 + #lista_pos
|
||||
```
|
||||
|
||||
Il codice qui in alto ritorna il numero di nodi in un *volume cuboidale*.
|
||||
Il che è diverso da usare `find_node_near`, il quale usa la distanza dalla posizione data (cioé una *sfera*).
|
||||
Per ovviare a ciò, bisogna controllare l'intervallo manualmente.
|
||||
Il codice qui in alto, tuttavia, non fa proprio quello che ci serve, in quanto fa controlli basati su un'area, mentre `find_node_near` li fa su un intervallo.
|
||||
Per ovviare a ciò, bisogna purtroppo controllare l'intervallo manualmente.
|
||||
|
||||
```lua
|
||||
local pos1 = vector.subtract(pos, { x = 5, y = 5, z = 5 })
|
||||
local pos2 = vector.add(pos, { x = 5, y = 5, z = 5 })
|
||||
local lista_pos =
|
||||
core.find_nodes_in_area(pos1, pos2, { "default:stone" })
|
||||
minetest.find_nodes_in_area(pos1, pos2, { "default:stone" })
|
||||
local vel_crescita = 1
|
||||
for i=1, #lista_pos do
|
||||
local delta = vector.subtract(lista_pos[i], pos)
|
||||
if delta.x*delta.x + delta.y*delta.y + delta.z*delta.z <= 5*5 then
|
||||
if delta.x*delta.x + delta.y*delta.y <= 5*5 then
|
||||
vel_crescita = vel_crescita + 1
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
Ora il codice aumenterà correttamente `vel_crescita` basandosi su quanti nodi di pietra ci sono in un intervallo.
|
||||
|
||||
Notare come si sia comparata la distanza al quadrato dalla posizione, invece che calcolarne la radice quadrata per ottenerne la distanza vera e propria.
|
||||
Questo perché i computer trovano le radici quadrate computazionalmente pesanti, quindi dovrebbero essere evitate il più possibile.
|
||||
Questo perché i computer trovano le radici quadrate computazionalmente pesanti, quindi dovresti evitare di usarle il più possibile.
|
||||
|
||||
Ci sono altre variazioni delle due funzioni sopracitate, come `find_nodes_with_meta` e `find_nodes_in_area_under_air`, che si comportano in modo simile e sono utili in altre circostanze.
|
||||
|
||||
@ -120,23 +115,23 @@ Ci sono altre variazioni delle due funzioni sopracitate, come `find_nodes_with_m
|
||||
### Scrittura dei nodi
|
||||
|
||||
Puoi usare `set_node` per sovrascrivere nodi nella mappa.
|
||||
Ogni chiamata a `set_node` ricalcolerà la luce e richiamerà i suoi callback, il che significa che `set_node` è alquanto lento quando usato su un elevato numero di nodi.
|
||||
Ogni chiamata a set_node ricalcolerà la luce, il ché significa che set_node è alquanto lento quando usato su un elevato numero di nodi.
|
||||
|
||||
```lua
|
||||
core.set_node({ x = 1, y = 3, z = 4 }, { name = "default:stone" })
|
||||
minetest.set_node({ x = 1, y = 3, z = 4 }, { name = "default:stone" })
|
||||
|
||||
local nodo = core.get_node({ x = 1, y = 3, z = 4 })
|
||||
local nodo = minetest.get_node({ x = 1, y = 3, z = 4 })
|
||||
print(nodo.name) --> default:stone
|
||||
```
|
||||
|
||||
`set_node` rimuoverà ogni metadato e inventario associato a quel nodo: ciò non è sempre desiderabile, specialmente se si stanno usando
|
||||
set_node rimuoverà ogni metadato e inventario associato a quel nodo: ciò non è sempre desiderabile, specialmente se si stanno usando
|
||||
più definizioni di nodi per rappresentarne concettualmente uno. Un esempio è il nodo fornace: per quanto lo si immagini come un nodo unico,
|
||||
sono in verità due.
|
||||
|
||||
Si può impostare un nuovo nodo senza rimuoverne metadati e inventario con `swap_node`:
|
||||
|
||||
```lua
|
||||
core.swap_node({ x = 1, y = 3, z = 4 }, { name = "default:stone" })
|
||||
minetest.swap_node({ x = 1, y = 3, z = 4 }, { name = "default:stone" })
|
||||
```
|
||||
|
||||
### Rimozione dei nodi
|
||||
@ -146,15 +141,15 @@ Un nodo deve sempre essere presente. Per rimuoverlo, basta impostarlo uguale a `
|
||||
Le seguenti due linee di codice sono equivalenti, rimuovendo in entrambi i casi il nodo:
|
||||
|
||||
```lua
|
||||
core.remove_node(pos)
|
||||
core.set_node(pos, { name = "air" })
|
||||
minetest.remove_node(pos)
|
||||
minetest.set_node(pos, { name = "air" })
|
||||
```
|
||||
|
||||
Infatti, `remove_node` non fa altro che richiamare `set_node` con nome `air`.
|
||||
|
||||
## Caricamento blocchi
|
||||
|
||||
Puoi usare `core.emerge_area` per caricare i blocchi mappa.
|
||||
Puoi usare `minetest.emerge_area` per caricare i blocchi mappa.
|
||||
Questo comando è asincrono, ovvero i blocchi non saranno caricati istantaneamente; al contrario, verranno caricati man mano e il callback associato sarà richiamato a ogni passaggio.
|
||||
|
||||
```lua
|
||||
@ -164,7 +159,7 @@ local pos1 = vector.subtract(pos, mezza_dimensione)
|
||||
local pos2 = vector.add (pos, mezza_dimensione)
|
||||
|
||||
local param = {} -- dati persistenti tra un callback e l'altro
|
||||
core.emerge_area(pos1, pos2, mio_callback, param)
|
||||
minetest.emerge_area(pos1, pos2, mio_callback, param)
|
||||
```
|
||||
|
||||
Minetest chiamerà la funzione locale definita qua sotto `mio_callback` ogni volta che carica un blocco, con delle informazioni sul progresso.
|
||||
@ -183,12 +178,12 @@ local function mio_callback(pos, action,
|
||||
|
||||
-- Invia messaggio indicante il progresso
|
||||
if param.blocchi_totali == param.blocchi_caricati then
|
||||
core.chat_send_all("Ho finito di caricare blocchi!")
|
||||
else
|
||||
minetest.chat_send_all("Ho finito di caricare blocchi!")
|
||||
end
|
||||
local percentuale = 100 * param.blocchi_caricati / param.blocchi_totali
|
||||
local msg = string.format("Caricamento blocchi %d/%d (%.2f%%)",
|
||||
param.blocchi_caricati, param.blocchi_totali, percentuale)
|
||||
core.chat_send_all(msg)
|
||||
minetest.chat_send_all(msg)
|
||||
end
|
||||
end
|
||||
```
|
||||
@ -205,7 +200,7 @@ local mezza_dimensione = { x = 10, y = 10, z = 10 }
|
||||
local pos1 = vector.subtract(pos, mezza_dimensione)
|
||||
local pos2 = vector.add (pos, mezza_dimensione)
|
||||
|
||||
core.delete_area(pos1, pos2)
|
||||
minetest.delete_area(pos1, pos2)
|
||||
```
|
||||
|
||||
Questo cancellerà tutti i blocchi mappa in quell'area, anche quelli solo parzialmente selezionati.
|
||||
|
@ -1,5 +1,5 @@
|
||||
---
|
||||
title: Oggetti, giocatori ed entità
|
||||
title: Oggetti, giocatori e entità
|
||||
layout: default
|
||||
root: ../..
|
||||
idx: 3.4
|
||||
@ -15,14 +15,10 @@ degrad:
|
||||
|
||||
In questo capitolo imparerai come manipolare gli oggetti e come definirne di tuoi.
|
||||
|
||||
- [Cosa sono gli oggetti, i giocatori e le entità?](#cosa-sono-gli-oggetti-i-giocatori-e-le-entità)
|
||||
- [Posizione e velocità](#posizione-e-velocità)
|
||||
- [Proprietà degli oggetti](#proprietà-degli-oggetti)
|
||||
- [Entità](#entità)
|
||||
- [Salute e danno](#salute-e-danno)
|
||||
- [Punti vita (HP)](#punti-vita-hp)
|
||||
- [Pugni, Gruppi Danno e Gruppi Armatura](#pugni-gruppi-danno-e-gruppi-armatura)
|
||||
- [Esempi di calcolo del danno](#esempi-di-calcolo-del-danno)
|
||||
- [Cosa sono gli oggetti, i giocatori e le entità?](#cosa-sono-gli-oggetti-i-giocatori-e-le-entita)
|
||||
- [Posizione e velocità](#posizione-e-velocita)
|
||||
- [Proprietà degli oggetti](#proprieta-degli-oggetti)
|
||||
- [Entità](#entita)
|
||||
- [Oggetti figli](#oggetti-figli)
|
||||
- [Il tuo turno](#il-tuo-turno)
|
||||
|
||||
@ -43,7 +39,7 @@ Questa distinzione è resa meno chiara dal fatto che le entità sono controllate
|
||||
`get_pos` e `set_pos` permettono di ottenere e impostare la posizione di un oggetto.
|
||||
|
||||
```lua
|
||||
local giocatore = core.get_player_by_name("bob")
|
||||
local giocatore = minetest.get_player_by_name("bob")
|
||||
local pos = giocatore:get_pos()
|
||||
giocatore:set_pos({ x = pos.x, y = pos.y + 1, z = pos.z })
|
||||
```
|
||||
@ -90,6 +86,18 @@ Come mostrato nella prossima sezione, le entità possono avere delle proprietà
|
||||
Un'entità ha una tabella di definizione che ricorda quella degli oggetti (intesi come *items*).
|
||||
Questa tabella può contenere metodi di callback, proprietà iniziali e membri personalizzati.
|
||||
|
||||
Tuttavia, c'è una differenza sostanziale tra le due; perché quando un'entità appare (come quando viene creata o caricata) una nuova tabella viene generata per quell'entità, *ereditando* le proprietà dalla tabella originaria tramite una metatabella.
|
||||
Ci si riferisce solitamente a questa nuova tabella con Tabella di Entità Lua e può essere usata per immagazzinare variabili per quella specifica entità.
|
||||
|
||||
Le metatabelle rappresentano un aspetto importante di Lua, che bisogna tenere bene a mente in quanto sono una parte essenziale del linguaggio.
|
||||
|
||||
In parole povere, le metatabelle permettono di controllare come si comporta una tabella quando viene usata una certa sintassi in Lua.
|
||||
Vengono usate soprattutto per la loro abilità di usare un'altra tabella come prototipo, fungendo da valori di base di quest'ultima quando essa non contiene le proprietà e i metodi richiesti.
|
||||
|
||||
Mettiamo che si voglia accedere al campo X della tabella A.
|
||||
Se la tabella A ha quel campo, allora ritornerà normalmente.
|
||||
Tuttavia, se X non esiste ma esiste una metatabella B associata ad A, B verrà ispezionata alla ricerca di un eventuale X prima di ritornare (eventualmente) nil.
|
||||
|
||||
```lua
|
||||
local MiaEntita = {
|
||||
initial_properties = {
|
||||
@ -112,29 +120,16 @@ function MiaEntita:imposta_messaggio(msg)
|
||||
end
|
||||
```
|
||||
|
||||
Tuttavia, c'è una differenza sostanziale tra entità e oggetti; perché quando un'entità appare (come quando viene creata o caricata) una nuova tabella viene generata per quell'entità, *ereditando* le proprietà dalla tabella originaria tramite una metatabella.
|
||||
|
||||
<!--
|
||||
Questa eredità avviene usando una metatabella. Le metatabelle rappresentano un aspetto importante di Lua, che bisogna tenere bene a mente in quanto sono una parte essenziale del linguaggio.
|
||||
|
||||
In parole povere, le metatabelle permettono di controllare come si comporta una tabella quando viene usata una certa sintassi in Lua.
|
||||
Vengono usate soprattutto per la loro abilità di usare un'altra tabella come prototipo, fungendo da valori di base di quest'ultima quando essa non contiene le proprietà e i metodi richiesti.
|
||||
|
||||
Mettiamo che si voglia accedere al campo `x` della tabella `a` (`a.x`).
|
||||
Se la tabella `a` ha quel campo, allora ritornerà normalmente.
|
||||
Tuttavia, se `a.x` non esiste ma esiste una metatabella `b` associata ad `a`, `b` verrà ispezionata alla ricerca di un eventuale `b.x` da ritornare al posto di `nil`.
|
||||
-->
|
||||
|
||||
Sia la tabella di un ObjectRef che quella di un'entità forniscono modi per ottenerne la controparte:
|
||||
|
||||
```lua
|
||||
local entita = oggetto:get_luaentity()
|
||||
local oggetto = entita.object
|
||||
print("L'entità si trova a " .. core.pos_to_string(oggetto:get_pos()))
|
||||
print("L'entità si trova a " .. minetest.pos_to_string(oggetto:get_pos()))
|
||||
```
|
||||
|
||||
Ci sono diversi callback disponibili da usare per le entità.
|
||||
Una lista completa può essere trovata in [lua_api.md](https://minetest.gitlab.io/minetest/minetest-namespace-reference/#registered-definition-tables).
|
||||
Una lista completa può essere trovata in [lua_api.txt]({{ page.root }}/lua_api.html#registered-entities).
|
||||
|
||||
```lua
|
||||
function MiaEntita:on_step(dtime)
|
||||
@ -142,9 +137,9 @@ function MiaEntita:on_step(dtime)
|
||||
local pos_giu = vector.subtract(pos, vector.new(0, 1, 0))
|
||||
|
||||
local delta
|
||||
if core.get_node(pos_giu).name == "air" then
|
||||
if minetest.get_node(pos_giu).name == "air" then
|
||||
delta = vector.new(0, -1, 0)
|
||||
elseif core.get_node(pos).name == "air" then
|
||||
elseif minetest.get_node(pos).name == "air" then
|
||||
delta = vector.new(0, 0, 1)
|
||||
else
|
||||
delta = vector.new(0, 1, 0)
|
||||
@ -156,7 +151,7 @@ function MiaEntita:on_step(dtime)
|
||||
end
|
||||
|
||||
function MiaEntita:on_punch(hitter)
|
||||
core.chat_send_player(hitter:get_player_name(), self.message)
|
||||
minetest.chat_send_player(hitter:get_player_name(), self.message)
|
||||
end
|
||||
```
|
||||
|
||||
@ -167,14 +162,14 @@ Questo succede nella *Staticdata*, una stringa che contiene tutte le informazion
|
||||
|
||||
```lua
|
||||
function MiaEntita:get_staticdata()
|
||||
return core.write_json({
|
||||
return minetest.write_json({
|
||||
messaggio = self.messaggio,
|
||||
})
|
||||
end
|
||||
|
||||
function MiaEntita:on_activate(staticdata, dtime_s)
|
||||
if staticdata ~= "" and staticdata ~= nil then
|
||||
local data = core.parse_json(staticdata) or {}
|
||||
local data = minetest.parse_json(staticdata) or {}
|
||||
self:imposta_messaggio(data.messaggio)
|
||||
end
|
||||
end
|
||||
@ -190,14 +185,14 @@ Questo significa che il suo staticdata inizialmente potrebbe essere vuoto (dato
|
||||
Infine, c'è bisogno di registrare la tabella usando `register_entity`.
|
||||
|
||||
```lua
|
||||
core.register_entity("miamod:entita", MiaEntita)
|
||||
minetest.register_entity("miamod:entita", MiaEntita)
|
||||
```
|
||||
|
||||
L'entità può essere spawnata da una mod nel seguente modo:
|
||||
|
||||
```lua
|
||||
local pos = { x = 1, y = 2, z = 3 }
|
||||
local oggetto = core.add_entity(pos, "miamod:entita", nil)
|
||||
local oggetto = minetest.add_entity(pos, "miamod:entita", nil)
|
||||
```
|
||||
|
||||
Il terzo parametro è lo staticdata inziale.
|
||||
@ -207,82 +202,6 @@ Per impostare il messaggio, puoi usare la Tabella di Entità Lua:
|
||||
oggetto:get_luaentity():imposta_messaggio("ciao!")
|
||||
```
|
||||
|
||||
## Salute e danno
|
||||
|
||||
### Punti vita (HP)
|
||||
|
||||
Ogni oggetto ha un valore Punti Vita (HP), che rappresenta la salute attuale.
|
||||
Nei giocatori è inoltre possibile impostare il valore di salute massima tramite la proprietà `hp_max`.
|
||||
Al raggiungere gli 0 HP, un oggetto muore.
|
||||
|
||||
```lua
|
||||
local hp = oggetto:get_hp()
|
||||
oggetto:set_hp(hp + 3)
|
||||
```
|
||||
|
||||
### Pugni, Gruppi Danno e Gruppi Armatura
|
||||
|
||||
Il danno è la riduzione degli HP di un oggetto.
|
||||
Quest'ultimo può prendere "a pugni" un altro oggetto per infliggere danno.
|
||||
"A pugni" perché non si parla necessariamente di un pugno vero e proprio: può essere infatti un'esplosione, un fendente, e via dicendo.
|
||||
|
||||
Il danno complessivo è calcolato moltiplicando i Gruppi Danno del pugno con le vulnerabilità dell'obiettivo.
|
||||
Questo è poi eventualmente ridotto a seconda di quanto recente è stato il colpo precedente.
|
||||
Vedremo tra poco nel dettaglio quest'aspetto.
|
||||
|
||||
Proprio come i [Gruppi Danno dei nodi](../items/nodes_items_crafting.html#strumenti-capacità-e-friabilità), questi gruppi possono prendere qualsiasi nome e non necessitano di essere registrati.
|
||||
Tuttavia, si è soliti usare gli stessi nomi di quelli dei nodi.
|
||||
|
||||
La vulnerabilità di un oggetto a un certo tipo di danno dipende dalla sua [proprietà](#proprietà-degli-oggetti) `armor_groups`.
|
||||
Al contrario di quello che potrebbe far intendere il nome, `armor_groups` specifica la percentuale di danno subita da specifici Gruppi Danno, e non la resistenza.
|
||||
Se un Gruppo Danno non è elencato nei Gruppi Armatura di un oggetto, quest'ultimo ne sarà completamente immune.
|
||||
|
||||
```lua
|
||||
obiettivo:set_armor_groups({
|
||||
fleshy = 90,
|
||||
crumbly = 50,
|
||||
})
|
||||
```
|
||||
|
||||
Nell'esempio qui sopra, l'oggetto subirà il 90% di danno `fleshy` e 50% di quello `crumbly`.
|
||||
|
||||
Quando un giocatore prende "a pugni" un oggetto, i Gruppi Danno vengono estrapolati dall'oggetto che ha attualmente il mano.
|
||||
Negli altri casi, saranno le mod a decidere quali Gruppi Danno usare.
|
||||
|
||||
### Esempi di calcolo del danno
|
||||
|
||||
Prendiamo a pugni l'oggetto `target`:
|
||||
|
||||
```lua
|
||||
local capacita_oggetto = {
|
||||
full_punch_interval = 0.8,
|
||||
damage_groups = { fleshy = 5, choppy = 10 },
|
||||
|
||||
-- Questo è usato solo per scavare nodi, ma è comunque richiesto
|
||||
max_drop_level=1,
|
||||
groupcaps={
|
||||
fleshy={times={[1]=2.5, [2]=1.20, [3]=0.35}, uses=30, maxlevel=2},
|
||||
},
|
||||
}
|
||||
|
||||
local tempo_da_ultimo_pugno = capacita_oggetto.full_punch_interval
|
||||
obiettivo:punch(oggetto, tempo_da_ultimo_pugno, capacita_oggetto)
|
||||
```
|
||||
|
||||
Ora, calcoliamo a quanto ammonterà il danno.
|
||||
I Gruppi Danno del pugno sono `fleshy=5` e `choppy=10`, con l'obiettivo che prenderà 90% di danno da fleshy e 0% da choppy.
|
||||
|
||||
Per prima cosa, moltiplichiamo i Gruppi Danno per le vulnerabilità, e ne sommiamo il risultato.
|
||||
Poi, moltiplichiamo per un numero tra 0 e 1 a seconda di `tempo_da_ultimo_pugno`.
|
||||
|
||||
```lua
|
||||
= (5*90/100 + 10*0/100) * limit(tempo_da_ultimo_pugno / full_punch_interval, 0, 1)
|
||||
= (5*90/100 + 10*0/100) * 1
|
||||
= 4.5
|
||||
```
|
||||
|
||||
Dato che HP è un intero, il danno è arrotondato a 5 punti.
|
||||
|
||||
## Oggetti figli
|
||||
|
||||
Gli oggetti figli (*attachments*) si muovono quando il genitore - l'oggetto al quale sono legati - viene mosso.
|
||||
|
@ -9,19 +9,19 @@ redirect_from:
|
||||
- /it/map/node_metadata.html
|
||||
---
|
||||
|
||||
## Introduzione <!-- omit in toc -->
|
||||
## Introduction <!-- omit in toc -->
|
||||
|
||||
In questo capitolo imparerai i vari modi per immagazzinare dati.
|
||||
|
||||
- [Metadati](#metadati)
|
||||
- [Cos'è un metadato?](#cosè-un-metadato)
|
||||
- [Cos'è un metadato?](#cos-e-un-metadato)
|
||||
- [Ottenere i metadati di un oggetto](#ottenere-i-metadati-di-un-oggetto)
|
||||
- [Lettura e scrittura](#lettura-e-scrittura)
|
||||
- [Chiavi speciali](#chiavi-speciali)
|
||||
- [Immagazzinare tabelle](#immagazzinare-tabelle)
|
||||
- [Metadati privati](#metadati-privati)
|
||||
- [Tabelle Lua](#tabelle-lua)
|
||||
- [Storaggio Mod](#storaggio-mod)
|
||||
- [Storaggio mod](#storaggio-mod)
|
||||
- [Database](#database)
|
||||
- [Decidere quale usare](#decidere-quale-usare)
|
||||
- [Il tuo turno](#il-tuo-turno)
|
||||
@ -47,7 +47,7 @@ Il dato in sé, come il tipo di un nodo o la quantità di un ItemStack, non rien
|
||||
Se si conosce la posizione di un nodo, si possono ottenere i suoi metadati:
|
||||
|
||||
```lua
|
||||
local meta = core.get_meta({ x = 1, y = 2, z = 3 })
|
||||
local meta = minetest.get_meta({ x = 1, y = 2, z = 3 })
|
||||
```
|
||||
|
||||
Quelli dei giocatori e degli ItemStack invece sono ottenuti tramite `get_meta()`:
|
||||
@ -87,7 +87,7 @@ print(meta:get_string("count")) --> "3"
|
||||
Questo è utile, per esempio, per mostrare lo stato o il proprietario di un nodo.
|
||||
|
||||
`description` è usato negli ItemStack per sovrascrivere la descrizione al passare il mouse sopra l'oggetto in un formspec (come l'inventario, li vedremo più avanti).
|
||||
È possibile utilizzare `core.colorize()` per cambiarne il colore.
|
||||
È possibile utilizzare `minetest.colorize()` per cambiarne il colore.
|
||||
|
||||
`owner` è una chiave comune, usata per immagazzinare il nome del giocatore a cui appartiene l'oggetto o il nodo.
|
||||
|
||||
@ -100,14 +100,14 @@ Quello in Lua tende a essere molto più veloce e corrisponde al formato usato da
|
||||
|
||||
```lua
|
||||
local data = { username = "utente1", score = 1234 }
|
||||
meta:set_string("foo", core.serialize(data))
|
||||
meta:set_string("foo", minetest.serialize(data))
|
||||
|
||||
data = core.deserialize(meta:get_string("foo"))
|
||||
data = minetest.deserialize(minetest:get_string("foo"))
|
||||
```
|
||||
|
||||
### Metadati privati
|
||||
|
||||
Di base, tutti i metadati dei nodi sono inviati al client. Rendendo le loro chiavi private, questo invece non succede.
|
||||
Le voci nei metadati di un nodo possono essere segnate come private, senza venire quindi inviate al client (al contrario delle normali).
|
||||
|
||||
```lua
|
||||
meta:set_string("segreto", "asd34dn")
|
||||
@ -130,7 +130,7 @@ Lo spazio d'archiviazione della mod (*storage*) usa la stessa identica API dei m
|
||||
Il primo infatti è per mod, e può essere ottenuto solo durante l'inizializzazione - appunto - della mod.
|
||||
|
||||
```lua
|
||||
local memoria = core.get_mod_storage()
|
||||
local memoria = minetest.get_mod_storage()
|
||||
```
|
||||
|
||||
Nell'esempio è ora possibile manipolare lo spazio d'archiviazione come se fosse un metadato:
|
||||
@ -148,10 +148,10 @@ Dovresti rendere ciò opzionale, separando il come i dati vengono salvati e il d
|
||||
local backend
|
||||
if use_database then
|
||||
backend =
|
||||
dofile(core.get_modpath("miamod") .. "/backend_sqlite.lua")
|
||||
dofile(minetest.get_modpath("miamod") .. "/backend_sqlite.lua")
|
||||
else
|
||||
backend =
|
||||
dofile(core.get_modpath("miamod") .. "/backend_storage.lua")
|
||||
dofile(minetest.get_modpath("miamod") .. "/backend_storage.lua")
|
||||
end
|
||||
|
||||
backend.get_foo("a")
|
||||
@ -161,15 +161,15 @@ backend.set_foo("a", { score = 3 })
|
||||
Il file `backend_storage.lua` dell'esempio (puoi nominarlo come vuoi) dovrebbe includere l'implementazione del metodo di storaggio:
|
||||
|
||||
```lua
|
||||
local memoria = core.get_mod_storage()
|
||||
local memoria = minetest.get_mod_storage()
|
||||
local backend = {}
|
||||
|
||||
function backend.set_foo(key, value)
|
||||
memoria:set_string(key, core.serialize(value))
|
||||
memoria:set_string(key, minetest.serialize(value))
|
||||
end
|
||||
|
||||
function backend.get_foo(key)
|
||||
return core.deserialize(memoria:get_string(key))
|
||||
function backend.get_foo(key, value)
|
||||
return minetest.deserialize(memoria:get_string(key))
|
||||
end
|
||||
|
||||
return backend
|
||||
@ -182,7 +182,7 @@ Un ambiente non sicuro è una tabella disponibile solamente alle mod con accesso
|
||||
Gli ambienti non sicuri saranno trattati più nel dettaglio nel capitolo sulla [Sicurezza](../quality/security.html).
|
||||
|
||||
```lua
|
||||
local amb_nonsicuro = core.request_insecure_environment()
|
||||
local amb_nonsicuro = minetest.request_insecure_environment()
|
||||
assert(amb_nonsicuro, "Per favore aggiungi miamod a secure.trusted_mods nelle impostazioni")
|
||||
|
||||
local _sql = amb_nonsicuro.require("lsqlite3")
|
||||
@ -214,4 +214,4 @@ Si prestano bene per i grandi ammontare di dati.
|
||||
## Il tuo turno
|
||||
|
||||
* Crea un nodo che sparisce dopo essere stato colpito cinque volte.
|
||||
(Usa `on_punch` nella definizione del nodo e `core.set_node`)
|
||||
(Usa `on_punch` nella definizione del nodo e `minetest.set_node`)
|
||||
|
@ -32,25 +32,34 @@ A ogni nodo è associato un timer.
|
||||
Questi timer possono essere gestiti ottenendo un oggetto NodeTimerRef (quindi un riferimento, come già visto per gli inventari).
|
||||
|
||||
```lua
|
||||
local timer = core.get_node_timer(pos)
|
||||
local timer = minetest.get_node_timer(pos)
|
||||
timer:start(10.5) -- in secondi
|
||||
```
|
||||
|
||||
Nell'esempio sottostante controlliamo che un timer sia attivo (`is_started()`), da quanto (`get_elapsed()`), quanto manca (`get_timeout()`) e infine lo fermiamo (`stop()`)
|
||||
|
||||
```lua
|
||||
if timer:is_started() then
|
||||
print("Il timer sta andando, e gli rimangono " .. timer:get_timeout() .. " secondi!")
|
||||
print("Sono passati " .. timer:get_elapsed() .. " secondi")
|
||||
end
|
||||
|
||||
timer:stop()
|
||||
```
|
||||
|
||||
Quando un timer raggiunge lo zero, viene eseguito il metodo `on_timer`, che va dichiarato dentro la tabella di definizione del nodo.
|
||||
`on_timer` richiede un solo parametro, ovvero la posizione del nodo.
|
||||
|
||||
```lua
|
||||
core.register_node("porteautomatiche:porta_aperta", {
|
||||
minetest.register_node("porteautomatiche:porta_aperta", {
|
||||
on_timer = function(pos)
|
||||
core.set_node(pos, { name = "porteautomatiche:porta_chiusa" })
|
||||
minetest.set_node(pos, { name = "porteautomatiche:porta_chiusa" })
|
||||
return false
|
||||
end
|
||||
})
|
||||
```
|
||||
|
||||
Ritornando `true`, il timer ripartirà (con la stessa durata di prima).
|
||||
È inoltre possibile usare `get_node_timer(pos)` all'interno di `on_timer`, basta assicurarsi di ritornare `false` per evitare conflitti.
|
||||
|
||||
Ritornando true, il timer ripartirà (con la stessa durata di prima).
|
||||
|
||||
Potresti aver tuttavia notato una limitazione: per questioni di ottimizzazione, infatti, è possibile avere uno e un solo timer per tipo di nodo, e solo un timer attivo per nodo.
|
||||
|
||||
@ -60,15 +69,15 @@ Potresti aver tuttavia notato una limitazione: per questioni di ottimizzazione,
|
||||
Erba aliena, a scopo illustrativo del capitolo, è un tipo d'erba che ha una probabilità di apparire vicino all'acqua.
|
||||
|
||||
```lua
|
||||
core.register_node("alieni:erba", {
|
||||
minetest.register_node("alieni:erba", {
|
||||
description = "Erba Aliena",
|
||||
light_source = 3, -- Il nodo irradia luce. Min 0, max 14
|
||||
tiles = {"alieni_erba.png"},
|
||||
groups = {choppy=1},
|
||||
on_use = core.item_eat(20)
|
||||
on_use = minetest.item_eat(20)
|
||||
})
|
||||
|
||||
core.register_abm({
|
||||
minetest.register_abm({
|
||||
nodenames = {"default:dirt_with_grass"}, -- nodo sul quale applicare l'ABM
|
||||
neighbors = {"default:water_source", "default:water_flowing"}, -- nodi che devono essere nei suoi dintorni (almeno uno)
|
||||
interval = 10.0, -- viene eseguito ogni 10 secondi
|
||||
@ -76,7 +85,7 @@ core.register_abm({
|
||||
action = function(pos, node, active_object_count,
|
||||
active_object_count_wider)
|
||||
local pos = {x = pos.x, y = pos.y + 1, z = pos.z}
|
||||
core.set_node(pos, {name = "alieni:erba"})
|
||||
minetest.set_node(pos, {name = "alieni:erba"})
|
||||
end
|
||||
})
|
||||
```
|
||||
|
@ -8,18 +8,16 @@ redirect_from: /it/chapters/chat.html
|
||||
cmd_online:
|
||||
level: warning
|
||||
title: I giocatori offline possono eseguire comandi
|
||||
message: |
|
||||
Viene passato il nome del giocatore al posto del giocatore in sé perché le mod possono eseguire comandi in vece di un giocatore offline.
|
||||
Per esempio, il ponte IRC permette ai giocatori di eseguire comandi senza dover entrare in gioco.
|
||||
message: <p>Viene passato il nome del giocatore al posto del giocatore in sé perché le mod possono eseguire comandi in vece di un giocatore offline.
|
||||
Per esempio, il bridge IRC permette ai giocatori di eseguire comandi senza dover entrare in gioco.</p>
|
||||
|
||||
Assicurati quindi di non dar per scontato che un giocatore sia connesso.
|
||||
Puoi controllare ciò tramite `core.get_player_by_name`, per vedere se ritorna qualcosa o meno.
|
||||
<p>Assicurati quindi di non dar per scontato che un giocatore sia connesso.
|
||||
Puoi controllare ciò tramite <pre>minetest.get_player_by_name</pre>, per vedere se ritorna qualcosa o meno.</p>
|
||||
|
||||
cb_cmdsprivs:
|
||||
level: warning
|
||||
title: Privilegi e comandi
|
||||
message: |
|
||||
Il privilegio shout non è necessario per far sì che un giocatore attivi questo richiamo.
|
||||
message: Il privilegio shout non è necessario per far sì che un giocatore attivi questo callback.
|
||||
Questo perché i comandi sono implementati in Lua, e sono semplicemente dei messaggi in chat che iniziano con /.
|
||||
|
||||
---
|
||||
@ -28,23 +26,18 @@ cb_cmdsprivs:
|
||||
|
||||
Le mod possono interagire con la chat del giocatore, tra l'inviare messaggi, intercettarli e registrare dei comandi.
|
||||
|
||||
- [Inviare messaggi](#inviare-messaggi)
|
||||
- [A tutti i giocatori](#a-tutti-i-giocatori)
|
||||
- [A giocatori specifici](#a-giocatori-specifici)
|
||||
- [Inviare messaggi a tutti i giocatori](#inviare-messaggi-a-tutti-i-giocatori)
|
||||
- [Inviare messaggi a giocatori specifici](#inviare-messaggi-a-giocatori-specifici)
|
||||
- [Comandi](#comandi)
|
||||
- [Accettare più argomenti](#accettare-più-argomenti)
|
||||
- [Usare string.split](#usare-stringsplit)
|
||||
- [Usare i pattern Lua](#usare-i-pattern-lua)
|
||||
- [Intercettare i messaggi](#intercettare-i-messaggi)
|
||||
- [Complex Subcommands](#complex-subcommands)
|
||||
- [Intercettare i messaggi](#interecettare-i-messaggi)
|
||||
|
||||
## Inviare messaggi
|
||||
|
||||
### A tutti i giocatori
|
||||
## Inviare messaggi a tutti i giocatori
|
||||
|
||||
Per inviare un messaggio a tutti i giocatori connessi in gioco, si usa la funzione `chat_send_all`:
|
||||
|
||||
```lua
|
||||
core.chat_send_all("Questo è un messaggio visualizzabile da tutti")
|
||||
minetest.chat_send_all("Questo è un messaggio visualizzabile da tutti")
|
||||
```
|
||||
|
||||
Segue un esempio di come apparirerebbe in gioco:
|
||||
@ -55,12 +48,12 @@ Segue un esempio di come apparirerebbe in gioco:
|
||||
|
||||
Il messaggio appare su una nuova riga, per distinguerlo dai messaggi dei giocatori.
|
||||
|
||||
### A giocatori specifici
|
||||
## Inviare messaggi a giocatori specifici
|
||||
|
||||
Per inviare un messaggio a un giocatore in particolare, si usa invece la funzione `chat_send_player`:
|
||||
|
||||
```lua
|
||||
core.chat_send_player("Tizio", "Questo è un messaggio per Tizio")
|
||||
minetest.chat_send_player("Tizio", "Questo è un messaggio per Tizio")
|
||||
```
|
||||
|
||||
Questo messaggio viene mostrato esattamente come il precedente, ma solo, in questo caso, a Tizio.
|
||||
@ -70,7 +63,7 @@ Questo messaggio viene mostrato esattamente come il precedente, ma solo, in ques
|
||||
Per registrare un comando, per esempio `/foo`, si usa `register_chatcommand`:
|
||||
|
||||
```lua
|
||||
core.register_chatcommand("foo", {
|
||||
minetest.register_chatcommand("foo", {
|
||||
privs = {
|
||||
interact = true,
|
||||
},
|
||||
@ -82,53 +75,24 @@ core.register_chatcommand("foo", {
|
||||
|
||||
Nel codice qui in alto, `interact` è elencato come [privilegio](privileges.html) necessario; in altre parole, solo i giocatori che hanno quel privilegio possono usare il comando.
|
||||
|
||||
`param` è una stringa contenente tutto ciò che un giocatore scrive dopo il nome del comando.
|
||||
Per esempio, in `/grantme uno,due,tre`, `param` equivarrà a `uno,due,tre`.
|
||||
|
||||
I comandi ritornano un massimo di due valori, dove il primo è un booleano che indica l'eventuale successo, mentre il secondo è un messaggio da inviare all'utente.
|
||||
|
||||
{% include notice.html notice=page.cmd_online %}
|
||||
|
||||
## Sottocomandi complessi
|
||||
|
||||
### Accettare più argomenti
|
||||
È spesso necessario creare dei comandi complessi, come per esempio:
|
||||
|
||||
Non è raro che i comandi richiedano più argomenti, come per esempio `/squadra entra <nome_squadra>`.
|
||||
Ci sono due modi per implementare ciò: usare `string.split` o i pattern Lua.
|
||||
* `/msg <a> <messaggio>`
|
||||
* `/team entra <nometeam>`
|
||||
* `/team esci <nometeam>`
|
||||
* `/team elenco`
|
||||
|
||||
|
||||
#### Usare string.split
|
||||
|
||||
Una stringa può essere spezzettata in più parti tramite `string.split(" ")`:
|
||||
Questo viene solitamente reso possibile dai [pattern di Lua](https://www.lua.org/pil/20.2.html).
|
||||
I pattern sono un modo di estrapolare "cose" dal testo usando delle regole ben precise.
|
||||
|
||||
```lua
|
||||
local parti = param:split(" ")
|
||||
local cmd = parti[1]
|
||||
|
||||
if cmd == "entra" then
|
||||
local nome_squadra = parti[2]
|
||||
squadra.entra(name, nome_squadra)
|
||||
return true, "Sei dentro la squadra!"
|
||||
elseif cmd == "gioc_max" then
|
||||
local nome_squadra = parti[2]
|
||||
local gioc_max = tonumber(parti[3])
|
||||
if nome_squadra and gioc_max then
|
||||
return true, "Giocatori massimi della squadra " .. nome_squadra .. " impostati a " .. gioc_max
|
||||
else
|
||||
return false, "Uso: /squadra gioc_max <nome_squadra> <numero>"
|
||||
end
|
||||
else
|
||||
return false, "È necessario un comando"
|
||||
end
|
||||
```
|
||||
|
||||
|
||||
#### Usare i pattern Lua
|
||||
|
||||
[I pattern Lua](https://www.lua.org/pil/20.2.html) sono un modo per estrapolare pezzi di testo seguendo delle regole.
|
||||
Sono perfetti in caso di argomenti che contengono spazi, o comunque quando è richiesto un controllo più meticoloso dei parametri catturati.
|
||||
|
||||
```lua
|
||||
local a, msg = string.match(param, "^([%a%d_-]+) (.+)$")
|
||||
local a, msg = string.match(param, "^([%a%d_-]+) (*+)$")
|
||||
```
|
||||
|
||||
Il codice sovrastante implementa `/msg <a> <messaggio>`. Vediamo cos'è successo partendo da sinistra:
|
||||
@ -137,9 +101,9 @@ Il codice sovrastante implementa `/msg <a> <messaggio>`. Vediamo cos'è successo
|
||||
* `()` è un gruppo - qualsiasi cosa che combaci con ciò che è contenuto al suo interno verrà ritornato da string.match;
|
||||
* `[]` significa che i caratteri al suo interno sono accettati;
|
||||
* `%a` significa che accetta ogni lettera e `%d` ogni cifra.
|
||||
* `[%a%d_-]` significa che accetta ogni lettera, cifra, `_` e `-`.
|
||||
* `[%d%a_-]` significa che accetta ogni lettera, cifra, `_` e `-`.
|
||||
* `+` dice di combaciare ciò che lo precede una o più volte.
|
||||
* `.` dice di combaciare qualsiasi tipo di carattere.
|
||||
* `*` dice di combaciare qualsiasi tipo di carattere.
|
||||
* `$` dice di combaciare la fine della stringa.
|
||||
|
||||
Detto semplicemente, il pattern cerca un nome (una parola fatta di lettere, numeri, trattini o trattini bassi), poi uno spazio e poi il messaggio (uno o più caratteri, qualsiasi essi siano).
|
||||
@ -149,9 +113,7 @@ Questo è come molte mod implementano comandi complessi.
|
||||
Una guida più completa ai pattern è probabilmente quella su [lua-users.org](http://lua-users.org/wiki/PatternsTutorial) o la [documentazione PIL](https://www.lua.org/pil/20.2.html).
|
||||
|
||||
<p class="book_hide">
|
||||
C'è anche una libreria scritta dall'autore di questo libro che può essere usata
|
||||
per creare comandi complessi senza l'utilizzo di pattern:
|
||||
<a href="https://gitlab.com/rubenwardy/ChatCmdBuilder">Chat Command Builder</a>.
|
||||
C'è anche una libreria scritta dall'autore di questo libro che può essere usata per creare comandi complessi senza l'utilizzo di pattern: <a href="chat_complex.html">Chat Command Builder</a>.
|
||||
</p>
|
||||
|
||||
|
||||
@ -160,13 +122,13 @@ Una guida più completa ai pattern è probabilmente quella su [lua-users.org](ht
|
||||
Per intercettare un messaggio, si usa `register_on_chat_message`:
|
||||
|
||||
```lua
|
||||
core.register_on_chat_message(function(name, message)
|
||||
minetest.register_on_chat_message(function(name, message)
|
||||
print(name .. " ha detto " .. message)
|
||||
return false
|
||||
end)
|
||||
```
|
||||
|
||||
Ritornando `false`, si permette al messaggio di essere inviato.
|
||||
Ritornando false, si permette al messaggio di essere inviato.
|
||||
In verità `return false` può anche essere omesso in quanto `nil` verrebbe ritornato implicitamente, e nil è trattato come false.
|
||||
|
||||
{% include notice.html notice=page.cb_cmdsprivs %}
|
||||
@ -175,10 +137,10 @@ Dovresti assicurarti, poi, che il messaggio potrebbe essere un comando che invia
|
||||
o che l'utente potrebbere non avere `shout`.
|
||||
|
||||
```lua
|
||||
core.register_on_chat_message(function(name, message)
|
||||
minetest.register_on_chat_message(function(name, message)
|
||||
if message:sub(1, 1) == "/" then
|
||||
print(name .. " ha eseguito un comando")
|
||||
elseif core.check_player_privs(name, { shout = true }) then
|
||||
elseif minetest.check_player_privs(name, { shout = true }) then
|
||||
print(name .. " ha detto " .. message)
|
||||
else
|
||||
print(name .. " ha provato a dire " .. message ..
|
||||
|
173
_it/players/chat_complex.md
Normal file
173
_it/players/chat_complex.md
Normal file
@ -0,0 +1,173 @@
|
||||
---
|
||||
title: Chat Command Builder
|
||||
layout: default
|
||||
root: ../..
|
||||
idx: 4.3
|
||||
description: Creazione di comandi complessi semplificandosi la vita
|
||||
redirect_from: /it/chapters/chat_complex.html
|
||||
---
|
||||
|
||||
## Introduzione <!-- omit in toc -->
|
||||
|
||||
Questo capitolo ti mostrerà come creare comandi complessi con ChatCmdBuilder, come `/msg <nome> <messaggio>`, `/team entra <nometeam>` or `/team esci <nometeam>`.
|
||||
|
||||
Tieni conto che ChatCmdBuilder è una libreria creata dall'autore di questo libro, e che molti modder tendono a usare il metodo illustrato nel capitolo [Chat e comandi](chat.html#complex-subcommands).
|
||||
|
||||
- [Perché ChatCmdBuilder?](#perche-chatcmdbuilder)
|
||||
- [Tratte](#tratte)
|
||||
- [Funzioni nei sottocomandi](#funzioni-nei-sottocomandi)
|
||||
- [Installare ChatCmdBuilder](#installare-chatcmdbuilder)
|
||||
- [Esempio: comando complesso /admin](#esempio-comando-complesso-admin)
|
||||
|
||||
## Perché ChatCmdBuilder?
|
||||
|
||||
Le mod tradizionali implementano questi comandi complessi usando i pattern Lua.
|
||||
|
||||
```lua
|
||||
local nome = string.match(param, "^join ([%a%d_-]+)")
|
||||
```
|
||||
|
||||
Io, tuttavia, trovo i pattern Lua illeggibili e scomodi.
|
||||
Per via di ciò, ho creato una libreria che ti semplifichi la vita.
|
||||
|
||||
```lua
|
||||
ChatCmdBuilder.new("sethp", function(cmd)
|
||||
cmd:sub(":target :hp:int", function(name, target, hp)
|
||||
local giocatore = minetest.get_player_by_name(target)
|
||||
if giocatore then
|
||||
giocatore:set_hp(hp)
|
||||
return true, "Gli hp di " .. target .. " sono ora " .. hp
|
||||
else
|
||||
return false, "Giocatore " .. target .. " non trovato"
|
||||
end
|
||||
end)
|
||||
end, {
|
||||
description = "Imposta gli hp del giocatore",
|
||||
privs = {
|
||||
kick = true
|
||||
-- ^ è probabilmente meglio impostare un nuovo privilegio
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
`ChatCmdBuilder.new(name, setup_func, def)` crea un nuovo comando chiamato `name`.
|
||||
Poi, chiama la funzione passatagli (`setup_func`), che crea a sua volta i sottocomandi.
|
||||
Ogni `cmd:sub(route, func)` è un sottocomando.
|
||||
|
||||
Un sottocomando è una particolare risposta a un parametro di input.
|
||||
Quando un giocatore esegue il comando, il primo sottocomando che combacia con l'input verrà eseguito.
|
||||
Se non ne viene trovato nessuno, il giocatore verrà avvisato della sintassi non valida.
|
||||
Nel codice qui in alto, per esempio, se qualcuno scrive qualcosa come `/sethp nickname 12`, la funzione corrispondente verrà chiamata.
|
||||
Tuttavia, qualcosa come `/sethp 12 bleh` genererà un messaggio d'errore.
|
||||
|
||||
`:name :hp:int` è una tratta.
|
||||
Descrive il formato del parametro passato a /teleport.
|
||||
|
||||
## Tratte
|
||||
|
||||
Una tratta è composta di fermate e variabili, dove le prime sono obbligatorie.
|
||||
Una fermata è per esempio `crea` in `/team crea :nometeam :giocatorimassimi:int`, ma anche gli spazi contano come tali.
|
||||
|
||||
Le variabili possono cambiare valore a seconda di cosa scrive l'utente. Per esempio `:nometeam` e `:giocatorimassimi:int`.
|
||||
|
||||
Le variabili sono definite con `:nome:tipo`: il nome è usato nella documentazione, mentre il tipo è usato per far combaciare l'input.
|
||||
Se il tipo non è specificato, allora sarà di base `word`.
|
||||
|
||||
I tipi consentiti sono:
|
||||
|
||||
* `word` - Predefinito. Qualsiasi stringa senza spazi;
|
||||
* `int` - Qualsiasi numero intero;
|
||||
* `number` - Qualsiasi numero, decimali inclusi;
|
||||
* `pos` - Coordinate. Il formato può essere 1,2,3, o 1.1,2,3.4567, o (1,2,3), o ancora 1.2, 2 ,3.2;
|
||||
* `text` - Qualsiasi stringa, spazi inclusi. Può esserci solo un `text` e non può essere seguito da nient'altro.
|
||||
|
||||
In `:nome :hp:int`, ci sono due variabili:
|
||||
|
||||
* `name` - di tipo `word` in quanto non è stato specificato
|
||||
* `hp` - di tipo `int`, quindi un numero intero
|
||||
|
||||
## Funzioni nei sottocomandi
|
||||
|
||||
Il primo parametro è il nome di chi invia il comando. Le variabili sono poi passate alla funzione nell'ordine in cui sono state dichiarate.
|
||||
|
||||
```lua
|
||||
cmd:sub(":target :hp:int", function(name, target, hp)
|
||||
-- funzione del sottocomando
|
||||
end)
|
||||
```
|
||||
|
||||
## Installare ChatCmdBuilder
|
||||
|
||||
Il codice sorgente può essere trovato e scaricato su
|
||||
[Github](https://github.com/rubenwardy/ChatCmdBuilder/).
|
||||
|
||||
Ci sono due modi per installarlo:
|
||||
|
||||
1. Installarlo come una mod a sé stante;
|
||||
2. Includere nella tua mod l'init.lua di ChatCmdBuilder rinominandolo chatcmdbuilder.lua, e integrarlo tramite `dofile`.
|
||||
|
||||
## Esempio: comando complesso /admin
|
||||
|
||||
Segue un esempio che crea un comando che aggiunge le seguenti funzioni per chi ha il permesso `kick` e `ban` (quindi, in teoria, un admin):
|
||||
|
||||
* `/admin uccidi <nome>` - uccide un utente;
|
||||
* `/admin sposta <nome> a <pos>` - teletrasporta un utente;
|
||||
* `/admin log <nome>` - mostra il log di un utente;
|
||||
* `/admin log <nome> <messaggio>` - aggiunge un messaggio al log di un utente.
|
||||
|
||||
```lua
|
||||
local admin_log
|
||||
local function carica()
|
||||
admin_log = {}
|
||||
end
|
||||
local function salva()
|
||||
-- todo
|
||||
end
|
||||
carica()
|
||||
|
||||
ChatCmdBuilder.new("admin", function(cmd)
|
||||
cmd:sub("uccidi :nome", function(name, target)
|
||||
local giocatore = minetest.get_player_by_name(target)
|
||||
if giocatore then
|
||||
giocatore:set_hp(0)
|
||||
return true, "Hai ucciso " .. target
|
||||
else
|
||||
return false, "Unable to find " .. target
|
||||
end
|
||||
end)
|
||||
|
||||
cmd:sub("sposta :nome to :pos:pos", function(nome, target, pos)
|
||||
local giocatore = minetest.get_player_by_name(target)
|
||||
if giocatore then
|
||||
giocatore:setpos(pos)
|
||||
return true, "Giocatore " .. target .. " teletrasportato a " ..
|
||||
minetest.pos_to_string(pos)
|
||||
else
|
||||
return false, "Giocatore " .. target .. " non trovato"
|
||||
end
|
||||
end)
|
||||
|
||||
cmd:sub("log :nome", function(name, target)
|
||||
local log = admin_log[target]
|
||||
if log then
|
||||
return true, table.concat(log, "\n")
|
||||
else
|
||||
return false, "Nessuna voce per " .. target
|
||||
end
|
||||
end)
|
||||
|
||||
cmd:sub("log :nome :messaggio", function(name, target, messaggio)
|
||||
local log = admin_log[target] or {}
|
||||
table.insert(log, messaggio)
|
||||
admin_log[target] = log
|
||||
salva()
|
||||
return true, "Aggiunto"
|
||||
end)
|
||||
end, {
|
||||
description = "Strumenti per gli admin",
|
||||
privs = {
|
||||
kick = true,
|
||||
ban = true
|
||||
}
|
||||
})
|
||||
```
|
@ -48,8 +48,6 @@ Il modo in cui elementi diversi venivano posizionati nel formspec variava in man
|
||||
Da Minetest 5.1.0, tuttavia, è stata introdotta una funzione chiamata Coordinate Reali (*real coordinates*), la quale punta a correggere questo comportamento tramite l'introduzione di un sistema di coordinate coerente.
|
||||
L'uso delle coordinate reali è caldamente consigliato, onde per cui questo capitolo non tratterà di quelle vecchie.
|
||||
|
||||
Usando una versione del formspec maggiore o uguale a 2, esse saranno abilitate di base.
|
||||
|
||||
## Anatomia di un formspec
|
||||
|
||||
### Elementi
|
||||
@ -66,7 +64,7 @@ Si possono concatenare più elementi, piazzandoli eventualmente su più linee:
|
||||
bo[param1]
|
||||
|
||||
Gli elementi sono o oggetti come i campi di testo e i pulsanti, o dei metadati come la grandezza e lo sfondo.
|
||||
Per una lista esaustiva di tutti i possibili elementi, si rimanda a [lua_api.md](https://minetest.gitlab.io/minetest/formspec/).
|
||||
Per una lista esaustiva di tutti i possibili elementi, si rimanda a [lua_api.txt](../../lua_api.html#elements).
|
||||
|
||||
### Intestazione
|
||||
|
||||
@ -74,12 +72,12 @@ L'intestazione di un formspec contiene informazioni che devono apparire prima di
|
||||
Questo include la grandezza del formspec, la posizione, l'ancoraggio, e se il tema specifico del gioco debba venir applicato.
|
||||
|
||||
Gli elementi nell'intestazione devono essere definiti in un ordine preciso, altrimenti ritorneranno un errore.
|
||||
L'ordine è dato nel paragrafo qui in alto e, come sempre, documentato in lua_api.md.
|
||||
L'ordine è dato nel paragrafo qui in alto e, come sempre, documentato in [lua_api.txt](../../lua_api.html#sizewhfixed_size).
|
||||
|
||||
La grandezza è in caselle formspec - un'unità di misura che è circa 64 pixel, ma varia a seconda della densità dello schermo e delle impostazioni del client.
|
||||
Ecco un formspec di 2x2:
|
||||
|
||||
formspec_version[4]
|
||||
formspec_version[3]
|
||||
size[2,2]
|
||||
|
||||
Notare come è stata esplicitamente definita la versione del linguaggio: senza di essa, il sistema datato sarebbe stato usato di base - che avrebbe impossibilitato il posizionamento coerente degli elementi e altre nuove funzioni.
|
||||
@ -88,8 +86,9 @@ La posizione e l'ancoraggio degli elementi sono usati per collocare il formspec
|
||||
La posizione imposta dove si troverà (con valore predefinito al centro, `0.5,0.5`), mentre l'ancoraggio da dove partire, permettendo di allineare il formspec con i bordi dello schermo.
|
||||
Per esempio, lo si può posizionare ancorato a sinistra in questo modo:
|
||||
|
||||
formspec_version[4]
|
||||
formspec_version[3]
|
||||
size[2,2]
|
||||
real_coordinates[true]
|
||||
position[0,0.5]
|
||||
anchor[0,0.5]
|
||||
|
||||
@ -121,9 +120,8 @@ function indovina.prendi_formspec(nome)
|
||||
local testo = "Sto pensando a un numero... Prova a indovinare!"
|
||||
|
||||
local formspec = {
|
||||
"formspec_version[4]",
|
||||
"size[6,3.476]",
|
||||
"label[0.375,0.5;", core.formspec_escape(testo), "]",
|
||||
"label[0.375,0.5;", minetest.formspec_escape(testo), "]",
|
||||
"field[0.375,1.25;5.25,0.8;numero;Numero;]",
|
||||
"button[1.5,2.3;3,0.8;indovina;Indovina]"
|
||||
}
|
||||
@ -143,10 +141,10 @@ Il metodo principale per farlo è usare `show_formspec`:
|
||||
|
||||
```lua
|
||||
function indovina.mostra_a(nome)
|
||||
core.show_formspec(nome, "indovina:gioco", indovina.prendi_formspec(nome))
|
||||
minetest.show_formspec(nome, "indovina:gioco", indovina.prendi_formspec(nome))
|
||||
end
|
||||
|
||||
core.register_chatcommand("gioco", {
|
||||
minetest.register_chatcommand("gioco", {
|
||||
func = function(name)
|
||||
indovina.mostra_a(name)
|
||||
end,
|
||||
@ -180,19 +178,19 @@ Per far sì che i formspec siano utili, le informazioni devono essere ritornate
|
||||
Il metodo per fare ciò è chiamato Campo di Compilazione (*formspec field submission*), e per `show_formspec` quel campo viene ottenuto usando un callback globale:
|
||||
|
||||
```lua
|
||||
core.register_on_player_receive_fields(function(player, formname, fields)
|
||||
minetest.register_on_player_receive_fields(function(player, formname, fields)
|
||||
if formname ~= "indovina:gioco" then
|
||||
return
|
||||
end
|
||||
|
||||
if fields.indovina then
|
||||
local p_name = player:get_player_name()
|
||||
core.chat_send_all(p_name .. " ha tentato di indovinare con il numero " .. fields.numero)
|
||||
minetest.chat_send_all(p_name .. " ha tentato di indovinare con il numero " .. fields.numero)
|
||||
end
|
||||
end)
|
||||
```
|
||||
|
||||
La funzione data in `core.register_on_player_receive_fields` è chiamata ogni volta che un utente invia un modulo.
|
||||
La funzione data in `minetest.register_on_player_receive_fields` è chiamata ogni volta che un utente invia un modulo.
|
||||
La maggior parte dei callback necessiteranno di controllare il nome fornito alla funzione, e uscire se non è quello esatto; tuttavia, alcuni potrebbero necessitare di operare su più moduli, se non addirittura su tutti.
|
||||
|
||||
Il parametro `fields` è una tabella di tutti i valori inviati dall'utente, indicizzati per stringhe.
|
||||
@ -223,7 +221,7 @@ local function prendi_contesto(nome)
|
||||
return contesto
|
||||
end
|
||||
|
||||
core.register_on_leaveplayer(function(player)
|
||||
minetest.register_on_leaveplayer(function(player)
|
||||
_contexts[player:get_player_name()] = nil
|
||||
end)
|
||||
```
|
||||
@ -236,7 +234,7 @@ function indovina.mostra_a(nome)
|
||||
contesto.soluzione = contesto.soluzione or math.random(1, 10)
|
||||
|
||||
local formspec = indovina.prendi_formspec(nome, contesto)
|
||||
core.show_formspec(nome, "indovina:gioco", formspec)
|
||||
minetest.show_formspec(nome, "indovina:gioco", formspec)
|
||||
end
|
||||
```
|
||||
|
||||
@ -282,11 +280,11 @@ Ci sono tre diversi modi per far sì che un formspec sia consegnato al client:
|
||||
|
||||
### Formspec nei nodi
|
||||
|
||||
`core.show_formspec` non è l'unico modo per mostrare un formspec; essi possono infatti essere aggiunti anche ai [metadati di un nodo](../map/storage.html).
|
||||
`minetest.show_formspec` non è l'unico modo per mostrare un formspec; essi possono infatti essere aggiunti anche ai [metadati di un nodo](node_metadata.html).
|
||||
Per esempio, questo è usato con le casse per permettere tempi più veloci d'apertura - non si ha bisogno di aspettare che il server invii il formspec della cassa al giocatore.
|
||||
|
||||
```lua
|
||||
core.register_node("miamod:tastodestro", {
|
||||
minetest.register_node("miamod:tastodestro", {
|
||||
description = "Premimi col tasto destro del mouse!",
|
||||
tiles = {"miamod_tastodestro.png"},
|
||||
groups = {cracky = 1},
|
||||
@ -295,9 +293,9 @@ core.register_node("miamod:tastodestro", {
|
||||
-- Il codice che segue imposta il formspec della cassa.
|
||||
-- I metadati sono un modo per immagazzinare dati nel nodo.
|
||||
|
||||
local meta = core.get_meta(pos)
|
||||
local meta = minetest.get_meta(pos)
|
||||
meta:set_string("formspec",
|
||||
"formspec_version[4]" ..
|
||||
"formspec_version[3]" ..
|
||||
"size[5,5]"..
|
||||
"label[1,1;Questo è mostrato al premere col destro]"..
|
||||
"field[1,2;2,1;x;x;]")
|
||||
@ -312,7 +310,7 @@ core.register_node("miamod:tastodestro", {
|
||||
I formspec impostati in questo modo non innescano lo stesso callback.
|
||||
Per far in modo di ricevere il modulo di input per i formspec nei nodi, bisogna includere una voce `on_receive_fields` al registrare il nodo.
|
||||
|
||||
Questo stile di callback viene innescato al premere invio in un campo, che è possibile grazie a `core.show_formspec`; tuttavia, questi tipi di moduli possono essere mostrati solo
|
||||
Questo stile di callback viene innescato al premere invio in un campo, che è possibile grazie a `minetest.show_formspec`; tuttavia, questi tipi di moduli possono essere mostrati solo
|
||||
tramite il premere col tasto destro del mouse su un nodo. Non è possibile farlo programmaticamente.
|
||||
|
||||
### Inventario del giocatore
|
||||
@ -321,7 +319,7 @@ L'inventario del giocatore è un formspec, che viene mostrato al premere "I".
|
||||
Il callback globale viene usato per ricevere eventi dall'inventario, e il suo nome è `""`.
|
||||
|
||||
Ci sono svariate mod che permettono ad altrettante mod di personalizzare l'inventario del giocatore.
|
||||
La mod ufficialmente raccomandata è [SFINV](https://github.com/rubenwardy/sfinv/blob/master/Tutorial.md), ed è inclusa in Minetest Game.
|
||||
La mod ufficialmente raccomandata è [Simple Fast Inventory (sfinv)](sfinv.html), ed è inclusa in Minetest Game.
|
||||
|
||||
### Il tuo turno
|
||||
|
||||
|
@ -18,10 +18,10 @@ Le HUD, infatti, non accettano input dall'utente, lasciando quel ruolo ai [forms
|
||||
- [Esempio: tabellone segnapunti](#esempio-tabellone-segnapunti)
|
||||
- [Elementi di testo](#elementi-di-testo)
|
||||
- [Parametri](#parametri)
|
||||
- [Tornando all'esempio](#tornando-allesempio)
|
||||
- [Tornando all'esempio](#tornando-all-esempio)
|
||||
- [Elementi immagine](#elementi-immagine)
|
||||
- [Parametri](#parametri-1)
|
||||
- [Tornando all'esempio](#tornando-allesempio-1)
|
||||
- [Tornando all'esempio](#tornando-all-esempio-1)
|
||||
- [Cambiare un elemento](#cambiare-un-elemento)
|
||||
- [Salvare gli ID](#salvare-gli-id)
|
||||
- [Altri elementi](#altri-elementi)
|
||||
@ -79,7 +79,7 @@ Questo permette all'intero pannello di essere ancorato sulla destra della finest
|
||||
Puoi creare un elemento HUD una volta ottenuto il riferimento al giocatore al quale assegnarla:
|
||||
|
||||
```lua
|
||||
local giocatore = core.get_player_by_name("tizio")
|
||||
local giocatore = minetest.get_player_by_name("tizio")
|
||||
local idx = giocatore:hud_add({
|
||||
hud_elem_type = "text",
|
||||
position = {x = 0.5, y = 0.5},
|
||||
@ -268,9 +268,9 @@ function punteggio.aggiorna_hud(giocatore)
|
||||
end
|
||||
end
|
||||
|
||||
core.register_on_joinplayer(punteggio.aggiorna_hud)
|
||||
minetest.register_on_joinplayer(punteggio.aggiorna_hud)
|
||||
|
||||
core.register_on_leaveplayer(function(player)
|
||||
minetest.register_on_leaveplayer(function(player)
|
||||
hud_salvate[player:get_player_name()] = nil
|
||||
end)
|
||||
```
|
||||
@ -278,4 +278,4 @@ end)
|
||||
|
||||
## Altri elementi
|
||||
|
||||
Dai un occhio a [lua_api.md](https://minetest.gitlab.io/minetest/hud/) per una lista completa degli elementi HUD.
|
||||
Dai un occhio a [lua_api.txt]({{ page.root }}/lua_api.html#hud-element-types) per una lista completa degli elementi HUD.
|
||||
|
@ -15,7 +15,7 @@ Per esempio, un valore di 2 sulla gravità, renderà la gravità di un utente du
|
||||
- [Esempio base](#esempio-base)
|
||||
- [Sovrascritture disponibili](#sovrascritture-disponibili)
|
||||
- [Vecchio sistema di movimento](#vecchio-sistema-di-movimento)
|
||||
- [Incompatibilità tra mod](#incompatibilità-tra-mod)
|
||||
- [Incompatibilità tra mod](#incompatibilita-tra-mod)
|
||||
- [Il tuo turno](#il-tuo-turno)
|
||||
|
||||
## Esempio base
|
||||
@ -23,9 +23,9 @@ Per esempio, un valore di 2 sulla gravità, renderà la gravità di un utente du
|
||||
Segue l'esempio di un comando di antigravità:
|
||||
|
||||
```lua
|
||||
core.register_chatcommand("antigrav", {
|
||||
minetest.register_chatcommand("antigrav", {
|
||||
func = function(name, param)
|
||||
local giocatore = core.get_player_by_name(name)
|
||||
local giocatore = minetest.get_player_by_name(name)
|
||||
giocatore:set_physics_override({
|
||||
gravity = 0.1, -- imposta la gravità al 10% del suo valore originale
|
||||
-- (0.1 * 9.81)
|
||||
@ -36,7 +36,7 @@ core.register_chatcommand("antigrav", {
|
||||
|
||||
## Sovrascritture disponibili
|
||||
|
||||
`set_physics_override()` è una tabella. Stando a [lua_api.md](https://minetest.gitlab.io/minetest/class-reference/#player-only-no-op-for-other-objects), le chiavi possono essere:
|
||||
`set_physics_override()` è una tabella. Stando a [lua_api.txt]({{ page.root }}/lua_api.html#player-only-no-op-for-other-objects), le chiavi possono essere:
|
||||
|
||||
* `speed`: moltiplicatore della velocità di movimento (predefinito: 1)
|
||||
* `jump`: moltiplicatore del salto (predefinito: 1)
|
||||
@ -61,4 +61,4 @@ Quando si imposta una sovrascrittura, sovrascriverà qualsiasi altro suo simile
|
||||
|
||||
* **Sonic**: Imposta il moltiplicatore di velocità a un valore elevato (almeno 6) quando un giocatore entra in gioco;
|
||||
* **Super rimbalzo**: Aumenta il valore del salto in modo che il giocatore possa saltare 20 metri (1 cubo = 1 metro);
|
||||
* **Spazio**: Fai in modo che la gravità diminuisca man mano che si sale di altitudine.
|
||||
* **Space**: Fai in modo che la gravità diminuisca man mano che si sale di altitudine.
|
||||
|
@ -32,7 +32,7 @@ I privilegi non sono fatti per indicare classi o status.
|
||||
* ban
|
||||
* vote
|
||||
* worldedit
|
||||
* area_admin - se si tratta di un privilegio per gli amministratori è ok
|
||||
* area_admin - admin functions of one mod is ok
|
||||
|
||||
**Privilegi sbagliati:**
|
||||
|
||||
@ -46,7 +46,7 @@ I privilegi non sono fatti per indicare classi o status.
|
||||
Usa `register_privilege` per dichiarare un nuovo privilegio:
|
||||
|
||||
```lua
|
||||
core.register_privilege("voto", {
|
||||
minetest.register_privilege("voto", {
|
||||
description = "Può votare nei sondaggi",
|
||||
give_to_singleplayer = false
|
||||
})
|
||||
@ -59,7 +59,7 @@ core.register_privilege("voto", {
|
||||
Per controllare velocemente se un giocatore ha tutti i privilegi necessari o meno:
|
||||
|
||||
```lua
|
||||
local celo, manca = core.check_player_privs(player_or_name, {
|
||||
local celo, manca = minetest.check_player_privs(player_or_name, {
|
||||
interact = true,
|
||||
voto = true })
|
||||
```
|
||||
@ -68,7 +68,7 @@ In quest'esempio, `celo` è true se il giocatore ha sia `interact` che `voto`.
|
||||
Se `celo` è false, allora `manca` conterrà una tabella con i privilegi mancanti.
|
||||
|
||||
```lua
|
||||
local celo, manca = core.check_player_privs(name, {
|
||||
local celo, manca = minetest.check_player_privs(name, {
|
||||
interact = true,
|
||||
voto = true })
|
||||
|
||||
@ -82,7 +82,7 @@ end
|
||||
Se non hai bisogno di controllare i privilegi mancanti, puoi inserire `check_player_privs` direttamente nel costrutto if:
|
||||
|
||||
```lua
|
||||
if not core.check_player_privs(name, { interact=true }) then
|
||||
if not minetest.check_player_privs(name, { interact=true }) then
|
||||
return false, "Hai bisogno del privilegio 'interact' per eseguire quest'azione!"
|
||||
end
|
||||
```
|
||||
@ -92,11 +92,11 @@ end
|
||||
Si può accedere o modificare i privilegi di un giocatore anche se quest'ultimo non risulta online.
|
||||
|
||||
```lua
|
||||
local privs = core.get_player_privs(name)
|
||||
local privs = minetest.get_player_privs(name)
|
||||
print(dump(privs))
|
||||
|
||||
privs.voto = true
|
||||
core.set_player_privs(name, privs)
|
||||
minetest.set_player_privs(name, privs)
|
||||
```
|
||||
|
||||
I privilegi sono sempre specificati come una tabella chiave-valore, con il loro nome come chiave e true/false come valore.
|
||||
|
@ -1,4 +1,223 @@
|
||||
---
|
||||
sitemap: false
|
||||
redirect_to: "https://github.com/rubenwardy/sfinv/blob/master/Tutorial.md"
|
||||
title: "SFINV"
|
||||
layout: default
|
||||
root: ../..
|
||||
idx: 4.7
|
||||
description: una mod per rendere più semplice la creazione di un inventario complesso
|
||||
redirect_from: /it/chapters/sfinv.html
|
||||
---
|
||||
|
||||
## Introduzione <!-- omit in toc -->
|
||||
|
||||
Simple Fast Inventory (SFINV) è una mod presente in Minetest Game, usata per creare il [formspec](formspecs.html) del giocatore.
|
||||
SFINV ha un'API che permette di aggiungere e altresì gestire le pagine mostrate.
|
||||
|
||||
Mentre SFINV di base mostra le pagine come finestre, le pagine sono chiamate tali in quanto è assolutamente possibile che una mod o un gioco decidano di mostrarle in un altro formato.
|
||||
Per esempio, più pagine possono essere mostrate nel medesimo formspec.
|
||||
|
||||
- [Registrare una pagina](#registrare-una-pagina)
|
||||
- [Ricevere eventi](#ricevere-eventi)
|
||||
- [Condizioni per la visualizzazione](#condizioni-per-la-visualizzazione)
|
||||
- [Callback on_enter e on_leave](#callback-onenter-e-onleave)
|
||||
- [Aggiungere a una pagina esistente](#aggiungere-a-una-pagina-esistente)
|
||||
|
||||
## Registrare una pagina
|
||||
|
||||
SFINV fornisce la funzione chiamata `sfinv.register_page` per creare nuove pagine.
|
||||
Basta chiamare la funzione con il nome che si vuole assegnare alla pagina e la sua definizione:
|
||||
|
||||
```lua
|
||||
sfinv.register_page("miamod:ciao", {
|
||||
title = "Ciao!",
|
||||
get = function(self, player, context)
|
||||
return sfinv.make_formspec(player, context,
|
||||
"label[0.1,0.1;Ciao mondo!]", true)
|
||||
end
|
||||
})
|
||||
```
|
||||
|
||||
La funzione `make_formspec` circonda il formspec con il codice di SFINV.
|
||||
Il quarto parametro, attualmente impostato a `true`, determina se l'inventario del giocatore è mostrato.
|
||||
|
||||
Rendiamo le cose più eccitanti; segue il codice della generazione di un formspec per gli admin.
|
||||
Questa finestra permetterà agli admin di cacciare o bannare i giocatori selezionandoli da una lista e premendo un pulsante.
|
||||
|
||||
```lua
|
||||
sfinv.register_page("mioadmin:mioadmin", {
|
||||
title = "Finestra",
|
||||
get = function(self, player, context)
|
||||
local giocatori = {}
|
||||
context.mioadmin_giocatori = giocatori
|
||||
|
||||
-- Usare un array per costruire un formspec è decisamente più veloce
|
||||
local formspec = {
|
||||
"textlist[0.1,0.1;7.8,3;lista_giocatori;"
|
||||
}
|
||||
|
||||
-- Aggiunge tutti i giocatori sia alla lista testuale che a quella - appunto - dei giocatori
|
||||
local primo = true
|
||||
for _ , giocatore in pairs(minetest.get_connected_players()) do
|
||||
local nome_giocatore = giocatore:get_player_name()
|
||||
giocatori[#giocatori + 1] = nome_giocatore
|
||||
if not primo then
|
||||
formspec[#formspec + 1] = ","
|
||||
end
|
||||
formspec[#formspec + 1] =
|
||||
minetest.formspec_escape(nome_giocatore)
|
||||
primo = false
|
||||
end
|
||||
formspec[#formspec + 1] = "]"
|
||||
|
||||
-- Aggiunge i pulsanti
|
||||
formspec[#formspec + 1] = "button[0.1,3.3;2,1;caccia;Caccia]"
|
||||
formspec[#formspec + 1] = "button[2.1,3.3;2,1;banna;Caccia e Banna]"
|
||||
|
||||
-- Avvolge il formspec nella disposizione di SFINV
|
||||
-- (es: aggiunge le linguette delle finestre e lo sfondo)
|
||||
return sfinv.make_formspec(player, context,
|
||||
table.concat(formspec, ""), false)
|
||||
end,
|
||||
})
|
||||
```
|
||||
|
||||
Non c'è niente di nuovo in questa parte di codice; tutti i concetti sono già stati trattati o qui in alto o nei precedenti capitoli.
|
||||
|
||||
<figure>
|
||||
<img src="{{ page.root }}//static/sfinv_admin_fs.png" alt="Pagina per gli amministratori">
|
||||
</figure>
|
||||
|
||||
## Ricevere eventi
|
||||
|
||||
Puoi ricevere eventi formspec tramite l'aggiunta della funzione `on_player_receive_fields` nella definizione SFINV.
|
||||
|
||||
```lua
|
||||
on_player_receive_fields = function(self, player, context, fields)
|
||||
-- TODO: implementarlo
|
||||
end,
|
||||
```
|
||||
|
||||
`on_player_receive_fields` funziona alla stessa maniera di `minetest.register_on_player_receive_fields`, con la differenza che viene richiesto il contesto al posto del nome del form.
|
||||
Tieni a mente che gli eventi interni di SFINV, come la navigazione tra le varie finestre, vengono gestiti dentro la mod stessa, e che quindi non verranno ricevuti in questo callback.
|
||||
|
||||
Implementiamo ora `on_player_receive_fields` nella mod:
|
||||
|
||||
```lua
|
||||
on_player_receive_fields = function(self, player, context, fields)
|
||||
-- evento della lista testuale: controlla il tipo di evento e imposta il nuovo indice se è cambiata la selezione
|
||||
if fields.lista_giocatori then
|
||||
local event = minetest.explode_textlist_event(fields.lista_giocatori)
|
||||
if event.type == "CHG" then
|
||||
context.mioadmin_sel_id = event.index
|
||||
end
|
||||
|
||||
-- Al premere "Caccia"
|
||||
elseif fields.caccia then
|
||||
local nome_giocatore =
|
||||
context.myadmin_players[context.mioadmin_sel_id]
|
||||
if player_name then
|
||||
minetest.chat_send_player(player:get_player_name(),
|
||||
"Cacciato " .. nome_giocatore)
|
||||
minetest.kick_player(nome_giocatore)
|
||||
end
|
||||
|
||||
-- Al premere "Caccia e Banna"
|
||||
elseif fields.banna then
|
||||
local nome_giocatore =
|
||||
context.myadmin_players[context.mioadmin_sel_id]
|
||||
if player_name then
|
||||
minetest.chat_send_player(player:get_player_name(),
|
||||
"Banned " .. player_name)
|
||||
minetest.ban_player(nome_giocatore)
|
||||
minetest.kick_player(nome_giocatore, "Banned")
|
||||
end
|
||||
end
|
||||
end,
|
||||
```
|
||||
|
||||
C'è, tuttavia, un problema abbastanza grande a riguardo: chiunque può cacciare o bannare i giocatori!
|
||||
C'è bisogno di un modo per mostrare questa finestra solo a chi ha i privilegi `kick` e `ban`.
|
||||
Fortunatamente, SFINV ci permette di farlo!
|
||||
|
||||
## Condizioni per la visualizzazione
|
||||
|
||||
Si può aggiungere una funzione `is_in_nav` nella definizione della pagina se si desidera gestire quando la pagina deve essere mostrata:
|
||||
|
||||
```lua
|
||||
is_in_nav = function(self, player, context)
|
||||
local privs = minetest.get_player_privs(player:get_player_name())
|
||||
return privs.kick or privs.ban
|
||||
end,
|
||||
```
|
||||
|
||||
Se si ha bisogno di controllare un solo privilegio o si vuole eseguire un `and`, si bisognerebbe usare `minetest.check_player_privs()` al posto di `get_player_privs`.
|
||||
|
||||
Tieni a mente che `is_in_nav` viene chiamato soltanto alla generazione dell'inventario del giocatore.
|
||||
Questo succede quando un giocatore entra in gioco, si muove tra le finestre, o una mod richiede a SFINV di rigenerare l'inventario.
|
||||
|
||||
Ciò significa che hai bisogno di richiedere manualmente la rigenerazione del formspec dell'inventario per ogni evento che potrebbe cambiare il risultato ti `is_in_nav`.
|
||||
Nel nostro caso, abbiamo bisogno di farlo ogni volta che i permessi `kick` o `ban` vengono assegnati/revocati a un giocatore:
|
||||
|
||||
```lua
|
||||
local function al_cambio_privilegi(nome_target, nome_garante, priv)
|
||||
if priv ~= "kick" and priv ~= "ban" then
|
||||
return
|
||||
end
|
||||
|
||||
local giocatore = minetest.get_player_by_name(nome_target)
|
||||
if not giocatore then
|
||||
return
|
||||
end
|
||||
|
||||
local contesto = sfinv.get_or_create_context(giocatore)
|
||||
if contesto.page ~= "mioadmin:mioadmin" then
|
||||
return
|
||||
end
|
||||
|
||||
sfinv.set_player_inventory_formspec(giocatore, contesto)
|
||||
end
|
||||
|
||||
minetest.register_on_priv_grant(al_cambio_privilegi)
|
||||
minetest.register_on_priv_revoke(al_cambio_privilegi)
|
||||
```
|
||||
|
||||
## Callback on_enter e on_leave
|
||||
|
||||
Un giocatore *entra* in una finestra quando la finestra è selezionata e *esce* dalla finestra quando un'altra finestra è prossima a essere selezionata.
|
||||
Attenzione che è possibile selezionare più pagine alla volta se viene usata un tema personalizzato.
|
||||
|
||||
Si tenga conto, poi, che questi eventi potrebbero non essere innescati dal giocatore, in quanto potrebbe addirittura non avere un formspec aperto in quel momento.
|
||||
Per esempio, `on_enter` viene chiamato dalla pagina principale anche quando un giocatore entra in gioco, ancor prima che apri l'inventario.
|
||||
|
||||
Infine, non è possibile annullare il cambio pagina, in quanto potrebbe potenzialmente confondere il giocatore.
|
||||
|
||||
```lua
|
||||
on_enter = function(self, player, context)
|
||||
|
||||
end,
|
||||
|
||||
on_leave = function(self, player, context)
|
||||
|
||||
end,
|
||||
```
|
||||
|
||||
## Aggiungere a una pagina esistente
|
||||
|
||||
Per aggiungere contenuti a una pagina che già esiste, avrai bisogno di sovrascrivere la pagina e modificare il formspec che viene ritornato:
|
||||
|
||||
```lua
|
||||
local vecchia_funzione = sfinv.registered_pages["sfinv:crafting"].get
|
||||
sfinv.override_page("sfinv:crafting", {
|
||||
get = function(self, player, context, ...)
|
||||
local ret = vecchia_funzione(self, player, context, ...)
|
||||
|
||||
if type(ret) == "table" then
|
||||
ret.formspec = ret.formspec .. "label[0,0;Ciao]"
|
||||
else
|
||||
-- Retrocompatibilità
|
||||
ret = ret .. "label[0,0;Ciao]"
|
||||
end
|
||||
|
||||
return ret
|
||||
end
|
||||
})
|
||||
```
|
||||
|
@ -82,7 +82,7 @@ E hai ragione! L'API di Minetest è molto incentrata sull'Osservatore, per far i
|
||||
|
||||
## Modello-Vista-Controllo
|
||||
|
||||
Nel prossimo capitolo discuteremo di come testare automaticamente il codice, e uno dei problemi che riscontreremo sarà come separare il più possibile la logica (calcoli, cosa bisognerebbe fare) dalle chiamate alle API (`core.*`, altre mod).
|
||||
Nel prossimo capitolo discuteremo di come testare automaticamente il codice, e uno dei problemi che riscontreremo sarà come separare il più possibile la logica (calcoli, cosa bisognerebbe fare) dalle chiamate alle API (`minetest.*`, altre mod).
|
||||
|
||||
Un modo per fare ciò è pensare a:
|
||||
|
||||
@ -148,14 +148,14 @@ function terreno.mostra_formspec_crea(nome)
|
||||
]]
|
||||
end
|
||||
|
||||
core.register_chatcommand("/land", {
|
||||
minetest.register_chatcommand("/land", {
|
||||
privs = { terreno = true },
|
||||
func = function(name)
|
||||
land.gestore_richiesta_crea(name)
|
||||
end,
|
||||
})
|
||||
|
||||
core.register_on_player_receive_fields(function(player,
|
||||
minetest.register_on_player_receive_fields(function(player,
|
||||
formname, fields)
|
||||
terreno.gestore_invio_crea(player:get_player_name(),
|
||||
fields.nome_area)
|
||||
@ -191,7 +191,7 @@ Al contrario, un approccio più comune e leggermente meno rigido è quello API-V
|
||||
In un mondo ideale, si avrebbero le 3 aree MVC perfettamente separate... ma siamo nel mondo reale.
|
||||
Un buon compromesso è ridurre la mod in due parti:
|
||||
|
||||
* **API** - modello + controllo. Non ci dovrebbe essere nessun uso di `core.` nella API.
|
||||
* **API** - modello + controllo. Non ci dovrebbe essere nessun uso di `minetest.` nella API.
|
||||
* **Vista** - la vista, esattamente come quella spiegata sopra.
|
||||
È buona norma strutturare questa parte in file separati per ogni tipo di evento.
|
||||
|
||||
|
@ -10,26 +10,51 @@ redirect_from: /it/chapters/common_mistakes.html
|
||||
|
||||
Questo capitolo illustra gli errori più comuni e come evitarli.
|
||||
|
||||
- [Fai attenzione quando salvi gli ObjectRef in delle variabili (giocatori ed entità)](#fai-attenzione-quando-salvi-gli-objectref-in-delle-variabili-giocatori-ed-entità)
|
||||
- [Non salvare mai ObjectRef (giocatori ed entità)](#non-salvare-mai-objectref-giocatori-ed-entita)
|
||||
- [Non fidarti dei campi dei formspec](#non-fidarti-dei-campi-dei-formspec)
|
||||
- [Imposta gli ItemStack dopo averli modificati](#imposta-gli-itemstack-dopo-averli-modificati)
|
||||
|
||||
## Fai attenzione quando salvi gli ObjectRef in delle variabili (giocatori ed entità)
|
||||
## Non salvare mai ObjectRef (giocatori ed entità)
|
||||
|
||||
Un ObjectRef viene invalidato quando il giocatore o l'entità che esso rappresenta abbandona il gioco.
|
||||
Ciò si verifica quando un giocatore si disconnette, o quando un'entità viene rimossa dalla memoria.
|
||||
Se l'oggetto rappresentato da un ObjectRef viene rimosso - per esempio quando il giocatore si disconnette o un'entità viene rimossa dalla memoria - chiamare metodi su quell'oggetto causerà la chiusura improvvisa del server (*crash*).
|
||||
|
||||
Da Minetest 5.2, i metodi degli ObjectRef ritorneranno sempre `nil` quando non validi.
|
||||
In altre parole, ogni chiamata verrà ignorata.
|
||||
|
||||
Si dovrebbe evitare quanto possibile di immagazzinare gli ObjectRef in delle variabili.
|
||||
Se ciò tuttavia accade, assicurati di controllare se esiste ancora, come illustrato qui di seguito:
|
||||
Sbagliato:
|
||||
|
||||
```lua
|
||||
-- questo codice funziona solo da Minetest 5.2 in poi
|
||||
if obj:get_pos() then
|
||||
-- è valido!
|
||||
minetest.register_on_joinplayer(function(player)
|
||||
local function func()
|
||||
local pos = player:get_pos() -- MALE!
|
||||
-- `player` viene salvato per essere utilizzato dopo.
|
||||
-- Se il giocatore si disconnette, il server crasha
|
||||
end
|
||||
|
||||
minetest.after(1, func)
|
||||
|
||||
foobar[player:get_player_name()] = player
|
||||
-- RISCHIOSO
|
||||
-- Non è consigliato fare così.
|
||||
-- Usa invece minetest.get_connected_players() e minetest.get_player_by_name().
|
||||
end)
|
||||
```
|
||||
|
||||
Giusto:
|
||||
|
||||
```lua
|
||||
minetest.register_on_joinplayer(function(player)
|
||||
local function func(name)
|
||||
-- Tenta di ottenere il riferimento
|
||||
local player = minetest.get_player_by_name(name)
|
||||
|
||||
-- Controlla che il giocatore sia online
|
||||
if player then
|
||||
-- è online, procedo
|
||||
local pos = player:get_pos()
|
||||
end
|
||||
end
|
||||
|
||||
-- Passa il nome nella funzione
|
||||
minetest.after(1, func, player:get_player_name())
|
||||
end)
|
||||
```
|
||||
|
||||
## Non fidarti dei campi dei formspec
|
||||
@ -40,11 +65,11 @@ Per esempio, il seguente codice presenta una vulnerabilità che permette ai gioc
|
||||
|
||||
```lua
|
||||
local function show_formspec(name)
|
||||
if not core.check_player_privs(name, { privs = true }) then
|
||||
if not minetest.check_player_privs(name, { privs = true }) then
|
||||
return false
|
||||
end
|
||||
|
||||
core.show_formspec(name, "modman:modman", [[
|
||||
minetest.show_formspec(name, "modman:modman", [[
|
||||
size[3,2]
|
||||
field[0,0;3,1;target;Nome;]
|
||||
button_exit[0,1;3,1;sub;Promuovi]
|
||||
@ -52,14 +77,14 @@ local function show_formspec(name)
|
||||
return true
|
||||
})
|
||||
|
||||
core.register_on_player_receive_fields(function(player,
|
||||
minetest.register_on_player_receive_fields(function(player,
|
||||
formname, fields)
|
||||
-- MALE! Manca il controllo dei privilegi!
|
||||
|
||||
local privs = core.get_player_privs(fields.target)
|
||||
local privs = minetest.get_player_privs(fields.target)
|
||||
privs.kick = true
|
||||
privs.ban = true
|
||||
core.set_player_privs(fields.target, privs)
|
||||
minetest.set_player_privs(fields.target, privs)
|
||||
return true
|
||||
end)
|
||||
```
|
||||
@ -67,9 +92,9 @@ end)
|
||||
Aggiungi un controllo dei privilegi per ovviare:
|
||||
|
||||
```lua
|
||||
core.register_on_player_receive_fields(function(player,
|
||||
minetest.register_on_player_receive_fields(function(player,
|
||||
formname, fields)
|
||||
if not core.check_player_privs(name, { privs = true }) then
|
||||
if not minetest.check_player_privs(name, { privs = true }) then
|
||||
return false
|
||||
end
|
||||
|
||||
@ -105,7 +130,7 @@ inv:set_stack("main", 1, pila)
|
||||
Il comportamento dei callback è leggermente più complicato.
|
||||
|
||||
```lua
|
||||
core.register_on_item_eat(function(hp_change, replace_with_item,
|
||||
minetest.register_on_item_eat(function(hp_change, replace_with_item,
|
||||
itemstack, user, pointed_thing)
|
||||
itemstack:get_meta():set_string("description", "Un po' smangiucchiato")
|
||||
-- Quasi corretto! I dati saranno persi se un altro callback annulla questa chiamata
|
||||
@ -117,7 +142,7 @@ Se nessun callback cancella l'operazione, la pila sarà impostata e la descrizio
|
||||
È meglio quindi fare così:
|
||||
|
||||
```lua
|
||||
core.register_on_item_eat(function(hp_change, replace_with_item,
|
||||
minetest.register_on_item_eat(function(hp_change, replace_with_item,
|
||||
itemstack, user, pointed_thing)
|
||||
itemstack:get_meta():set_string("description", "Un po' smangiucchiato")
|
||||
user:get_inventory():set_stack("main", user:get_wield_index(),
|
||||
|
@ -19,12 +19,13 @@ LuaCheck può essere usato in combinazione con l'editor per fornire avvertimenti
|
||||
- [Configurare LuaCheck](#configurare-luacheck)
|
||||
- [Risoluzione problemi](#risoluzione-problemi)
|
||||
- [Uso nell'editor](#uso-nelleditor)
|
||||
- [Controllare i commit con Travis](#controllare-i-commit-con-travis)
|
||||
|
||||
## Installare LuaCheck
|
||||
|
||||
### Windows
|
||||
|
||||
Basta scaricare luacheck.exe dall'apposita [pagina delle versioni su Github](https://github.com/mpeterv/luacheck/releases).
|
||||
Basta scaricare luacheck.exe dall'apposita [pagina delle release su Github](https://github.com/mpeterv/luacheck/releases).
|
||||
|
||||
### Linux
|
||||
|
||||
@ -52,7 +53,7 @@ Su Linux, esegui `luacheck .` nella cartella principale del progetto.
|
||||
## Configurare LuaCheck
|
||||
|
||||
Crea un file chiamato .luacheckrc nella cartella principale del tuo progetto.
|
||||
Questa può essere quella di un gioco, di un pacchetto mod o di una mod singola.
|
||||
Questa può essere quella di un gioco, di una modpack o di una mod.
|
||||
|
||||
Inserisci il seguente codice all'interno:
|
||||
|
||||
@ -79,7 +80,7 @@ read_globals = {
|
||||
|
||||
Poi, avrai bisogno di assicurarti che funzioni eseguendo LuaCheck: dovresti ottenere molti meno errori questa volta.
|
||||
Partendo dal primo errore, modifica il codice per risolvere il problema, o modifica la configurazione di LuaCheck se il codice è corretto.
|
||||
Dài un occhio alla lista sottostante.
|
||||
Dai un occhio alla lista sottostante.
|
||||
|
||||
### Risoluzione problemi
|
||||
|
||||
@ -95,7 +96,46 @@ Dài un occhio alla lista sottostante.
|
||||
È caldamente consigliato installare un'estensione per il tuo editor di fiducia che ti mostri gli errori senza eseguire alcun comando.
|
||||
Queste sono disponibili nella maggior parte degli editor, come:
|
||||
|
||||
* **Atom** - `linter-luacheck`;
|
||||
* **VSCode** - Ctrl+P, poi incolla: `ext install dwenegar.vscode-luacheck`;
|
||||
* **Sublime** - Installala usando package-control:
|
||||
[SublimeLinter](https://github.com/SublimeLinter/SublimeLinter),
|
||||
[SublimeLinter-luacheck](https://github.com/SublimeLinter/SublimeLinter-luacheck).
|
||||
|
||||
## Controllare i commit con Travis
|
||||
|
||||
Se il tuo progetto è pubblico ed è su Github, puoi usare TravisCI - un servizio gratuito per eseguire controlli sui commit.
|
||||
Questo significa che ogni commit pushato verrà controllato secondo le impostazioni di LuaCheck, e una spunta verde o una X rossa appariranno al suo fianco per segnalare se sono stati trovati errori o meno.
|
||||
Ciò è utile soprattutto per quando il tuo progetto riceve una richiesta di modifica (*pull request*) per verificare se il codice è scritto bene senza doverlo scaricare.
|
||||
|
||||
Prima di tutto, vai su [travis-ci.org](https://travis-ci.org/) ed esegui l'accesso con il tuo account Github.
|
||||
Dopodiché cerca la repo del tuo progetto nel tuo profilo Travis, e abilita Travis cliccando sull'apposito bottone.
|
||||
|
||||
Poi, crea un file chiamato `.travis.yml` con il seguente contenuto:
|
||||
|
||||
```yml
|
||||
language: generic
|
||||
sudo: false
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- luarocks
|
||||
before_install:
|
||||
- luarocks install --local luacheck
|
||||
script:
|
||||
- $HOME/.luarocks/bin/luacheck .
|
||||
notifications:
|
||||
email: false
|
||||
```
|
||||
|
||||
Se il tuo progetto è un gioco piuttosto che una mod o un pacchetto di mod, cambia la riga dopo `script:` con:
|
||||
|
||||
```yml
|
||||
- $HOME/.luarocks/bin/luacheck mods/
|
||||
```
|
||||
|
||||
Ora esegui il commit e il push su Github.
|
||||
Vai alla pagina del tuo progetto e clicca su "commits".
|
||||
Dovresti vedere un cerchietto arancione di fianco al commit che hai appena fatto.
|
||||
Dopo un po' di tempo il cerchietto dovrebbe cambiare in una spunta verde o in una X rossa (a seconda dell'esito, come detto prima).
|
||||
In entrambi i casi, puoi cliccare l'icona per vedere il resoconto dell'operazione e l'output di LuaCheck.
|
||||
|
@ -10,15 +10,17 @@ redirect_from: /it/chapters/readmore.html
|
||||
|
||||
Dopo aver letto questo libro, se mastichi l'inglese dai un occhio a ciò che segue:
|
||||
|
||||
### Moddaggio di Minetest
|
||||
### Modding di Minetest
|
||||
|
||||
* Riferimento alla API Lua di Minetest - [versione interattiva](https://minetest.gitlab.io/minetest/) |
|
||||
[versione su pagina singola](https://github.com/minetest/minetest/blob/master/doc/lua_api.md).
|
||||
* Riferimento alla API Lua di Minetest - [versione HTML]({{ page.root }}/lua_api.html) |
|
||||
[versione solo testo](https://github.com/minetest/minetest/blob/master/doc/lua_api.txt).
|
||||
* Esplora la [Wiki Sviluppatore](http://dev.minetest.net/Main_Page).
|
||||
* Spulcia le [mod esistenti](https://forum.minetest.net/viewforum.php?f=11).
|
||||
|
||||
### Programmazione in Lua
|
||||
|
||||
* [Programmazione in Lua (PIL)](http://www.lua.org/pil/).
|
||||
* [Corso accelerato su Lua](http://luatut.com/crash_course.html).
|
||||
|
||||
### Modellazione 3D
|
||||
|
||||
|
@ -11,20 +11,21 @@ redirect_from: /it/chapters/releasing.html
|
||||
Rilasciare (o pubblicare) una mod permette ad altre persone di poterne usufruire.
|
||||
Una volta che una mod è stata rilasciata potrebbe venir usata nelle partite locali (a giocatore singolo) o nei server, inclusi quelli pubblici.
|
||||
|
||||
- [Scegliere una licenza](#scegliere-una-licenza)
|
||||
- [Scegliere la licenza](#scegliere-la-licenza)
|
||||
- [LGPL e CC-BY-SA](#lgpl-e-cc-by-sa)
|
||||
- [CC0](#cc0)
|
||||
- [MIT](#mit)
|
||||
- [Impacchettare](#impacchettare)
|
||||
- [README.txt](#readmetxt)
|
||||
- [mod.conf / game.conf](#modconf--gameconf)
|
||||
- [description.txt](#descriptiontxt)
|
||||
- [screenshot.png](#screenshotpng)
|
||||
- [Caricare](#caricare)
|
||||
- [Sistemi di Controllo Versione](#sistemi-di-controllo-versione)
|
||||
- [Rilasciare su ContentDB](#rilasciare-su-contentdb)
|
||||
- [Allegati sul forum](#allegati-sul-forum)
|
||||
- [Creare la discussione sul forum](#creare-la-discussione-sul-forum)
|
||||
- [Titolo](#titolo)
|
||||
|
||||
## Scegliere una licenza
|
||||
## Scegliere la licenza
|
||||
|
||||
Avrai bisogno di specificare una licenza per la tua mod.
|
||||
Questo è importante perché dice alle altre persone cosa possono e non possono fare col tuo lavoro.
|
||||
@ -36,9 +37,6 @@ Puoi adottare la licenza che preferisci; tuttavia, sappi che le mod con licenze
|
||||
|
||||
Tieni anche a mente che **la licenza di pubblico dominio non è una licenza valida**, perché la sua definizione varia da stato a stato.
|
||||
|
||||
È importante sottolineare che la WTFPL (*do What The Fuck you want to Public License*, la "facci il cazzo che ti pare")
|
||||
[è caldamente *s*consigliata](https://content.minetest.net/help/wtfpl/), e alcune persone potrebbero decidere di non usare la tua mod se ha questa licenza.
|
||||
|
||||
### LGPL e CC-BY-SA
|
||||
|
||||
Questa è la combinazione più comune nella comunità di Minetest, nonché quella usata sia da Minetest che da Minetest Game.
|
||||
@ -51,7 +49,9 @@ Ciò significa che:
|
||||
|
||||
### CC0
|
||||
|
||||
Queste licenze possono essere usate sia per il codice che per contenuti artistici, permettendo a chiunque di fare quello che gli va - incluso il non citare l'autore.
|
||||
Queste licenze permettono a chiunque di fare quello che gli va - incluso il non citare l'autore - e possono essere usate sia per il codice che per i contenuti artistici.
|
||||
|
||||
È importante sottolineare che la WTFPL (*do What The Fuck you want to Public License*, la "facci il cazzo che ti pare") è caldamente *s*consigliata, e alcune persone potrebbero decidere di non usare la tua mod se ha questa licenza.
|
||||
|
||||
### MIT
|
||||
|
||||
@ -60,80 +60,119 @@ La differenza con la LGPL è che le copie derivate in questo caso non devono per
|
||||
|
||||
## Impacchettare
|
||||
|
||||
Ci sono alcuni file che è consigliato includere nelle proprie mod e nei propri giochi prima di rilasciarli.
|
||||
Ci sono alcuni file che è consigliato includere nella propria mod prima di rilasciarla.
|
||||
|
||||
### README.txt
|
||||
|
||||
Il README dovrebbe dichiarare:
|
||||
|
||||
* Cosa fa la mod/gioco;
|
||||
* Come si usa;
|
||||
* Cosa fa la mod;
|
||||
* Che licenza ha;
|
||||
* Quali dipendenze richiede;
|
||||
* Come installare la mod;
|
||||
* Versione corrente della mod;
|
||||
* Eventualmente, dove segnalare i problemi o comunque richiedere aiuto.
|
||||
|
||||
### mod.conf / game.conf
|
||||
### description.txt
|
||||
|
||||
Assicurati di aggiungere una descrizione che spieghi cosa fa la mod o il gioco, usando la chiave `description`.
|
||||
Questo file spiega cosa fa la mod.
|
||||
Cerca di essere preciso e coinciso: dovrebbe essere breve perché il contenuto verrà mostrato nell'installer del motore di gioco, che ha uno spazio limitato.
|
||||
|
||||
Per esempio, consigliato:
|
||||
|
||||
description = Aggiunge zuppa, torte, pane e succhi
|
||||
Aggiunge zuppa, torte, pane e succhi.
|
||||
|
||||
Sconsigliato:
|
||||
|
||||
description = Cibo per Minetest
|
||||
Cibo per Minetest.
|
||||
|
||||
### screenshot.png
|
||||
|
||||
Gli screenshot dovrebbero essere in proporzione 3:2 e avere una grandezza minima di 300x200px.
|
||||
|
||||
Lo screen verrà mostrato all'interno di Minetest come anteprima del contenuto.
|
||||
Lo screen verrà mostrato nel bazar delle mod (sono tutte gratuite).
|
||||
|
||||
## Caricare
|
||||
|
||||
Per far sì che un potenziale utente possa scaricare la tua mod, c'è bisogno di caricarla in uno spazio pubblico.
|
||||
Ci sono svariati modi per fare ciò quindi usa l'approccio che ritieni più opportuno; l'importante è che esso rispetti i requisiti qui elencati, ed eventuale richieste aggiuntive dei moderatori del forum:
|
||||
Ci sono svariati modi per fare ciò quindi usa l'approccio che ritieni più opportuno; l'importante è che esso rispetti i requisiti qui elencati, ed eventuale richieste aggiuntive aggiunta dai moderatori del forum:
|
||||
|
||||
* **Stabile** - Il sito che conterrà il file non dovrebbe essere propenso a chiudere i battenti da un momento all'altro senza preavviso;
|
||||
* **Link diretto** - Dovresti essere in grado di cliccare su un link e scaricare il file senza il bisogno di dover passare per altre pagine;
|
||||
* **Senza virus** - Caricamenti su siti sospetti potrebbero contenere materiali non sicuri.
|
||||
|
||||
ContentDB soddisfa questi requisiti, richiedendo giusto un file .zip.
|
||||
* **Stabile** - Il sito che conterrà il file non dovrebbe essere propenso a morire da un momento all'altro senza preavviso;
|
||||
* **Link diretto** - Dovresti essere in grado di cliccare su un link sul forum e scaricare il file senza il bisogno di dover passare per altre pagine;
|
||||
* **Senza virus** - Le mod con contenuti malevoli saranno rimosse dal forum.
|
||||
|
||||
### Sistemi di Controllo Versione
|
||||
|
||||
Un Sistema di Controllo Versione (VCS, *Version Control System*) è un programma che gestisce i cambiamenti di altri programmi, spesso facilitandone la distribuzione e la collaborazione.
|
||||
È consigliato usare un sistema di controllo versione che:
|
||||
|
||||
La maggior parte dei creatori di mod su Minetest usa Git e un sito per ospitare il loro codice come GitHub o GitLab.
|
||||
* Permetta agli altri sviluppatori di inviare le loro modifiche facilmente;
|
||||
* Permetta al codice di essere visualizzato prima di essere scaricato;
|
||||
* Permetta agli utenti di fare segnalazioni (bug, domande ecc).
|
||||
|
||||
Usare git può essere difficile all'inizio.
|
||||
La maggior parte dei creatori di mod su Minetest usa GitHub o GitLab come sito per ospitare il loro codice, ma esistono anche altre opzioni.
|
||||
|
||||
Usare siti come GitHub e GitLab può essere difficile all'inizio.
|
||||
Se hai bisogno di una mano e mastichi l'inglese, prova a dare un occhio a [Pro Git book](http://git-scm.com/book/en/v1/Getting-Started) - gratis da leggere online.
|
||||
|
||||
## Rilasciare su ContentDB
|
||||
### Allegati sul forum
|
||||
|
||||
ContentDB è il luogo ufficiale dove trovare e distribuire mod, giochi e pacchetti texture.
|
||||
Gli utenti possono manualmente andare alla ricerca di contenuti tramite il sito, o scaricarli e installarli direttamente dall'integrazione presente nel menù principale di Minetest.
|
||||
Un'alternativa all'usare un sistema di controllo versione è il caricare le mod come allegati sul forum.
|
||||
Questo può essere fatto alla creazione della discussione nella sezione delle mod (spiegato sotto).
|
||||
|
||||
Iscriviti su [ContentDB](https://content.minetest.net) e aggiungi il tuo lavoro.
|
||||
Assicurati di leggere le linee guida (in inglese) nella sezione d'aiuto (*Help*).
|
||||
Prima di tutto, avrai bisogno di creare uno zip con i file della mod (il procedimento varia da sistema operativo a sistema operativo, ma solitamente si parla di premere il tasto destro su uno dei file dopo averli selezionati tutti).
|
||||
|
||||
Quando crei una discussione sul forum - nella pagina "Create a Topic" illustrata sotto - vai alla "Upload Attachment" situata in basso.
|
||||
Clicca poi su "Browse", selezionando il file zip.
|
||||
È inoltre consigliato specificare la versione della mod nel campo dei commenti ("File comment").
|
||||
|
||||
<figure>
|
||||
<img src="{{ page.root }}/static/releasing_attachments.png" alt="Upload Attachment">
|
||||
<figcaption>
|
||||
La scheda Upload Attachment.
|
||||
</figcaption>
|
||||
</figure>
|
||||
|
||||
## Creare la discussione sul forum
|
||||
|
||||
Puoi anche creare una discussione sul forum per far in modo che gli utenti possano discutere ciò che hai fatto.
|
||||
|
||||
Per le mod usa la sezione ["WIP Mods"](https://forum.minetest.net/viewforum.php?f=9) (*Work In Progress*), per i giochi invece ["WIP Games"](https://forum.minetest.net/viewforum.php?f=50).
|
||||
|
||||
|
||||
Puoi ora creare la discussione nella sezione ["WIP Mods"](https://forum.minetest.net/viewforum.php?f=9) (WIP sta per *Work In Progress*, lavori in corso).\\
|
||||
Quando ritieni che la tua mod abbia raggiunto la sua prima versione ufficiale, puoi [richiedere (in inglese) che venga spostata](https://forum.minetest.net/viewtopic.php?f=11&t=10418) in "Mod Releases".
|
||||
|
||||
La discussione dovrebbe contenere contenuti simili al README, con giusto un po' più di coinvolgimento e il link per scaricare la mod.
|
||||
È buona cosa aggiungere anche degli eventuali screenshot per far capire al volo cosa fa la mod, se possibile.
|
||||
|
||||
La formattazione del forum di Minetest è in bbcode.
|
||||
Segue un esempio di una mod chiamata "superspecial" (si è tenuto l'esempio in inglese dato che bisogna scrivere appunto in inglese sul forum, NdT):
|
||||
|
||||
|
||||
Adds magic, rainbows and other special things.
|
||||
|
||||
See download attached.
|
||||
|
||||
[b]Version:[/b] 1.1
|
||||
[b]License:[/b] LGPL 2.1 or later
|
||||
|
||||
Dependencies: default mod (found in minetest_game)
|
||||
|
||||
Report bugs or request help on the forum topic.
|
||||
|
||||
[h]Installation[/h]
|
||||
|
||||
Unzip the archive, rename the folder to superspecial and
|
||||
place it in minetest/mods/
|
||||
|
||||
( GNU/Linux: If you use a system-wide installation place
|
||||
it in ~/.minetest/mods/. )
|
||||
|
||||
( If you only want this to be used in a single world, place
|
||||
the folder in worldmods/ in your world directory. )
|
||||
|
||||
For further information or help see:
|
||||
[url]https://wiki.minetest.net/Installing_Mods[/url]
|
||||
|
||||
Se hai intenzione di usare questo esempio per la tua mod, ricordati ovviamente di cambiare "superspecial" con il nome vero e proprio.
|
||||
|
||||
### Titolo
|
||||
|
||||
Il titolo della discussione deve seguire uno dei seguenti formati:
|
||||
|
||||
* [Mod] Nome mod [nomemod]
|
||||
|
@ -12,7 +12,7 @@ La sicurezza è molto importante per evitare che una mod permetta di far perdere
|
||||
- [Concetti fondamentali](#concetti-fondamentali)
|
||||
- [Formspec](#formspec)
|
||||
- [Non fidarsi mai dei campi dei formspec](#non-fidarsi-mai-dei-campi-dei-formspec)
|
||||
- [Il momento per controllare non è il momento dell'uso (Time of Check is not Time of Use)](#il-momento-per-controllare-non-è-il-momento-delluso-time-of-check-is-not-time-of-use)
|
||||
- [Il momento per controllare non è il momento dell'uso (Time of Check is not Time of Use)](#il-momento-per-controllare-non-e-il-momento-delluso-time-of-check-is-not-time-of-use)
|
||||
- [Ambienti (non sicuri)](#ambienti-non-sicuri)
|
||||
|
||||
## Concetti fondamentali
|
||||
@ -33,7 +33,7 @@ Qualsiasi utente può inviare qualsiasi formspec con i valori che preferisce qua
|
||||
Segue del codice trovato realmente in una mod:
|
||||
|
||||
```lua
|
||||
core.register_on_player_receive_fields(function(player,
|
||||
minetest.register_on_player_receive_fields(function(player,
|
||||
formname, fields)
|
||||
for key, field in pairs(fields) do
|
||||
local x,y,z = string.match(key,
|
||||
@ -71,7 +71,7 @@ Minetest permette alle mod di richiedere ambienti senza limiti, dando loro acces
|
||||
Riesci a individuare la vulnerabilità in questo pezzo di codice??
|
||||
|
||||
```lua
|
||||
local ie = core.request_insecure_environment()
|
||||
local ie = minetest.request_insecure_environment()
|
||||
ie.os.execute(("path/to/prog %d"):format(3))
|
||||
```
|
||||
|
||||
|
@ -1,153 +0,0 @@
|
||||
---
|
||||
title: Traduzione
|
||||
layout: default
|
||||
root: ../..
|
||||
idx: 8.05
|
||||
marked_text_encoding:
|
||||
level: info
|
||||
title: Marked Text Encoding
|
||||
message: |
|
||||
Non hai davvero bisogno di capire come funziona il testo formattato, ma potrebbe aiutarti a capire meglio.
|
||||
|
||||
```
|
||||
"\27(T@miamod)Hello everyone!\27E"
|
||||
```
|
||||
|
||||
* `\27` è il carattere di escape - è usato per dire a Minetest di far attenzione, in quanto sta per seguire qualcosa di speciale. È usato sia per le traduzioni che per la colorazione del testo.
|
||||
* `(T@miamod)` dice che il testo a seguire è traducibile usando il dominio testuale di `miamod`.
|
||||
* `Hello everyone!` è il testo in inglese da tradurre, passato alla funzione di traduzione.
|
||||
* `\27E` è di nuovo il carattere di escape, dove `E` è usato per segnalare che si è arrivati alla fine.
|
||||
---
|
||||
|
||||
## Introduzione <!-- omit in toc -->
|
||||
|
||||
Aggiungere il supporto per le traduzioni nelle tue mod e giochi dà la possibilità a più persone di gustarsele.
|
||||
Stando a Google Play, il 64% dei giocatori di Minetest Android non usano l'inglese come prima lingua.
|
||||
Per quanto Minetest non tenga traccia di questo parametro nelle altre piattaforme, vien comunque da sé pensare che una buona parte di giocatrici e giocatori non siano madrelingua inglesi.
|
||||
|
||||
Minetest ti permette di localizzare i tuoi contenuti in tante lingue diverse, chiedendoti il testo base in inglese, seguito da dei file di traduzione che mappano le parole/frasi equivalenti nelle altre lingue. Questo processo di traduzione è fatto a lato client, cosicché ogni giocatore possa vedere il contenuto nella propria lingua (se disponibile).
|
||||
|
||||
|
||||
- [Come funziona la traduzione lato client?](#come-funziona-la-traduzione-lato-client)
|
||||
- [Testo formattato](#testo-formattato)
|
||||
- [File di traduzione](#file-di-traduzione)
|
||||
- [Formattare una stringa](#formattare-una-stringa)
|
||||
- [Buona prassi per una buona traduzione](#buona-prassi-per-una-buona-traduzione)
|
||||
- [Traduzioni lato server](#traduzioni-lato-server)
|
||||
- [Per concludere](#per-concludere)
|
||||
|
||||
|
||||
## Come funziona la traduzione lato client?
|
||||
|
||||
### Testo formattato
|
||||
|
||||
Il server ha bisogno di dire ai client come tradurre il testo.
|
||||
Questo accade grazie alla funzione `core.get_translator(dominiotestuale)`, che abbrevieremo con `S()`:
|
||||
|
||||
```lua
|
||||
local S = core.get_translator("miamod")
|
||||
|
||||
core.register_craftitem("miamod:oggetto", {
|
||||
description = S("My Item"),
|
||||
})
|
||||
```
|
||||
|
||||
Il primo parametro di `get_translator` è il dominio testuale, che funge da [spazio dei nomi](https://it.wikipedia.org/wiki/Namespace).
|
||||
Piuttosto che avere tutte le traduzioni di una lingua salvate nello stesso file, queste possono essere suddivise in domini testuali (un file per dominio per lingua).
|
||||
È buona norma assegnare al dominio testuale lo stesso nome della mod, onde evitare conflitti tra mod diverse.
|
||||
|
||||
Il testo formattato può essere usato nella maggior parte dei casi dove è richiesto un testo fatto per gli esseri umani - come i formspec, i campi di definizioni di un oggetto, `infotext` ecc.
|
||||
Nel caso dei formspec, tieni presente che dovrai usare la funzione di escape `core.formspec_escape` per una corretta visualizzazione.
|
||||
|
||||
Quando il client incontra del testo formattato, come quello passato in `description`, ne andrà a cercare il corrispettivo nel file di traduzione della lingua del giocatore. Se la ricerca non avrà avuto esito positivo, ritornerà quello in inglese.
|
||||
|
||||
Nel testo formattato sono presenti il testo sorgente in inglese, il dominio testuale, e qualsivoglia altro parametro passato a `S()`.
|
||||
Essenzialmente, è una codifica testuale della chiamata a `S` che contiene tutte le informazioni necessarie.
|
||||
|
||||
{% include notice.html notice=page.marked_text_encoding %}
|
||||
|
||||
|
||||
### File di traduzione
|
||||
|
||||
I file di traduzione sono file che possono essere trovati nella cartella `locale` di ogni mod.
|
||||
Al momento, l'unico formato supportato è `.tr`, ma è probabile che altri formati più tipici saranno aggiunti in futuro.
|
||||
I file di traduzione devono essere nominati nel seguente modo: `[dominiotestuale].[codicelingua].tr`.
|
||||
|
||||
I file `.tr` iniziano con un commento che ne specifica il dominio, seguito poi da righe che mappano il testo originale in inglese nella lingua del file.
|
||||
|
||||
Per esempio, `miamod.it.tr`:
|
||||
|
||||
```
|
||||
# textdomain: miamod
|
||||
Hello everyone!=Ciao a tutti!
|
||||
I like grapefruit=Mi piace il pompelmo
|
||||
```
|
||||
|
||||
Dovresti creare dei file di traduzione basati sul codice sorgente delle tue mod/giochi, usando uno strumento come [update_translations](https://github.com/minetest-tools/update_translations).
|
||||
Questo cercherà tutte le occorrenze di `S(` nel tuo codice Lua, creando in automatico un modello che traduttrici e traduttori potranno usare per tradurre nella loro lingua.
|
||||
Inoltre, si prenderà cura di aggiornare i file di traduzione ogniqualvolta verranno effettuate modifiche al codice.
|
||||
|
||||
|
||||
## Formattare una stringa
|
||||
|
||||
Non è raro dover inserire una variabile dentro una stringa da tradurre.
|
||||
È importante che il testo non sia semplicemente concatenato, in quanto impedirebbe a chi traduce di cambiare l'ordine delle variabili all'interno della frase.
|
||||
Al contrario, dovresti usare il seguente sistema di formattazione:
|
||||
|
||||
```lua
|
||||
core.register_on_joinplayer(function(player)
|
||||
core.chat_send_all(S("Everyone, say hi to @1!", player:get_player_name()))
|
||||
end)
|
||||
```
|
||||
|
||||
Se vuoi scrivere letteralmente `@` nella tua frase, dovrai usare una sequenza di escape scrivendo `@@`.
|
||||
|
||||
Dovresti evitare di concatenare stringhe *all'interno* di una frase; piuttosto, sarebbe meglio concatenare più frasi come da esempio, in quanto permette a chi traduce di lavorare su stringhe più piccole:
|
||||
|
||||
```lua
|
||||
S("Hello @1!", player_name) .. " " .. S("You have @1 new messages.", #msgs)
|
||||
```
|
||||
|
||||
|
||||
## Buona prassi per una buona traduzione
|
||||
|
||||
* Evita di concatenare il testo, optando invece per formattare le stringhe. Questo permette a chi traduce di avere pieno controllo sull'ordine degli elementi;
|
||||
* Crea i file di traduzione in automatico usando [update_translations](https://github.com/minetest-tools/update_translations);
|
||||
* È cosa comune che le variabili cambino il testo circostante, per esempio tramite genere e numero. Risulta spesso difficile trovare qualcosa che si sposi bene in tutti i casi, perciò si tende a cambiare la struttura della frase in modo che risulti sempre corretta ("Hai ottenuto 3 mele" -> "Hai ottenuto mela (x3)");
|
||||
* Le traduzioni potrebbero essere molto più lunghe o molto più corte rispetto all'originale. Assicurati di lasciare sempre un po' di respiro;
|
||||
* Non tutte le lingue scrivono i numeri nella stessa maniera, come per esempio `1.000` e `1'000`;
|
||||
* Non dar per scontato che le altre lingue usino le maiscuole nella stessa maniera della tua.
|
||||
|
||||
|
||||
## Traduzioni lato server
|
||||
|
||||
Certe volte ti capiterà di voler sapere quale traduzione di una tal stringa stia venendo visualizzata dal giocatore.
|
||||
Puoi usare `get_player_information` per ottenere la lingua utilizzata e `get_translated_string` per tradurne il testo formattato.
|
||||
|
||||
```lua
|
||||
local list = {
|
||||
S("Hello world!"),
|
||||
S("Potato")
|
||||
}
|
||||
|
||||
core.register_chatcommand("find", {
|
||||
func = function(name, param)
|
||||
local info = core.get_player_information(name)
|
||||
local lingua = info and info.language or "en"
|
||||
|
||||
for _, riga in ipairs(lista) do
|
||||
local trad = core.get_translated_string(language, riga)
|
||||
if trad:contains(query) then
|
||||
return riga
|
||||
end
|
||||
end
|
||||
end,
|
||||
})
|
||||
```
|
||||
|
||||
## Per concludere
|
||||
|
||||
Se ben gestita, l'API per le traduzioni permette di rendere mod e giochi più accessibili.
|
||||
|
||||
Si tenga comunque conto che Minetest è in continua evoluzione e che l'API verrà probabilmente ampliata in futuro.
|
||||
Per esempio, il supporto per i file di traduzione *gettext* permetterà l'utilizzo di piattaforme e strumenti consolidati come Weblate, mentre nel frattempo si sta lavorando al supporto per il genere e il numero.
|
@ -15,8 +15,9 @@ Scrivere i testing d'unità per le funzioni dove vengono chiamate quelle di Mine
|
||||
- [Il tuo primo test](#il-tuo-primo-test)
|
||||
- [init.lua](#initlua)
|
||||
- [api.lua](#apilua)
|
||||
- [tests/api_spec.lua](#testsapi_speclua)
|
||||
- [tests/api_spec.lua](#testsapispeclua)
|
||||
- [Simulare: usare funzioni esterne](#simulare-usare-funzioni-esterne)
|
||||
- [Controllare commit con Travis](#controllare-commit-con-travis)
|
||||
- [Conclusione](#conclusione)
|
||||
|
||||
## Installare Busted
|
||||
@ -52,7 +53,7 @@ Quello che fa è cercare i file Lua con il nome che termina in `_spec`, eseguend
|
||||
```lua
|
||||
miamod = {}
|
||||
|
||||
dofile(core.get_modpath("miamod") .. "/api.lua")
|
||||
dofile(minetest.get_modpath("miamod") .. "/api.lua")
|
||||
```
|
||||
|
||||
|
||||
@ -112,7 +113,7 @@ _G.minetest = {}
|
||||
|
||||
-- Definisce la funzione simulata
|
||||
local chiamate_chat_send_all = {}
|
||||
function core.chat_send_all(name, message)
|
||||
function minetest.chat_send_all(name, message)
|
||||
table.insert(chiamate_chat_send_all, { nome = name, messaggio = message })
|
||||
end
|
||||
|
||||
@ -153,8 +154,29 @@ end)
|
||||
```
|
||||
|
||||
|
||||
## Controllare commit con Travis
|
||||
|
||||
Lo script di Travis usato nel capitolo [Controllo automatico degli errori](luacheck.html) può essere modificato per eseguire (anche) Busted
|
||||
|
||||
```yml
|
||||
language: generic
|
||||
sudo: false
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- luarocks
|
||||
before_install:
|
||||
- luarocks install --local luacheck && luarocks install --local busted
|
||||
script:
|
||||
- $HOME/.luarocks/bin/luacheck .
|
||||
- $HOME/.luarocks/bin/busted .
|
||||
notifications:
|
||||
email: false
|
||||
```
|
||||
|
||||
|
||||
## Conclusione
|
||||
|
||||
I testing d'unità aumenteranno notevolmente la qualità e l'affidabilità di un progetto se usati adeguatamente, ma ti richiederanno di strutturare il codice in maniera diversa dal solito.
|
||||
|
||||
Per un esempio di mod con molti testing d'unità, vedere la mod [*crafting* di rubenwardy](https://github.com/rubenwardy/crafting).
|
||||
Per un esempio di mod con molti testing d'unità, vedere la [crafting di rubenwardy](https://github.com/rubenwardy/crafting).
|
||||
|
@ -3,50 +3,18 @@ layout: compress
|
||||
---
|
||||
|
||||
<!doctype html>
|
||||
|
||||
{% assign pathsplit = page.url | split: '/' %}
|
||||
{% assign language = pathsplit[1] %}
|
||||
{% assign language_info = site.data.languages | where: "code", language %}
|
||||
{% if language_info %}
|
||||
<html lang="{{ language }}">
|
||||
{% else %}
|
||||
<html>
|
||||
{% endif %}
|
||||
<head>
|
||||
<title>{% if page.homepage %}{% else %}{{ page.title }} - {% endif %}Luanti / Minetest Modding Book</title>
|
||||
<title>{% if page.homepage %}{% else %}{{ page.title }} - {% endif %}Minetest Modding Book</title>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width">
|
||||
<meta name="description" content="An easy guide to learn how to create mods for Minetest">
|
||||
<meta name="keywords" content="Minetest, modding, book, tutorial, guide, easy">
|
||||
<meta name="author" content="rubenwardy">
|
||||
<meta name="flattr:id" content="gl763e">
|
||||
<link rel="canonical" href="https://rubenwardy.com/minetest_modding_book{{ page.url }}">
|
||||
<meta name="og:url" content="https://rubenwardy.com/minetest_modding_book{{ page.url }}">
|
||||
<meta name="og:title" content="{{ page.title | escape }}">
|
||||
<meta name="og:author" content="rubenwardy">
|
||||
<meta name="og:site_name" content="Luanti Modding Book (formerly Minetest)">
|
||||
{% if page.description %}
|
||||
<meta name="og:description" content="{{ page.description | escape | strip }}">
|
||||
<meta name="description" content="{{ page.description | escape | strip }}">
|
||||
{% endif %}
|
||||
{% if page.image %}
|
||||
<meta name="og:image" content="{{ page.image | absolute_url }}">
|
||||
{% endif %}
|
||||
|
||||
{% assign oldSegment = "/" | append: language | append: "/" %}
|
||||
{% for other_lang in site.data.languages %}
|
||||
{% unless other_lang.code == language %}
|
||||
{% assign newSegment = "/" | append: other_lang.code | append: "/" %}
|
||||
|
||||
<link rel="alternate" hreflang="{{ other_lang.code }}"
|
||||
href="{{ page.url | replace: oldSegment, newSegment | relative_url }}">
|
||||
{% endunless %}
|
||||
{% endfor %}
|
||||
|
||||
{% if page.noindex %}
|
||||
<meta name="robots" content="noindex">
|
||||
{% endif %}
|
||||
|
||||
<style>body,html,nav{background:#333}nav,nav li,nav li a{display:block}body,html,main,nav li{margin:0;padding:0}main,nav{position:absolute;top:0}body,html{font-size:17px;color:#000}#container{width:100%;max-width:1100px;margin:auto;position:relative}nav{left:0;width:280px;list-style:none;color:#fff}nav li a{padding:5px;color:#ccc;text-decoration:none}main{left:280px;right:0}article{background:#fff;padding:0 20px 20px}</style>
|
||||
<link rel="stylesheet" href="{{ page.root }}/static/style.css?v=4">
|
||||
<link rel="stylesheet" href="{{ page.root }}/static/style.css?v=2">
|
||||
</head>
|
||||
<body>
|
||||
<div id="container">
|
||||
|
@ -8,12 +8,11 @@ layout: base
|
||||
|
||||
{% if language == "_it" %}
|
||||
{% assign language = "it" %}
|
||||
{% assign links = site.it %}
|
||||
{% assign links = site.it | sort: "idx" %}
|
||||
{% else %}
|
||||
{% assign language = "en" %}
|
||||
{% assign links = site.en %}
|
||||
{% assign links = site.en | sort: "idx" %}
|
||||
{% endif %}
|
||||
{% assign links = links | where_exp: "item", "item.sitemap != false" | sort: "idx" %}
|
||||
|
||||
{% assign num = 0 %}
|
||||
|
||||
@ -32,7 +31,9 @@ layout: base
|
||||
{% assign last_section = section %}
|
||||
{% assign num = num | plus:1 %}
|
||||
{% endfor %}
|
||||
<li><a href="https://github.com/rubenwardy/minetest_modding_book/archive/examples.zip" class="hr">Download Examples</a></li>
|
||||
|
||||
<li><a href="{{ page.root }}/lua_api.html" class="hr">Lua Modding API Reference</a></li>
|
||||
<li><a href="https://github.com/rubenwardy/minetest_modding_book/archive/examples.zip">Download Examples</a></li>
|
||||
</nav>
|
||||
|
||||
<main>
|
||||
@ -64,7 +65,7 @@ layout: base
|
||||
</ul>
|
||||
|
||||
<footer>
|
||||
© 2014-{{ site.time | date: '%Y' }}
|
||||
© 2014-20
|
||||
{% if language == "en" %}
|
||||
| Helpful? Consider
|
||||
<a href="https://rubenwardy.com/donate/">donating</a>
|
||||
|
@ -33,6 +33,68 @@ figure {
|
||||
padding: 0 0 0 6px;
|
||||
}
|
||||
|
||||
.notice-info {
|
||||
background: #ececec !important;
|
||||
border: 1px solid #aaa !important;
|
||||
}
|
||||
|
||||
.notice-danger {
|
||||
background: #fcc !important;
|
||||
border: 1px solid #a66 !important;
|
||||
}
|
||||
|
||||
.notice-warning {
|
||||
background: #FED;
|
||||
border: 1px solid #fc9;
|
||||
}
|
||||
|
||||
.notice-tip {
|
||||
background: #ccf;
|
||||
border: 1px solid #66a;
|
||||
}
|
||||
|
||||
.notice-green {
|
||||
background: #161;
|
||||
border: 1px solid #393;
|
||||
}
|
||||
|
||||
.notice {
|
||||
margin: 10px;
|
||||
display: block;
|
||||
padding: 5px;
|
||||
border-radius: 5px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.notice p {
|
||||
margin: 0 0 17px 0;
|
||||
}
|
||||
|
||||
.notice p:last-child {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.notice > span {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
width: 40px;
|
||||
font-size: 24px;
|
||||
text-align: center;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.notice > div {
|
||||
margin-left: 35px;
|
||||
}
|
||||
|
||||
.notice h2 {
|
||||
margin: 0 0 5px 0;
|
||||
padding: 0 0 2px 0;
|
||||
font-size: 100%;
|
||||
}
|
||||
|
||||
.header-link, .anchor {
|
||||
text-decoration: none;
|
||||
color: #bbb;
|
||||
@ -56,7 +118,7 @@ h1 {
|
||||
}
|
||||
|
||||
h2 {
|
||||
border-bottom: 1px solid #bbb;
|
||||
border-bottom: 1px solid black;
|
||||
margin: 30px 0 10px 0;
|
||||
display: block;
|
||||
padding: 0 0 5px 0;
|
||||
|
@ -1,121 +0,0 @@
|
||||
.feedback {
|
||||
background: white;
|
||||
padding: 1em;
|
||||
|
||||
input[name='username'], label[for='username'] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
h2 {
|
||||
border: none;
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.btn {
|
||||
--color-primary-dark: #007DB8;
|
||||
--color-primary-dark-highlight: #06aed5;
|
||||
|
||||
display: inline-block;
|
||||
padding: 0.375rem 0.75rem;
|
||||
margin: 0.25rem 0.25rem 0.25rem 0;
|
||||
font-size: 0.9375rem;
|
||||
line-height: 1.5;
|
||||
border-radius: 0.25rem;
|
||||
background: transparent;
|
||||
border: 1px solid transparent;
|
||||
color: white;
|
||||
transition: color 0.15s ease-in-out, filter 0.15s ease-in-out,
|
||||
background-color 0.15s ease-in-out, border-color 0.15s ease-in-out,
|
||||
box-shadow 0.15s ease-in-out;
|
||||
cursor: pointer;
|
||||
box-sizing: border-box;
|
||||
text-align: center;
|
||||
|
||||
&:hover {
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
background-color: rgba(255, 255, 255, 0.25);
|
||||
}
|
||||
|
||||
&.active {
|
||||
color: var(--color-primary-dark);
|
||||
}
|
||||
|
||||
img.icon {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background-color: var(--color-primary-dark);
|
||||
border-color: var(--color-primary-dark);
|
||||
|
||||
&:hover {
|
||||
background-color: var(--color-primary-dark-highlight);
|
||||
border-color: var(--color-primary-dark-highlight);
|
||||
}
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
button, input {
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
input, button, select, optgroup, textarea {
|
||||
margin: 0;
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
line-height: inherit;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
label {
|
||||
display: inline-block;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.form-control {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: calc(1.5em + 1.5rem + 2px);
|
||||
padding: 0.75rem 1rem;
|
||||
font-size: 0.9375rem;
|
||||
font-weight: 400;
|
||||
line-height: 1.5;
|
||||
color: #52575C;
|
||||
background-color: #fff;
|
||||
background-clip: padding-box;
|
||||
border: 1px solid #ced4da;
|
||||
border-radius: 0.25rem;
|
||||
transition: border-color 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out;
|
||||
transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
|
||||
transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out;
|
||||
}
|
||||
|
||||
.text-muted {
|
||||
color: #7A8288 !important;
|
||||
}
|
||||
.form-text {
|
||||
display: block;
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
|
||||
small, .small {
|
||||
font-size: 80%;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
textarea.form-control {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
textarea {
|
||||
overflow: auto;
|
||||
resize: vertical;
|
||||
}
|
@ -198,10 +198,3 @@ header span {
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@import "content";
|
||||
@import "code";
|
||||
@import "notice";
|
||||
@import "table";
|
||||
@import "feedback";
|
||||
|
@ -1,61 +0,0 @@
|
||||
.notice {
|
||||
margin: 2em 0;
|
||||
display: block;
|
||||
padding: 0.5rem;
|
||||
border-radius: 0.5rem;
|
||||
position: relative;
|
||||
|
||||
p {
|
||||
margin: 0 0 1em 0;
|
||||
}
|
||||
|
||||
p:last-child {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
& > span {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
width: 40px;
|
||||
font-size: 24px;
|
||||
text-align: center;
|
||||
display: block;
|
||||
}
|
||||
|
||||
& > div {
|
||||
margin-left: 35px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin: 0 0 5px 0;
|
||||
padding: 0 0 2px 0;
|
||||
font-size: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.notice-info {
|
||||
background: #ececec !important;
|
||||
border: 1px solid #aaa !important;
|
||||
}
|
||||
|
||||
.notice-danger {
|
||||
background: #fcc !important;
|
||||
border: 1px solid #a66 !important;
|
||||
}
|
||||
|
||||
.notice-warning {
|
||||
background: #FED;
|
||||
border: 1px solid #fc9;
|
||||
}
|
||||
|
||||
.notice-tip {
|
||||
background: #ccf;
|
||||
border: 1px solid #66a;
|
||||
}
|
||||
|
||||
.notice-green {
|
||||
background: #161;
|
||||
border: 1px solid #393;
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
table td, table th {
|
||||
border: 1px solid #ddd;
|
||||
padding: 8px;
|
||||
}
|
||||
table tr:nth-child(even) {
|
||||
background-color: #f2f2f2;
|
||||
}
|
||||
table th {
|
||||
padding: 12px 8px;
|
||||
text-align: left;
|
||||
background-color: #04AA6D;
|
||||
color: white;
|
||||
}
|
14
cat.html
Normal file
14
cat.html
Normal file
@ -0,0 +1,14 @@
|
||||
---
|
||||
layout: none
|
||||
---
|
||||
|
||||
{% if site.calc_word_count %}
|
||||
{% assign chapters = site.en | sort: "idx" %}
|
||||
{% for chapter in chapters %}
|
||||
{% unless chapter.homepage %}
|
||||
{{ chapter.content }}
|
||||
{% endunless %}
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
Cat disabled.
|
||||
{% endif %}
|
@ -1,29 +0,0 @@
|
||||
---
|
||||
title: Thanks for your feedback
|
||||
layout: base
|
||||
root: .
|
||||
sitemap: false
|
||||
noindex: true
|
||||
---
|
||||
|
||||
<main>
|
||||
<article>
|
||||
<h1>Luanti Modding Book (formerly Minetest)</h1>
|
||||
|
||||
<h2>Thanks for sharing your feedback!</h2>
|
||||
|
||||
<p>
|
||||
You're helping to make the modding book better.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<a href="{{ '/index.html' | relative_url }}">
|
||||
Back to the book
|
||||
</a>
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<footer>
|
||||
© 2014-{{ site.time | date: '%Y' }}
|
||||
</footer>
|
||||
</main>
|
30
index.html
30
index.html
@ -1,13 +1,15 @@
|
||||
---
|
||||
layout: none
|
||||
---
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Luanti Modding Book (formerly Minetest)</title>
|
||||
<meta name="og:description" content="An easy guide to learn how to create mods for Minetest">
|
||||
<meta name="description" content="An easy guide to learn how to create mods for Minetest">
|
||||
<link rel="canonical" href="https://rubenwardy.com/minetest_modding_book/">
|
||||
<title>Redirecting...</title>
|
||||
</head>
|
||||
<body>
|
||||
Detecting and redirecting to the correct translation.<br><br>
|
||||
|
||||
<a href="en/index.html">View English Translation</a><br><br>
|
||||
|
||||
<script>
|
||||
var languages = {{ site.data.languages | jsonify }};
|
||||
function getLanguage() {
|
||||
@ -22,18 +24,12 @@ layout: none
|
||||
}
|
||||
|
||||
var language = getLanguage() || languages[0];
|
||||
window.location.replace(language.code + "/index.html");
|
||||
var url = language.code + "/index.html";
|
||||
document.write('<a href="' + language.code +
|
||||
'/index.html">Redirecting to the ' + language.name +
|
||||
' version...</a>');
|
||||
|
||||
window.location.replace(url);
|
||||
</script>
|
||||
<meta http-equiv="refresh" content="0;URL='{{ 'en/index.html' | absolute_url }}'" />
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<h1>Luanti Modding Book (formerly Minetest)</h1>
|
||||
<p>An easy guide to learn how to create mods for Minetest.</p>
|
||||
<p>Detecting and redirecting to the correct translation.</p>
|
||||
<p>
|
||||
<a href="en/index.html">View Luanti Modding Book in English</a>
|
||||
</p>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -6,7 +6,7 @@ root: .
|
||||
|
||||
<main>
|
||||
<article>
|
||||
<h1>Luanti Modding Book (formerly Minetest)</h1>
|
||||
<h1>Minetest Modding Book</h1>
|
||||
|
||||
<h2>Choose a Language</h2>
|
||||
|
||||
@ -19,6 +19,6 @@ root: .
|
||||
</article>
|
||||
|
||||
<footer>
|
||||
© 2014-{{ site.time | date: '%Y' }}
|
||||
© 2014-20
|
||||
</footer>
|
||||
</main>
|
||||
|
6954
lua_api.html
6954
lua_api.html
File diff suppressed because it is too large
Load Diff
12
sitemap.json
12
sitemap.json
@ -1,7 +1,7 @@
|
||||
---
|
||||
---
|
||||
|
||||
{% assign pages = site.en | where_exp: "item", "item.sitemap != false" | sort: "idx" %}
|
||||
{% assign pages = site.en | sort: "idx" %}
|
||||
{% assign num = 0 %}
|
||||
|
||||
[
|
||||
@ -12,7 +12,7 @@
|
||||
{
|
||||
"idx": {{ link.idx }},
|
||||
"title": "{{ link.title }}",
|
||||
"loc": "https://rubenwardy.com/minetest_modding_book{{ link.url }}",
|
||||
"loc": "https://rubenwardy.com/minetest_modding_book/{{ link.url }}",
|
||||
{% if link.description %}
|
||||
"description": "{{ link.description }}",
|
||||
{% endif %}
|
||||
@ -22,6 +22,14 @@
|
||||
|
||||
{% assign num = num | plus:1 %}
|
||||
{% endfor %}
|
||||
|
||||
{
|
||||
"title": "Lua Modding API Reference",
|
||||
"loc": "https://rubenwardy.com/minetest_modding_book/lua_api.html",
|
||||
"description": "lua_api.html is an HTML version of lua_api.txt",
|
||||
"priority": 0.75
|
||||
},
|
||||
|
||||
{
|
||||
"title": "Download Examples",
|
||||
"loc": "https://github.com/rubenwardy/minetest_modding_book/archive/examples.zip",
|
||||
|
BIN
static/pixel_art_gimp_pencil.png
Normal file
BIN
static/pixel_art_gimp_pencil.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 98 KiB |
BIN
static/pixel_art_gimp_rubber.png
Normal file
BIN
static/pixel_art_gimp_rubber.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 28 KiB |
@ -2,3 +2,5 @@
|
||||
---
|
||||
|
||||
@import "main";
|
||||
@import "content";
|
||||
@import "code";
|
||||
|
2
utils/requirements.txt
Normal file
2
utils/requirements.txt
Normal file
@ -0,0 +1,2 @@
|
||||
markdown==2.6.8
|
||||
bs4==0.0.1
|
112
utils/update_lua_api.py
Normal file
112
utils/update_lua_api.py
Normal file
@ -0,0 +1,112 @@
|
||||
import markdown, datetime, re, string
|
||||
from bs4 import BeautifulSoup
|
||||
from urllib.request import urlopen
|
||||
|
||||
def get_key(index, li, title):
|
||||
title = title.replace(" ", "-")
|
||||
title = title.replace(" ", "-")
|
||||
title = title.replace(" ", "-")
|
||||
#title = re.sub(r'\([^)]*\)', '', title)
|
||||
title = pattern.sub('', title)
|
||||
|
||||
if title == "":
|
||||
return None
|
||||
|
||||
i = 0
|
||||
while True:
|
||||
key = title
|
||||
if i > 0:
|
||||
key = key + "_" + str(i)
|
||||
i = i + 1
|
||||
try:
|
||||
existing = index[key]
|
||||
except KeyError:
|
||||
return key
|
||||
|
||||
#
|
||||
# Downloading lua_api.txt
|
||||
#
|
||||
print("Downloading lua_api.txt...")
|
||||
|
||||
url = "https://raw.githubusercontent.com/minetest/minetest/master/doc/lua_api.txt"
|
||||
text = urlopen(url).read().decode("utf-8")
|
||||
|
||||
|
||||
print("Pre-generation replacements...")
|
||||
|
||||
header = """Minetest Lua Modding API Reference
|
||||
=================================="""
|
||||
text = text.replace(header, "")
|
||||
|
||||
#
|
||||
# Generating HTML
|
||||
#
|
||||
print("Generating HTML...")
|
||||
md = markdown.Markdown(extensions=['markdown.extensions.toc'])
|
||||
html = md.convert(text)
|
||||
|
||||
print("Post-generation replacements...")
|
||||
links = """<ul>
|
||||
<li>More information at <a href="http://www.minetest.net/">http://www.minetest.net/</a></li>
|
||||
<li>Developer Wiki: <a href="http://dev.minetest.net/">http://dev.minetest.net/</a></li>
|
||||
</ul>"""
|
||||
|
||||
html = html.replace("{{", "{ {")
|
||||
html = html.replace(links, "")
|
||||
|
||||
|
||||
credit = "This page was last updated "
|
||||
credit += datetime.date.today().strftime("%d/%B/%Y")
|
||||
credit += ".<br />See <a href=\"https://github.com/minetest/minetest/blob/master/doc/lua_api.txt\">doc/lua_api.txt</a> for the latest version (in plaintext)."
|
||||
credit += "<br />Generated using <a href=\"https://github.com/rubenwardy/minetest_modding_book/blob/gh-pages/update_lua_api.py\">a Python script</a>."
|
||||
links += credit
|
||||
html = html.replace("<h2 id=\"programming-in-lua\">", links + "<h2 id=\"programming-in-lua\">")
|
||||
|
||||
print("Parsing HTML...")
|
||||
soup = BeautifulSoup(html, 'html.parser')
|
||||
|
||||
pattern = re.compile('[\W]+')
|
||||
lis = soup.find_all("li")
|
||||
index = {}
|
||||
|
||||
# Build index of anchors
|
||||
headings = soup.find_all({"h1", "h2", "h3", "h4", "h5", "h6"})
|
||||
for tag in headings:
|
||||
if tag.has_attr("id"):
|
||||
index[tag["id"]] = True
|
||||
if tag.has_attr("name"):
|
||||
index[tag["name"]] = True
|
||||
|
||||
# Add anchors to <li>s containing <code>
|
||||
for li in lis:
|
||||
code = li.find_all('code')
|
||||
if len(code) > 0:
|
||||
key = get_key(index, li, code[0].string)
|
||||
if key is not None:
|
||||
index[key] = True
|
||||
#print("Created " + key)
|
||||
new_tag = soup.new_tag('a', href="#" + key)
|
||||
new_tag['class'] = "anchor"
|
||||
new_tag['name'] = key
|
||||
new_tag.string = "#"
|
||||
li.insert(0, new_tag)
|
||||
|
||||
|
||||
html = str(soup)
|
||||
|
||||
#
|
||||
# Writing to file
|
||||
#
|
||||
print("Writing to file...")
|
||||
file = open("lua_api.html", "w")
|
||||
file.write("---\ntitle: Lua Modding API Reference\nlayout: default\n---\n")
|
||||
file.write("<div class='notice notice-info'>\n")
|
||||
file.write("<h2>This is lua_api.txt nicely formated: I did not write this</h2>\n")
|
||||
file.write(credit)
|
||||
file.write("</div>\n")
|
||||
file.write("<h2 id=\"table-of-contents\">Table of Contents</h2>\n")
|
||||
file.write(md.toc)
|
||||
file.write(html)
|
||||
file.close()
|
||||
|
||||
print("Done")
|
Loading…
Reference in New Issue
Block a user