Compare commits

...

157 Commits

Author SHA1 Message Date
rubenwardy
b6120063a5 Read More: Remove dead link 2024-10-23 12:03:50 +01:00
rubenwardy
412df5d192 Rename Minetest -> Luanti 2024-10-23 00:41:04 +01:00
rubenwardy
e896108377 Recommend core. instead of minetest. 2024-10-23 00:39:22 +01:00
rubenwardy
05307506a6 Storage: Fix typo in code snippet 2024-08-06 18:23:52 +01:00
rubenwardy
f15a2fc290 Releasing: Fix broken link 2024-01-18 20:02:43 +00:00
rubenwardy
ecdefc67ac Remove feedback form 2024-01-10 22:31:47 +00:00
rubenwardy
2cb0a93e6d Chat: Fix */. explanation 2024-01-04 00:22:33 +00:00
Misnad Khasim
21e47a80ec Node Drawtypes: Fix a small typo in example code 2023-11-30 17:09:11 +00:00
rubenwardy
18b92ca388 Clarify tool definition
Fixes #122
2023-10-19 19:38:59 +01:00
rubenwardy
a38d0b7ad9 Update lua_api.html 2023-09-22 22:59:50 +00:00
rubenwardy
5740198898 Fix armor_groups 2023-09-05 23:47:56 +01:00
rubenwardy
6064c662f6 Add WTFPL link 2023-07-01 23:54:54 +01:00
Zughy
5339b83fa4 Improve Italian translation 2023-05-30 09:17:20 +00:00
rubenwardy
9e68fff033 Fix typo in feedback form 2023-05-30 09:56:49 +01:00
rubenwardy
84e46d9fc4 Rename send feedback button 2023-05-14 14:22:31 +01:00
rubenwardy
2850211d4f Fix site_name 2023-05-14 14:20:44 +01:00
rubenwardy
b67baac70b Fix feedback thanks page 2023-05-14 14:19:38 +01:00
rubenwardy
3cd8d52da0 Add feedback form 2023-05-14 14:09:10 +01:00
rubenwardy
fdf8976570 Add OpenGraph tags 2023-05-14 14:09:10 +01:00
rubenwardy
b1b9177c62 Delete cat.html 2023-05-07 13:57:36 +00:00
rubenwardy
bc5f697fbd Fix sitemaps 2023-05-01 13:28:27 +01:00
Zughy
1764aac705 IT | Update Read More chapter 2023-05-01 13:19:45 +01:00
rubenwardy
0340426a6f Replace lua_api.txt with lua_api.md 2023-05-01 12:22:02 +01:00
rubenwardy
419bf83d6c Fix broken links 2023-04-30 01:06:04 +01:00
rubenwardy
5e96a878bc Update index.html content 2023-04-30 00:37:27 +01:00
rubenwardy
45516a7589 Add canonical tag to homepage 2023-04-30 00:35:50 +01:00
rubenwardy
c252785070 Improve index.html: client-side redirect 2023-04-29 00:30:47 +01:00
rubenwardy
a709ec0fea Fix copyright year 2023-04-26 21:15:28 +00:00
Zughy
9ec80ec84b IT | callback chapter, translate title and description 2023-04-19 23:32:53 +02:00
Zughy
b9a3015124 IT | translate callbacks chapter 2023-04-19 23:28:13 +02:00
rubenwardy
69bb5c1a0d Common Mistakes: Fix typo 2023-04-11 17:52:09 +00:00
rubenwardy
7cf48f5132 Formspecs: Fix spelling of features 2023-01-27 14:04:31 +00:00
rubenwardy
46a909aae7 Update LICENSE 2022-09-26 13:31:46 +00:00
rubenwardy
72fa14880f Add LICENSE 2022-09-26 13:30:12 +00:00
debiankaios
bcb6c0dbb4 Fix various code typos/issues 2022-08-21 16:39:43 +00:00
rubenwardy
13a50925f1 Remove links to dev wiki 2022-08-15 23:29:44 +01:00
rubenwardy
56e157adc7 Callbacks: Fix code errors 2022-08-05 16:12:27 +01:00
rubenwardy
51cf848657 Inventories: Update chapter 2022-08-01 19:05:44 +01:00
rubenwardy
bfc343d1f5 Creating Textures: Update chapter 2022-08-01 19:02:25 +01:00
rubenwardy
52bdbab871 Item Callbacks: Fix description 2022-07-31 20:52:27 +01:00
rubenwardy
f7d2a61fbd Fix sitemap: false being included in sitemap.json 2022-07-31 20:40:47 +01:00
rubenwardy
df46f465cd Remove SFINV chapter 2022-07-31 20:33:39 +01:00
rubenwardy
f8c9da8261 Item Callbacks: Add chapter 2022-07-31 20:10:04 +01:00
rubenwardy
bc8820c4b6 Inventories: Update chapter
#104
2022-07-31 17:57:55 +01:00
rubenwardy
b5fcac7ed5 Lua Scripting: Update chapter 2022-07-31 17:39:25 +01:00
rubenwardy
e88b892551 Getting Started: Improve chapter 2022-07-31 17:23:13 +01:00
rubenwardy
e4bb144c7d Remove mentions of Atom editor 2022-07-12 20:16:18 +01:00
rubenwardy
8a021d8b39 Translations: Fix typos and improve wording 2022-07-04 17:47:23 +01:00
Zughy
b5c530c99e IT | small fixes 2022-06-19 22:29:35 +00:00
Zughy
f9bc3f1fc0 IT | translation chapter + cleanup 2022-06-19 22:18:53 +00:00
rubenwardy
f6c8a42696 Translations: Fix typos 2022-06-18 21:44:03 +01:00
rubenwardy
83067e5027 Chat: Restructure layout 2022-06-18 21:36:02 +01:00
rubenwardy
4506a8c3da Chat: Fix syntax error 2022-06-18 21:23:35 +01:00
rubenwardy
14a356ded1 Chat: Add string.split 2022-06-18 21:20:15 +01:00
rubenwardy
80605008c9 IT: Add missing Translations chapter 2022-06-17 17:39:28 +01:00
rubenwardy
8163b0cb37 Translation: Add chapter 2022-06-17 17:34:22 +01:00
rubenwardy
a5b822122f Remove information on basic Lua programming, link to PiL 2022-06-14 00:33:55 +01:00
rubenwardy
ca08a9af49 Remove mentions of Travis 2022-06-14 00:21:06 +01:00
rubenwardy
770a6f4e2e Fix <link rel=alternative> URLs 2022-06-07 18:14:53 +01:00
rubenwardy
7e12752e12 Fix canonical link 2022-06-07 18:10:31 +01:00
rubenwardy
f6f17982a2 Fix .gitlab-ci.yml 2022-06-07 18:07:46 +01:00
rubenwardy
6494e4b341 Add language and <link> tags 2022-06-07 17:55:25 +01:00
Zughy
f1fb608e98 IT | small fixes 2022-03-30 12:39:54 +00:00
rubenwardy
310f2d120d Lua: Reorder programming section 2022-03-22 12:25:47 +00:00
rubenwardy
6c8ef461b7 Update .gitlab-ci.yml 2022-03-21 00:39:02 +00:00
rubenwardy
fb99ab6d6c Fix syntax error in Lua in environment.md 2021-06-26 23:15:26 +01:00
rubenwardy
0c6cfc1563 Small improvements 2021-05-08 21:13:25 +01:00
rubenwardy
089ea288dd Remove ChatCmdBuilder chapter 2021-05-07 16:17:28 +01:00
Travis Wrightsman
f45055554b Fix modpack conf file extension 2021-03-27 17:02:32 +01:00
rubenwardy
5e0399a7af Storage: Fix example
Fixes #106
2021-02-21 04:26:49 +00:00
rubenwardy
f1f051c3cb Inventories: 0 is used to prevent take, not -1 2021-02-21 04:23:48 +00:00
Zughy
c455aff265 IT | Update to the latest commits 2021-01-28 00:13:08 +00:00
rubenwardy
d2252cfd95 Objects: HitPoints -> Health Points 2021-01-27 18:43:06 +00:00
rubenwardy
ae2ba6898f Lua Scripting: Fix mistakes 2021-01-27 16:37:21 +00:00
rubenwardy
12e6cb7291 Update copyright year 2021-01-25 12:26:08 +00:00
rubenwardy
8892bcd8e9 Items: Split Item Aliases from Item Names 2021-01-25 12:18:39 +00:00
rubenwardy
3e265da9b5 Objects: Add damage documentation 2021-01-25 11:05:28 +00:00
rubenwardy
c258e67f35 Update writing guide 2021-01-25 08:57:13 +00:00
rubenwardy
6622a49cd1 Basic Map Operations: Improve chapter
Fixes #100
2021-01-25 08:51:11 +00:00
rubenwardy
e8e16eee5d LVM: Fix ContentID inaccuracy 2021-01-25 08:38:39 +00:00
Ezhh
d12e5244b3 Lua Scripting: Improve chapter 2021-01-25 08:35:55 +00:00
rubenwardy
80bc6d32d8 Improve issues caused by cleanup commit 2021-01-05 20:51:13 +00:00
rubenwardy
658c35e35c Node Timers: Remove unnecessary information 2021-01-05 20:50:53 +00:00
rubenwardy
84aadea47a Basic Map Operations: Fix distance check being 2d 2020-12-24 19:45:10 +00:00
Zughy
bca09bd7ab IT | Update to latest commits 2020-12-24 18:40:59 +00:00
rubenwardy
dc54e0786e Fix missing HR 2020-12-24 13:41:51 +00:00
rubenwardy
e5add369f8 Remove lua_api.html 2020-12-24 13:38:26 +00:00
rubenwardy
37e530728b Improve various chapters 2020-12-24 13:25:31 +00:00
Zughy
25d346a15b IT | Small fixes 2020-10-14 18:18:55 +00:00
Zughy
704c3d84cf IT | Several small improvements 2020-08-19 22:49:39 +00:00
rubenwardy
62e2d0f835 Improve GitLab Pages build speed 2020-08-18 23:43:42 +01:00
rubenwardy
acdc71b34f Add language changing button 2020-08-18 23:38:19 +01:00
rubenwardy
725240d523 Fix various issues with translated nav bar 2020-08-18 21:57:46 +01:00
Marco
b289ec8f32 Index - changed the link related to my nick 2020-08-18 21:36:24 +01:00
Marco
85743781e5 Read More - Italian translation added. BOOM 2020-08-18 21:36:24 +01:00
Marco
079e0468e9 Releasing a Mod - Italian translation added 2020-08-18 21:36:24 +01:00
Marco
f97afc2f99 Automatic Unit Testing - Italian translation added 2020-08-18 21:36:24 +01:00
Marco
1df1473ea7 Intro to Clean Architectures - Italian translation added 2020-08-18 21:36:24 +01:00
Marco
7bd6469be2 Security - Italian translation added 2020-08-18 21:36:24 +01:00
Marco
bb7b24ad2f Automatic Error Checking - Italian translation added 2020-08-18 21:36:24 +01:00
Marco
21f9abe169 Common Mistakes - Italian translation added 2020-08-18 21:36:24 +01:00
Marco
2b6e929e2f Creating Games - Italian translation added 2020-08-18 21:36:24 +01:00
Marco
39d9777bb6 Lua Voxel Manipulators - Italian translation added 2020-08-18 21:36:24 +01:00
Marco
90787e574a Biomes and Decorations - Italian translation added 2020-08-18 21:36:24 +01:00
Marco
50687bc868 I reverted the whole 'players' folder like an idiot. Bless the local copy on the other PC 2020-08-18 21:36:24 +01:00
Marco
c0ca655327 Privileges - Italian translation added 2020-08-18 21:36:24 +01:00
Marco
af9e25f026 Objects, Players, and Entities - Italian translation added 2020-08-18 21:36:24 +01:00
Marco
35d9bf5a91 Storage and Metadata - Italian translation added 2020-08-18 21:36:24 +01:00
Marco
073bdaeb56 Node Timers and ABMs - Italian translation added 2020-08-18 21:36:24 +01:00
Marco
1c3dc8b070 Basic Map Operations - Italian translation added 2020-08-18 21:36:24 +01:00
Marco
03625f6448 ItemStacks and Inventories - Italian translation added 2020-08-18 21:36:24 +01:00
Zughy
4f00b12b1d Getting Started - small fixes 2020-08-18 21:36:24 +01:00
Zughy
4528250c0c 5 - Node Drawtypes, italian translation added 2020-08-18 21:36:24 +01:00
Marco
72a1d42082 4 - Creating Textures, italian translation added 2020-08-18 21:36:24 +01:00
Marco
765e60a133 3 - Nodes, Items and Crafting, Italian translation 2020-08-18 21:36:24 +01:00
Marco
287da22236 Italian language support added in _config.yml 2020-08-18 21:36:24 +01:00
Marco
e2b2cd79de 2 - Lua Scripting, Italian translation added 2020-08-18 21:36:24 +01:00
Marco
2509ac89f1 1 - Getting Started, Italian title and description 2020-08-18 21:36:24 +01:00
Marco
f408689104 1 - Getting Started, Italian translation added 2020-08-18 21:36:24 +01:00
Marco
96d6447281 Homepage, Italian translation added 2020-08-18 21:36:24 +01:00
Marco
f802a7fae1 Settings for Italian translation ready, _it folder created 2020-08-18 21:36:24 +01:00
Vinicius Martins
bffaa74d37 Update inventories.md
inv.get_size is a method so should be called with ":"
2020-08-08 22:56:50 +00:00
Zughy
0b71965a6a GUIs (Formspecs): A few fixes + missing description 2020-07-14 03:04:38 +00:00
Zughy
ec3a95cea6 Description + teeny tiny fix 2020-07-08 11:37:19 +02:00
Zughy
a635cc2c90 Several small changes 2020-07-05 23:26:25 +00:00
rubenwardy
cd1fea40a1 Update copyright year 2020-06-25 13:36:05 +00:00
Panquesito7
bd19594bf2 Add build status badge on README.md 2020-05-04 21:56:57 +00:00
Marco
ad3bb59e0a Update links to Gitlab and fix repetition 2020-05-04 22:55:54 +01:00
rubenwardy
d22f99669e Formspecs: Update for 5.2 2020-05-04 22:15:11 +01:00
rubenwardy
a2efa58809 Fix syntax error in README.md 2020-04-19 20:47:11 +00:00
rubenwardy
2b75ac00da Biomes and Decorations: Improve and fix errors 2020-03-28 20:22:29 +00:00
Ezhh
a465572ae5 Biomes and Decorations: Create chapter 2020-03-28 20:22:25 +00:00
rubenwardy
82aca38569 Commit dictionary 2020-03-28 20:00:12 +00:00
rubenwardy
390eec68ac Objects and Entities: Improve object property and entity table descriptions 2020-03-28 19:59:39 +00:00
rubenwardy
02658835b4 Add Gitlab Pages support 2019-11-30 20:47:28 +00:00
rubenwardy
8b7950066d Create advanced map section 2019-08-14 14:45:42 +01:00
rubenwardy
6e09b6f447 Formspecs: Rewrite chapter 2019-08-14 14:39:42 +01:00
Panquesito7
4feb9b4251 Fix wiki link (#100) 2019-08-14 00:51:46 +01:00
rubenwardy
048ebb2887 Fix table of content capitalisation 2019-08-13 15:09:51 +01:00
rubenwardy
a38a120833 ItemStacks and Inventories: Add detached inventory creation
Fixes #69
2019-07-01 21:48:08 +01:00
rubenwardy
6fa3c37871 Objects, Players, and Entities: add deg/rad warning, add attachment example
Fixes #70
2019-07-01 21:01:51 +01:00
rubenwardy
990883c439 LVM: Warn about minetest.get_mapgen_object('voxelmanip')
Fixes #94
2019-07-01 21:01:45 +01:00
rubenwardy
10bf0e31ce Clean Architecture: Reorder sections 2019-05-31 18:54:30 +01:00
rubenwardy
79a522f852 Fix grammar issues spotted by grammarly 2019-05-31 18:32:40 +01:00
rubenwardy
2934517a5d Node Drawtypes: Rewrite chapter 2019-03-26 02:52:05 +00:00
rubenwardy
059ce7fde7 Lua Scripting: Fix dead link 2019-03-25 18:05:59 +00:00
rubenwardy
19236cddf7 Getting Started: Add where are mods stored? 2019-03-25 18:04:09 +00:00
Hugo Locurcio
70aa77865d Enable colors when using Luacheck in Travis CI (#96)
Travis CI has supported colored console output for a while now.
2019-02-22 14:37:16 +00:00
ANAND
9479d5cbc1 Fix incorrect capitalisation 2019-01-17 11:49:59 +00:00
rubenwardy
d1986dcfa7
Nodes, Items, and Crafting: Fix typo 2018-12-17 23:06:02 +00:00
rubenwardy
ff1f0ec32c
Add Flattr meta to <head> 2018-11-18 22:08:11 +00:00
rubenwardy
6465c125c8 Node Timers and ABMs: Add note on return true in on_timer 2018-11-14 15:09:43 +00:00
rubenwardy
cea9940037 Node Timers and ABMs: Create from ABMs chapter 2018-11-14 03:43:45 +00:00
rubenwardy
9e7657621a
Improve wording and grammar [3/3] 2018-10-27 03:10:37 +01:00
rubenwardy
5cad89cacc
Fix some Americanisms 2018-10-20 02:33:33 +01:00
rubenwardy
3819933de0
Improve wording and grammar [2/3] 2018-10-20 01:37:41 +01:00
rubenwardy
ab55b0d779
Update chapter IDs to be consistent 2018-10-19 14:29:23 +01:00
94 changed files with 8411 additions and 9490 deletions

81
.gitignore vendored
View File

@ -1,10 +1,19 @@
*.zip
# Created by https://www.gitignore.io/api/node,ruby,linux,jekyll
# Created by https://www.gitignore.io/api/ruby,code,linux,jekyll
# Edit at https://www.gitignore.io/?templates=ruby,code,linux,jekyll
### Code ###
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
!.vscode/spellright.dict
### Jekyll ###
_site/
.sass-cache/
.jekyll-cache/
.jekyll-metadata
### Linux ###
@ -22,67 +31,6 @@ _site/
# .nfs files are created when an open file is removed but is still being accessed
.nfs*
### Node ###
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# nyc test coverage
.nyc_output
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Typescript v1 declaration files
typings/
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
### Ruby ###
*.gem
*.rbc
@ -95,11 +43,13 @@ typings/
/test/tmp/
/test/version_tmp/
/tmp/
package-lock.json
# Used by dotenv library to load environment variables.
# .env
# Ignore Byebug command history file.
.byebug_history
## Specific to RubyMotion:
.dat*
.repl_history
@ -113,7 +63,6 @@ build-iPhoneSimulator/
# We recommend against adding the Pods directory to your .gitignore. However
# you should judge for yourself, the pros and cons are mentioned at:
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
#
# vendor/Pods/
## Documentation cache and generated files:
@ -136,4 +85,4 @@ build-iPhoneSimulator/
# unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
.rvmrc
# End of https://www.gitignore.io/api/node,ruby,linux,jekyll
# End of https://www.gitignore.io/api/ruby,code,linux,jekyll

31
.gitlab-ci.yml Normal file
View File

@ -0,0 +1,31 @@
image: jekyll/jekyll:4
variables:
JEKYLL_ENV: production
LC_ALL: C.UTF-8
before_script:
- rm Gemfile.lock
- bundle install
test:
stage: test
interruptible: true
script:
- bundle exec jekyll build -d test
artifacts:
paths:
- test
except:
- master
pages:
stage: deploy
interruptible: true
script:
- bundle exec jekyll build -d public
artifacts:
paths:
- public
only:
- master

10
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,10 @@
{
"spellright.language": [
"en_GB"
],
"spellright.documentTypes": [
"latex",
"plaintext",
"markdown"
]
}

9
.vscode/spellright.dict vendored Normal file
View File

@ -0,0 +1,9 @@
metatables
Lua
metatable
singleplayer
Staticdata
biomes
Voronoi

10
Gemfile Normal file
View File

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

75
Gemfile.lock Normal file
View File

@ -0,0 +1,75 @@
GEM
remote: https://rubygems.org/
specs:
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)
eventmachine (>= 0.12.9)
http_parser.rb (~> 0.6.0)
eventmachine (1.2.7)
ffi (1.15.0)
forwardable-extended (2.6.0)
http_parser.rb (0.6.0)
i18n (1.8.10)
concurrent-ruby (~> 1.0)
jekyll (4.2.0)
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-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)
jekyll-redirect-from (0.16.0)
jekyll (>= 3.3, < 5.0)
jekyll-sass-converter (2.1.0)
sassc (> 2.0.1, < 3.0)
jekyll-sitemap (1.4.0)
jekyll (>= 3.7, < 5.0)
jekyll-watch (2.2.1)
listen (~> 3.0)
kramdown (2.3.1)
rexml
kramdown-parser-gfm (1.1.0)
kramdown (~> 2.0)
liquid (4.0.3)
listen (3.5.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)
rb-inotify (0.10.1)
ffi (~> 1.0)
rexml (3.2.5)
rouge (3.26.0)
safe_yaml (1.0.5)
sassc (2.4.0)
ffi (~> 1.9)
terminal-table (2.0.0)
unicode-display_width (~> 1.1, >= 1.1.1)
unicode-display_width (1.7.0)
webrick (1.7.0)
PLATFORMS
x86_64-linux
DEPENDENCIES
jekyll
jekyll-redirect-from
jekyll-sitemap
webrick
BUNDLED WITH
2.2.16

427
LICENSE Normal file
View File

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

View File

@ -1,8 +1,9 @@
# Minetest Modding Book
# Luanti 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/)
Book written by rubenwardy.
Book written by rubenwardy.
License: CC-BY-SA 3.0
## Finding your way around
@ -22,15 +23,25 @@ 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 Guidelines
### Chapter and Writing Guide
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
@ -67,9 +78,9 @@ Explain why/how these concepts are useful in modding
Paragraphs
```lua
\```lua
code
```
\```
## Parts in

View File

@ -1,3 +1,6 @@
url: "https://rubenwardy.com"
baseurl: "/minetest_modding_book"
sass:
# nested (default), compact, compressed, expanded
style: compressed
@ -9,3 +12,5 @@ plugins:
collections:
en:
output: true
it:
output: true

View File

@ -1,3 +1,9 @@
# cta = call to action (used when prompting user their language is available)
- code: en
name: English (UK)
cta: This book is available in English
- code: it
name: Italiano
cta: Questo libro è disponibile in italiano

243
_en/advmap/biomesdeco.md Normal file
View File

@ -0,0 +1,243 @@
---
title: Biomes and Decorations
author: Shara
layout: default
root: ../..
idx: 6.1
description: Create biomes and decorations to customise the map
---
## Introduction <!-- omit in toc -->
The ability to register biomes and decorations is vital when aiming to create an
interesting and varied in-game environment. This chapter teaches you how to
register biomes, how to control biome distribution, and how to place decorations in biomes.
- [What are Biomes?](#what-are-biomes)
- [Biome Placement](#biome-placement)
- [Heat and Humidity](#heat-and-humidity)
- [Visualising Boundaries using Voronoi Diagrams](#visualising-boundaries-using-voronoi-diagrams)
- [Creating a Voronoi Diagram using Geogebra](#creating-a-voronoi-diagram-using-geogebra)
- [Registering a Biome](#registering-a-biome)
- [What are Decorations?](#what-are-decorations)
- [Registering a Simple Decoration](#registering-a-simple-decoration)
- [Registering a Schematic Decoration](#registering-a-schematic-decoration)
- [Mapgen Aliases](#mapgen-aliases)
## What are Biomes?
A Minetest biome is a specific in-game environment. When registering biomes, you
can determine the types of nodes that appear in them during map generation.
Some of the most common types of node that may vary between biomes include:
* Top node: This is the node most commonly found on the surface. A well-known
example would be "Dirt with Grass" from Minetest Game.
* Filler node: This is the layer immediately beneath the top node.
In biomes with grass, it will often be dirt.
* Stone node: This is the node you most commonly see underground.
* Water node: This is usually a liquid and will be the node that appears
where you would expect bodies of water.
Other types of node can also vary between biomes, providing an opportunity
to create vastly different environments within the same game.
## Biome Placement
### Heat and Humidity
It is not enough to simply register a biome; you must also decide where it can
occur in game. This is done by assigning a heat and a humidity value to each biome.
You should think carefully about these values; they determine which biomes can
be neighbours to each other. Poor decisions could result in what is meant to
be a hot desert sharing a border with a glacier, and other improbable
combinations which you may prefer to avoid.
In game, heat and humidity values at any point of the map will usually be between
0 and 100. The values gradually change, increasing or decreasing as you move
around the map. The biome at any given point will be determined by which of the
registered biomes has heat and humidity values closest to those at that position on the map.
Because the changes in heat and humidity are gradual, it is good practice to assign
heat and humidity values to biomes based on reasonable expectations about that
biomes environment. For example:
* A desert might have high heat and low humidity.
* A snowy forest might have low heat and a medium humidity value.
* A swamp biome would generally have high humidity.
*
In practice, this means that, as long as you have a diverse range of biomes, you
are likely to find that the biomes which border each other form a logical progression.
### Visualising Boundaries using Voronoi Diagrams
<figure class="right_image">
<img src="{{ page.root }}/static/biomes_voronoi.png" alt="Vernoi">
<figcaption>
Voronoi diagram, showing the closest point.
<span class="credit">By <a href="https://en.wikipedia.org/wiki/Voronoi_diagram#/media/File:Euclidean_Voronoi_diagram.svg">Balu Ertl</a>, CC BY-SA 4.0.</span>
</figcaption>
</figure>
Fine-tuning heat and humidity values for biomes is
easier if you can visualise the relationship between the biomes you are using.
This is most important if you are creating a full set of your own biomes, but
can also be helpful if you are adding a biome to an existing set.
The simplest way to visualise which biomes may share borders is to create a
Voronoi diagram, which can be used to show which point on a 2-dimensional
diagram any given position is closest to.
A Voronoi diagram can reveal where biomes that should border each other do not,
and where biomes that should not border each other do. It can also give a
general insight into how common biomes will be in-game, with larger and more
central biomes being more common than smaller biomes or biomes that are located
on the outer edge of the diagram.
This is done by marking a point for each biome based on heat and humidity values,
where the x-axis is heat and the y-axis is humidity. The diagram is then
divided into areas, such that every position in a given area is closer to the
point inside that area than it is to any other point on the diagram.
Each area represents a biome. If two areas share a border, the biomes they
represent in-game can be located next to each other. The length of the border
shared between two areas, compared to the length shared with other areas, will
tell you how frequently two biomes are likely to be found next to each other.
### Creating a Voronoi Diagram using Geogebra
As well as drawing them by hand, you can also create Voronoi diagrams using
programs such as [Geogebra](https://www.geogebra.org).
1. Create points by selecting the point tool in the toolbar (icon is a point with 'A'),
and then clicking the chart. You can drag points around or explicitly set their
position in the left sidebar. You should also give each point a label, to make things clearer.
1. Next, create the voronoi by entering the following function into the
input box in the left sidebar:
```cpp
Voronoi({ A, B, C, D, E })
```
Where the each point is inside the curly brackets, separated by commas. You should now
3. Profit! You should now have a voronoi diagram with all draggable points.
## Registering a Biome
The following code registers a simple biome named grasslands biome:
```lua
core.register_biome({
name = "grasslands",
node_top = "default:dirt_with_grass",
depth_top = 1,
node_filler = "default:dirt",
depth_filler = 3,
y_max = 1000,
y_min = -3,
heat_point = 50,
humidity_point = 50,
})
```
This biome has one layer of Dirt with Grass nodes on the surface, and three layers
of Dirt nodes beneath this. It does not specify a stone node, so the node defined
in the mapgen alias registration for `mapgen_stone` will be present underneath the dirt.
There are many options when registering a biome, and these are documented
in the [Minetest Lua API Reference](https://minetest.gitlab.io/minetest/definition-tables/#biome-definition),
as always.
You dont need to define every option for every biome you create, but in some cases failure
to define either a specific option, or a suitable mapgen alias, can result in map generation errors.
## What are Decorations?
Decorations are either nodes or schematics that can be placed on the map at mapgen.
Some common examples include flowers, bushes, and trees. Other more creative uses
may include hanging icicles or stalagmites in caves, underground crystal formations,
or even the placement of small buildings.
Decorations can be restricted to specific biomes, by height, or by which nodes
they can be placed on. They are often used to develop the environment of a biome
by ensuring it has specific plants, trees or other features.
## Registering a Simple Decoration
Simple decorations are used to place single node decorations on the map during
map generation. You must specify the node that is to be placed as a decoration,
details for where it can be placed, and how frequently it occurs.
For example:
```lua
core.register_decoration({
deco_type = "simple",
place_on = {"base:dirt_with_grass"},
sidelen = 16,
fill_ratio = 0.1,
biomes = {"grassy_plains"},
y_max = 200,
y_min = 1,
decoration = "plants:grass",
})
```
In this example, the node named `plants:grass` will be placed in the biome named
grassy_plains on top of `base:dirt_with_grass` nodes, between the heights of `y = 1` and `y = 200`.
The fill_ratio value determines how frequently the decoration appears, with higher
values up to 1 resulting in a great number of decorations being placed. It is possible
to instead use noise parameters to determine placement.
## Registering a Schematic Decoration
Schematic decorations are very similar to simple decoration, but involve the placement
of a schematic instead of the placement of a single node. For example:
```lua
core.register_decoration({
deco_type = "schematic",
place_on = {"base:desert_sand"},
sidelen = 16,
fill_ratio = 0.0001,
biomes = {"desert"},
y_max = 200,
y_min = 1,
schematic = core.get_modpath("plants") .. "/schematics/cactus.mts",
flags = "place_center_x, place_center_z",
rotation = "random",
})
```
In this example the cactus.mts schematic is placed in desert biomes. You need to provide
a path to a schematic, which in this case is stored in a dedicated schematic directory within the mod.
This example also sets flags to center the placement of the schematic, and the rotation
is set to random. The random rotation of schematics when they are placed as decorations
helps introduce more variation when asymmetrical schematics are used.
## Mapgen Aliases
Existing games should already include suitable mapgen aliases, so you only need
to consider registering mapgen aliases of your own if you are making your own game.
Mapgen aliases provide information to the core mapgen, and can be registered in the form:
```lua
core.register_alias("mapgen_stone", "base:smoke_stone")
```
At a minimum you should register:
* mapgen_stone
* mapgen_water_source
* mapgen_river_water_source
If you are not defining cave liquid nodes for all biomes, you should also register:
* mapgen_lava_source

View File

@ -2,26 +2,33 @@
title: Lua Voxel Manipulators
layout: default
root: ../..
idx: 3.4
idx: 6.2
description: Learn how to use LVMs to speed up map operations.
redirect_from: /en/chapters/lvm.html
redirect_from:
- /en/chapters/lvm.html
- /en/map/lvm.html
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.
---
## Introduction
## Introduction <!-- omit in toc -->
The functions outlined in the [Basic Map Operations](environment.html) chapter
The functions outlined in the [Basic Map Operations](../map/environment.html) chapter
are convenient and easy to use, but for large areas they are inefficient.
Every time you call `set_node` or `get_node`, your mod needs to communicate with
the engine. This results in constant individual copying operations between the
engine and your mod, which is slow, and will quickly decrease the performance of
engine and your mod, which is slow and will quickly decrease the performance of
your game. Using a Lua Voxel Manipulator (LVM) can be a better alternative.
* [Concepts](#concepts)
* [Reading into the LVM](#reading-into-the-lvm)
* [Reading Nodes](#reading-nodes)
* [Writing Nodes](#writing-nodes)
* [Example](#example)
* [Your Turn](#your-turn)
- [Concepts](#concepts)
- [Reading into the LVM](#reading-into-the-lvm)
- [Reading Nodes](#reading-nodes)
- [Writing Nodes](#writing-nodes)
- [Example](#example)
- [Your Turn](#your-turn)
## Concepts
@ -38,16 +45,18 @@ and maximum positions that you need to modify. Then you can create and read into
an LVM. For example:
```lua
local vm = minetest.get_voxel_manip()
local vm = core.get_voxel_manip()
local emin, emax = vm:read_from_map(pos1, pos2)
```
For performance reasons, an LVM may not read the exact area you tell it to.
Instead, it may read a larger area. The larger area is given by `emin` and `emax`,
For performance reasons, an LVM will almost never read the exact area you tell it to.
Instead, it will likely read a larger area. The larger area is given by `emin` and `emax`,
which stand for *emerged min pos* and *emerged max pos*. An LVM will load the area
it contains for you - whether that involves loading from memory, from disk, or
calling the map generator.
{% include notice.html notice=page.mapgen_object %}
## Reading Nodes
To read the types of nodes at particular positions, you'll need to use `get_data()`.
@ -78,12 +87,13 @@ print(data[idx])
```
When you run this, you'll notice that `data[vi]` is an integer. This is because
the engine doesn't store nodes using their name string, as string comparison
is slow. Instead, the engine uses a content ID. You can find out the content
ID for a particular type of node with `get_content_id()`. For example:
the engine doesn't store nodes using strings, for performance reasons.
Instead, the engine uses an integer called a content ID.
You can find out the content ID for a particular type of node with
`get_content_id()`. For example:
```lua
local c_stone = minetest.get_content_id("default:stone")
local c_stone = core.get_content_id("default:stone")
```
You can then check whether the node is stone:
@ -95,9 +105,8 @@ if data[idx] == c_stone then
end
```
It is recommended that you find and store the content IDs of nodes types
at load time, because the IDs of a node type will never change. Make sure to store
the IDs in a local variable for performance reasons.
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.
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:
@ -117,17 +126,17 @@ end
```
The reason for this touches on the topic of computer architecture. Reading from RAM is rather
costly, so CPUs have multiple levels of caching. If the data a process requests
costly, so CPUs have multiple levels of caching. If the data that a process requests
is in the cache, it can very quickly retrieve it. If the data is not in the cache,
then a cache miss occurs and it will fetch the data it needs from RAM. Any data
surrounding the requested data is also fetched and then replaces the data in the cache. This is
because it's quite likely that the process will ask for data near that location again. This means
a good rule of optimisation is to iterate in a way that you read data one after
another, and avoid memory thrashing.
another, and avoid *cache thrashing*.
## Writing Nodes
First you need to set the new content ID in the data array:
First, you need to set the new content ID in the data array:
```lua
for z = min.z, max.z do
@ -155,23 +164,22 @@ 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 `minetest.fix_light`.
time using `core.fix_light`.
## Example
```lua
-- Get content IDs during load time, and store into a local
local c_dirt = minetest.get_content_id("default:dirt")
local c_grass = minetest.get_content_id("default:dirt_with_grass")
local function grass_to_dirt(pos1, pos2)
local c_dirt = core.get_content_id("default:dirt")
local c_grass = core.get_content_id("default:dirt_with_grass")
-- Read data into LVM
local vm = minetest.get_voxel_manip()
local vm = core.get_voxel_manip()
local emin, emax = vm:read_from_map(pos1, pos2)
local a = VoxelArea:new{
MinEdge = emin,
MaxEdge = emax
}
}
local data = vm:get_data()
-- Modify data
@ -194,10 +202,10 @@ end
## Your Turn
* Create `replace_in_area(from, to, pos1, pos2)` which replaces all instances of
* Create `replace_in_area(from, to, pos1, pos2)`, which replaces all instances of
`from` with `to` in the area given, where `from` and `to` are node names.
* Make a function which rotates all chest nodes by 90&deg;.
* Make a function which uses an LVM to cause mossy cobble to spread to nearby
stone and cobble nodes.
Does your implementation cause mossy cobble to spread more than a distance of one each
Does your implementation cause mossy cobble to spread more than a distance of one node each
time? If so, how could you stop this?

View File

@ -9,22 +9,27 @@ redirect_from:
- /en/basics/folders.html
---
## Introduction
## Introduction <!-- omit in toc -->
Understanding the basic structure of a mod's folder is an essential skill when
creating mods.
creating mods. In this chapter, you'll learn about how modding in Minetest works
and create your first mod.
* [What are Games and Mods?](#what-are-games-and-mods)
* [Mod Directory](#mod-directory)
* [Dependencies](#dependencies)
* [Mod Packs](#mod-packs)
* [Example](#example)
- [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)
- [Dependencies](#dependencies)
- [Mod Packs](#mod-packs)
## What are Games and Mods?
The power of Minetest is the ability to easily develop games without the need
to create your own voxel graphics, voxel algorithms or fancy networking code.
to create your own voxel graphics, voxel algorithms, or fancy networking code.
In Minetest, a game is a collection of modules which work together to provide the
content and behaviour of a game.
@ -42,85 +47,136 @@ Both the mods contained in a game and third-party mods use the same API.
This book will cover the main parts of the Minetest API,
and is applicable for both game developers and modders.
## Mod Directory
## Where are mods stored?
<a name="mod-locations"></a>
Each mod has its own directory where its Lua code, textures, models, and
sounds are placed. These directories need to be placed in a mod location such as
minetest/mods.
sounds are placed. Minetest checks in several different locations for
mods. These locations are commonly called *mod load paths*.
![Find the mod's directory]({{ page.root }}/static/folder_modfolder.jpg)
For a given world/save game, three mod locations are checked.
They are, in order:
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
1. Game mods. These are the mods that form the game that the world is running.
Eg: `minetest/games/minetest_game/mods/`, `/usr/share/minetest/games/minetest/`
2. Global mods, the location to which mods are nearly always installed to.
If in doubt, place them here.
Eg: `minetest/mods/`
3. World mods, the location to store mods which are specific to a
particular world.
Eg: `minetest/worlds/world/worldmods/`
`minetest` 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.
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.
## Creating your first mod
### Mod directory
Go to the global mods directory (About > Open user data directory > mods) and
create a new folder called "mymod". `mymod` is the mod name.
Each mod should have a unique *mod name*, a technical identifier (id) used to
refer to the mod. Mod names can include letters, numbers, and underscores. A
good name should describe what the mod does, and the directory that contains
the components of a mod must have the same name as the mod name. To find out if
a mod name is available, try searching for it on
[content.minetest.net](https://content.minetest.net).
mymod
├── init.lua (required) - Runs when the game loads.
├── mod.conf (recommended) - Contains description and dependencies.
├── textures (optional)
│   └── ... any textures or images
├── sounds (optional)
│   └── ... any sounds
└── ... any other files or directories
├── textures
│   └── mymod_node.png files
├── init.lua
└── mod.conf
Only the init.lua file is required in a mod for it to run on game load;
Mods only require an init.lua file;
however, mod.conf is recommended and other components may be needed
depending on the mod's functionality.
### mod.conf
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 should be listed in mod.conf.
Dependencies are specified in a comma-separated list in mod.conf.
### mod.conf
This file is used for mod metadata including the mod's name, description, and other
information. For example:
name = mymod
description = Adds foo, bar, and bo.
depends = modone, modtwo
optional_depends = modthree
### depends.txt
For compatibility with 0.4.x versions of Minetest, instead of only specifying
dependencies in mod.conf, you need to provide a depends.txt file in which
you list all dependencies:
modone
modtwo
modthree?
Each mod name is on its own line, and mod names with a question mark
following them are optional dependencies.
If an optional dependency is installed, it is loaded before the mod;
however, if the dependency is not installed, the mod still loads.
This is in contrast to normal dependencies which will cause the current
mod not to work if the dependency is not installed.
## Mod Packs
Mods can be grouped into mod packs which allow multiple mods to be packaged
Mods can be grouped into mod packs, which allow multiple mods to be packaged
and moved together. They are useful if you want to supply multiple mods to
a player, but don't want to make them download each one individually.
modpack1
├── modpack.lua (required) - signals that this is a mod pack
├── modpack.conf (required) - signals that this is a mod pack
├── mod1
│   └── ... mod files
└── mymod (optional)
@ -129,43 +185,3 @@ a player, but don't want to make them download each one individually.
Please note that a modpack is not a *game*.
Games have their own organisational structure which will be explained in the
Games chapter.
## Example
Here is an example which puts all of this together:
### Mod Folder
mymod
├── textures
│   └── mymod_node.png files
├── depends.txt
├── init.lua
└── mod.conf
### depends.txt
default
### init.lua
```lua
print("This file will be run at load time!")
minetest.register_node("mymod:node", {
description = "This is a node",
tiles = {"mymod_node.png"},
groups = {cracky = 1}
})
```
### mod.conf
name = mymod
descriptions = Adds a node
depends = default
This mod has the name "mymod". It has three text files: init.lua, mod.conf,
and depends.txt.\\
The script prints a message and then registers a node
which will be explained in the next chapter.\\
There's a single dependency, the
[default mod](https://content.minetest.net/metapackages/default/), which is
usually found in Minetest Game.\\
There is also a texture in textures/ for the node.

View File

@ -7,28 +7,49 @@ description: A basic introduction to Lua, including a guide on global/local scop
redirect_from: /en/chapters/lua.html
---
## Introduction
## Introduction <!-- omit in toc -->
In this chapter we will talk about scripting in Lua, the tools required,
and go over some techniques which you will probably find useful.
In this chapter, you'll learn about scripting in Lua, the tools required
to help with this, and some techniques that you may find useful.
- [Programming](#programming)
- [Coding in Lua](#coding-in-lua)
- [Code Editors](#code-editors)
- [Local and Global Scope](#local-and-global-scope)
- [Locals should be used as much as possible](#locals-should-be-used-as-much-as-possible)
- [Including other Lua Scripts](#including-other-lua-scripts)
## Programming
Programming is the action of taking a problem, such as sorting a list
of items, and turning it into steps that a computer can understand.
Teaching you the logical process of programming is beyond the scope of this book;
however, the following websites are quite useful in developing this:
* [Codecademy](http://www.codecademy.com/) is one of the best resources for
learning to write code. It provides an interactive tutorial experience.
* [Scratch](https://scratch.mit.edu) is a good resource for starting from
absolute basics, and learning the problem-solving techniques required to program.
It's great for children and teenagers.
* [Programming with Mosh](https://www.youtube.com/user/programmingwithmosh) is
a good YouTube series to learn programming.
### Coding in Lua
It's also beyond the scope of this book to teach Lua coding.
The [Programming in Lua (PiL)](https://www.lua.org/pil/contents.html) book is an
excellent introduction to Lua programming.
* [Code Editors](#code-editors)
* [Integrated Programming Environments](#integrated-programming-environments)
* [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)
* [Including other Lua Scripts](#including-other-lua-scripts)
## Code Editors
A code editor with code highlighting is sufficient for writing scripts in Lua.
Code highlighting gives different colours to different words and characters
depending on what they mean. This allows you to spot mistakes.
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:
```lua
function ctf.post(team,msg)
@ -46,166 +67,41 @@ function ctf.post(team,msg)
end
```
For example, keywords in the above snippet are highlighted such as if, then, end, and return.
table.insert is a function which comes with Lua by default.
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.
Here is a list of common editors well suited for Lua.
Other editors are available, of course.
Commonly used editors which are well-suited for Lua include:
* 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/)
* [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
### Integrated Programming Environments
Other suitable editors are also available.
IDEs allow you to debug code like a native application.
These are harder to set up than just a text editor.
One such IDE is Eclipse with the Koneki Lua plugin:
* Install Eclipse + Koneki.
* Create a new Lua project from existing source (specify Minetest's base directory).
* Follow instructions from Koneki wiki on how to do "Attach to remote Application" debugging (just a few steps).
* It is suggested to add those lines from wiki at beginning of builtin.lua.
* Start the debugger (set "Break on first line" in debugger configuration to see if it is working).
* Start Minetest.
* Enter the game to startup Lua.
## 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 to.
A local variable is only accessible from where it is defined. Here are some examples:
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:
```lua
-- Accessible from within this script file
@ -222,44 +118,12 @@ 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
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:
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:
Assignment to undeclared global 'foo' inside function at init.lua:2
@ -278,12 +142,11 @@ one()
two()
```
Remember that nil means **not initialised**.
The variable hasn't been assigned a value yet,
doesn't exist, or has been uninitialised (ie: set to nil).
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).
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.
Functions are variables of a special type, but should also be made local,
because other mods could have functions with the same names.
```lua
local function foo(bar)
@ -291,7 +154,9 @@ local function foo(bar)
end
```
API tables should be used to allow other mods to call the functions, like so:
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.
```lua
mymod = {}
@ -304,26 +169,29 @@ 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(minetest.get_modpath("modname") .. "/script.lua")
dofile(core.get_modpath("modname") .. "/script.lua")
```
"local" variables declared outside of any functions in a script file will be local to that script.
A script can return a value, which is useful for sharing private locals:
```lua
-- script.lua
return "Hello world!"
local module = {}
module.message = "Hello World!"
return module
-- init.lua
local ret = dofile(minetest.get_modpath("modname") .. "/script.lua")
print(ret) -- Hello world!
local ret = dofile(core.get_modpath("modname") .. "/script.lua")
print(ret.message) -- Hello world!
```
Later chapters will discuss how to split up the code of a mod in a lot of detail.
However, the simplistic approach for now is to have different files for different
types of things - nodes.lua, crafts.lua, craftitems.lua, etc.
[Later chapters](../quality/clean_arch.html) will discuss how best to split up
code for a mod.

View File

@ -2,36 +2,36 @@
title: Creating Games
layout: default
root: ../..
idx: 6.1
idx: 7.1
---
## Introduction
## Introduction <!-- omit in toc -->
The power of Minetest is the ability to easily create games without the need
to write your own voxel graphics and algorithms or fancy networking.
The power of Minetest is the ability to easily develop games without the need
to create your own voxel graphics, voxel algorithms, or fancy networking code.
* [What is a Game?](#what-is-a-game)
* [Game Directory](#game-directory)
* [Inter-game Compatibility](#inter-game-compatibility)
* [API Compatibility](#api-compatibility)
* [Groups and Aliases](#groups-and-aliases)
* [Your Turn](#your-turn)
- [What is a Game?](#what-is-a-game)
- [Game Directory](#game-directory)
- [Inter-game Compatibility](#inter-game-compatibility)
- [API Compatibility](#api-compatibility)
- [Groups and Aliases](#groups-and-aliases)
- [Your Turn](#your-turn)
## What is a Game?
Games are a collection of mods which work together to make a cohesive game.
A good game has a consistent underlying theme and a direction, for example
maybe it's a classic crafter miner with hard survival elements, or maybe
it's a space simulation game with a steam punk automation ascetic.
A good game has a consistent underlying theme and a direction, for example,
it could be a classic crafter miner with hard survival elements, or
it could be a space simulation game with a steampunk automation aesthetic.
Game design is a complex topic, and is actually a whole field of expertise.
Game design is a complex topic and is actually a whole field of expertise.
It's beyond the scope of the book to more than briefly touch on it.
## Game Directory
The structure and location of a game will seem rather familiar after working
with mods.
Games are found in a game location such as `minetest/games/<foo_game>`.
Games are found in a game location, such as `minetest/games/foo_game`.
foo_game
├── game.conf
@ -57,9 +57,9 @@ 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,
then it should have a compatible API.
For example, if a game includes a mod called `doors` then it should have the
That is, if a mod uses the same name as another mod, even if third-party,
it should have a compatible API.
For example, if a game includes a mod called `doors`, then it should have the
same API as `doors` in Minetest Game.
API compatibility for a mod is the sum of the following things:
@ -68,11 +68,11 @@ API compatibility for a mod is the sum of the following things:
For example, `mobs.register_mob`.
* Registered Nodes/Items - The presence of items.
It's probably fine to have partial breakages as long as 90% of dependency
usecases still works. For example, not having a random utility function that was
only actually used internally is ok, but not having `mobs.register_mobs` is bad.
Small breakages aren't that bad, such as not having a random utility
function that was only actually used internally, but bigger breakages
related to core features are very bad.
It's difficult to maintain API compatibility with a disgusting God mega-mod like
It's difficult to maintain API compatibility with a disgusting mega God-mod like
*default* in Minetest Game, in which case the game shouldn't include a mod named
default.
@ -83,11 +83,11 @@ To check whether a mod name has been taken, search for it on
### Groups and Aliases
Groups and Aliases are both massive tools in keeping compatibility between games,
Groups and Aliases are both useful tools in keeping compatibility between games,
as it allows item names to be different between different games. Common nodes
like stone and wood should have groups to indicate the material. It's also a
good idea to provide aliases from default nodes to any direct replacements.
## Your Turn
* Make a game - It can be simple, if you like.
* Create a simple game where the player gains points from digging special blocks.

View File

@ -1,18 +1,19 @@
---
title: Front Cover
layout: default
description: An easy guide to learn how to create mods for Minetest
homepage: true
no_header: true
root: ..
idx: 0.1
---
<div id="header">
<h1>Minetest Modding Book</h1>
<header>
<h1>Luanti Modding Book (formerly Minetest)</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>
</div>
</header>
## Introduction
@ -22,14 +23,14 @@ Each chapter focuses on a particular part of the API, and will soon get you maki
your own mods.
As well as [reading this book online](https://rubenwardy.com/minetest_modding_book),
you can also [download it in HTML form](https://github.com/rubenwardy/minetest_modding_book/releases).
you can also [download it in HTML form](https://gitlab.com/rubenwardy/minetest_modding_book/-/releases).
### Feedback and Contributions
Noticed a mistake, or want to give feedback? Make sure to tell me about it.
* Create a [GitHub Issue](https://github.com/rubenwardy/minetest_modding_book/issues).
* Create a [GitLab Issue](https://gitlab.com/rubenwardy/minetest_modding_book/-/issues).
* Post in the [Forum Topic](https://forum.minetest.net/viewtopic.php?f=14&t=10729).
* [Contact me](https://rubenwardy.com/contact/).
* Fancy contributing?
[Read the README](https://github.com/rubenwardy/minetest_modding_book/blob/master/README.md).
[Read the README](https://gitlab.com/rubenwardy/minetest_modding_book/-/blob/master/README.md).

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

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

View File

@ -3,11 +3,11 @@ title: Creating Textures
layout: default
root: ../..
idx: 2.2
description: An introduction to making textures in your editor of choice, an a guide on GIMP.
description: An introduction to making textures in your editor of choice, and a guide on GIMP.
redirect_from: /en/chapters/creating_textures.html
---
## Introduction
## Introduction <!-- omit in toc -->
Being able to create and optimise textures is a very useful skill when
developing for Minetest.
@ -15,15 +15,21 @@ There are many techniques relevant to working on pixel art textures,
and understanding these techniques will greatly improve
the quality of the textures you create.
* [Learning to Draw](#learning-to-draw)
* [Techniques](#techniques)
* [Editors](#editors)
Detailed approaches to creating good pixel art are outside the scope
of this book, and instead only the most relevant basic techniques
will be covered.
There are many [good online tutorials](http://www.photonstorm.com/art/tutorials-art/16x16-pixel-art-tutorial)
available, which cover pixel art in much more detail.
## Learning to Draw
Teaching how to draw good pixel art is out of scope of this book.
* [16×16 Pixel Art Tutorial on photonstorm.com](http://www.photonstorm.com/art/tutorials-art/16x16-pixel-art-tutorial)
- [Techniques](#techniques)
- [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
@ -32,7 +38,7 @@ Teaching how to draw good pixel art is out of scope of this book.
The pencil tool is available in most editors. When set to its lowest size,
it allows you to edit one pixel at a time without changing any other parts
of the image. By manipulating the pixels one at a time, you create clear
and sharp textures without unnecessary blurring. It also gives you a high
and sharp textures without unintended blurring. It also gives you a high
level of precision and control.
### Tiling
@ -50,12 +56,17 @@ to look at.
### Transparency
Transparency is important when designing textures for special nodes,
such as glass or water, which players should be able to see through,
and for craft item textures.
It's important to remember that not all editors support transparency,
so make sure you choose an editor which is suitable for the textures
you wish to create.
Transparency is important when creating textures for nearly all craftitems
and some nodes, such as glass.
Not all editors support transparency, so make sure you choose an
editor which is suitable for the textures you wish to create.
### 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
@ -67,16 +78,21 @@ This usually won't matter when making textures for the sides of nodes,
but if you need transparency in your textures you should choose a
different editor.
### Aseprite / LibreSprite
[Aseprite](https://www.aseprite.org/) is a proprietary pixel art editor.
It contains a lot of useful features by default such as color palettes and
animation tools.
[LibreSprite](https://libresprite.github.io/) is an open-source fork of Aseprite
from before it went proprietary.
### GIMP
GIMP is commonly used in the Minetest community. It has quite a high
learning curve because many of its features are not immediately
obvious.
When using GIMP, the pencil tool can be selected from the Toolbox:
<figure>
<img src="{{ page.root }}//static/pixel_art_gimp_pencil.png" alt="Pencil in GIMP">
</figure>
It's also advisable to select the Hard edge checkbox for the eraser tool.
When using GIMP, make sure to use the Pencil tool with the Pixel brush and a
size of 1. It's also advisable to select the "Hard edge" checkbox for the Eraser
tool.

View File

@ -11,43 +11,46 @@ redirect_from:
- /en/inventories/itemstacks.html
---
## Introduction
## Introduction <!-- omit in toc -->
In this chapter you will learn how to use and manipulate inventories, whether
that is a player inventory, a node inventory, or a detached inventory.
In this chapter, you will learn how to use and manipulate inventories, whether
that be a player inventory, a node inventory, or a detached inventory.
* [What are Item Stacks and Inventories?](#what-are-item-stacks-and-inventories)
* [ItemStacks](#itemstacks)
* [Inventory Locations](#inventory-locations)
* [Lists](#lists)
* [Size and Width](#size-and-width)
* [Checking Contents](#checking-contents)
* [Modifying Inventories and ItemStacks](#modifying-inventories-and-itemstacks)
* [Adding to a List](#adding-to-a-list)
* [Taking Items](#taking-items)
* [Manipulating Stacks](#manipulating-stacks)
* [Wear](#wear)
* [Lua Tables](#lua-tables)
- [What are ItemStacks and Inventories?](#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)
- [Modifying Inventories and ItemStacks](#modifying-inventories-and-itemstacks)
- [Adding to a List](#adding-to-a-list)
- [Taking Items](#taking-items)
- [Manipulating Stacks](#manipulating-stacks)
- [Wear](#wear)
- [Lua Tables](#lua-tables)
## What are ItemStacks and Inventories?
An ItemStack is the data behind a single cell in an inventory.
An *inventory* is a collection of *inventory lists*, each of which
is a 2D grid of ItemStacks.
Inventory lists are simply called *lists* in the context
of inventories.
The point of an inventory is to allow multiple grids when Players
and Nodes only have at most one inventory in them.
An *inventory* is a collection of *inventory lists*, each of which is a 2D grid
of ItemStacks. Inventory lists are referred to as *lists* in the context of
inventories.
Players and nodes only have a single inventory; lists enable you to have
multiple grids within that inventory. By default, the player has the "main" list
for the bulk of its inventory and a few lists for the crafting system.
## ItemStacks
ItemStacks have three components to them.
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
any mitigation such as registering an alias.
item name. Unknown items are common when users uninstall mods, or when mods
remove items without precautions, such as registering aliases.
```lua
print(stack:get_name())
@ -58,19 +61,14 @@ if not stack:is_known() then
end
```
The count will always be 0 or greater.
Through normal gameplay, the count should be no more than the maximum stack size
of the item - `stack_max`.
However, admin commands and buggy mods may result in stacks exceeding the maximum
size.
The count will always be 0 or greater. Through normal gameplay, the count should
be no more than the maximum stack size of the item - `stack_max`. However, admin
commands and buggy mods may result in stacks exceeding the maximum size.
```lua
print(stack:get_stack_max())
```
An ItemStack can be empty, in which case the count will be 0.
```lua
@ -78,7 +76,7 @@ print(stack:get_count())
stack:set_count(10)
```
ItemStacks can be constructed in multiple ways using the ItemStack function.
ItemStacks can be constructed in multiple ways using the ItemStack function:
```lua
ItemStack() -- name="", count=0
@ -87,24 +85,30 @@ ItemStack("default:stone 30")
ItemStack({ name = "default:wood", count = 10 })
```
Item metadata is an unlimited key-value store for data about the item.
Key-value means that you use a name (called the key) to access the data (called the value).
Some keys have special meaning, such as `description` which is used to have a per-stack
item description.
This will be covered in more detail in the Metadata and Storage chapter.
Item metadata is an unlimited key-value store for data about the item. Key-value
means that you use a name (called the key) to access the data (called the
value). Some keys have special meaning, such as `description` which is used to
have a per-stack item description. This will be covered in more detail in the
[Storage and Metadata](../map/storage.html) chapter.
## Inventory Locations
An Inventory Location is where and how the inventory is stored.
There are three types of inventory location: player, node, and detached.
An inventory is directly tied to one and only one location - updating the inventory
will cause it to update immediately.
An Inventory Location is where and how the inventory is stored. There are three
types of inventory location: player, node, and detached. An inventory is
directly tied to one and only one location - updating the inventory will cause
it to update immediately.
Node inventories are related to the position of a specific node, such as a chest.
The node must be loaded, because it is stored in [node metadata](node_metadata.html).
### Node Inventories
Node inventories are related to the position of a specific node, such as a
chest. The node must be loaded because it is stored in
[node metadata](../map/storage.html#metadata).
```lua
local inv = minetest.get_inventory({ type="node", pos={x=1, y=2, z=3} })
on_punch = function(pos, node)
local inv = core.get_inventory({ type="node", pos=pos })
-- now use the inventory
end,
```
The above obtains an *inventory reference*, commonly referred to as *InvRef*.
@ -112,48 +116,90 @@ Inventory references are used to manipulate an inventory.
*Reference* means that the data isn't actually stored inside that object,
but the object instead directly updates the data in-place.
Player inventories can be obtained similarly or using a player reference.
The player must be online to access their inventory.
```lua
local inv = minetest.get_inventory({ type="player", name="player1" })
-- or
local inv = player:get_inventory()
```
A detached inventory is one which is independent of players or nodes.
Detached inventories also don't save over a restart.
Detached inventories need to be created before then can be retrieved -
this will be covered latter.
```lua
local inv = minetest.get_inventory({
type="detached", name="inventory_name" })
```
The location of an inventory reference can be found like so:
```lua
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" })
-- 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.
```lua
local inv = core.get_inventory({
type="detached", name="inventory_name" })
```
Unlike the other types of inventory, you must first create a detached inventory
before accessing it:
```lua
core.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:
```lua
-- Input only detached inventory
core.create_detached_inventory("inventory_name", {
allow_move = function(inv, from_list, from_index, to_list, to_index, count, player)
return count -- allow moving
end,
allow_put = function(inv, listname, index, stack, player)
return stack:get_count() -- allow putting
end,
allow_take = function(inv, listname, index, stack, player)
return 0 -- don't allow taking
end,
on_put = function(inv, listname, index, stack, player)
core.chat_send_all(player:get_player_name() ..
" gave " .. stack:to_string() ..
" to the donation chest from " .. core.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.
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 a number of common lists
which all games have, such as the *main* inventory and *craft* slots.
Inventory Lists are a concept used to allow multiple grids to be stored inside a
single location. This is especially useful for the player as there are several
common lists that all games have, such as the *main* inventory and *craft*
slots.
### Size and Width
Lists have a size, which is the number of cells in the grid, and a width,
which is used when the engine.
The List width is not used when drawing the inventory in a window, as the
code behind the window determines the width to use.
Lists have a size, which is the total number of cells in the grid, and a width,
which is only used within the engine.
The width of the list is not used when drawing the inventory in a window,
because the code behind the window determines the width to use.
```lua
if inv:set_size("main", 32) then
inv:set_width("main", 8)
print("size: " .. inv.get_size("main"))
print("size: " .. inv:get_size("main"))
print("width: " .. inv:get_width("main"))
else
print("Error! Invalid itemname or size to set_size()")
@ -174,13 +220,20 @@ if inv:is_empty("main") then
end
```
`contains_item` can be used to see if a list contains a specific item.
`contains_item` can be used to see if a list contains a specific item:
```lua
if inv:contains_item("main", "default:stone") then
print("I've found some stone!")
end
```
## Modifying Inventories and ItemStacks
### Adding to a List
To add items to a list named `"main"`, whilst respecting any maximum stack sizes:
`add_item` adds items to a list (in this case `"main"`). In the example below,
the maximum stack size is also respected:
```lua
local stack = ItemStack("default:stone 99")
@ -222,7 +275,7 @@ print("Could not add" .. leftover:get_count() .. " of the items.")
-- ^ will be 51
print("Have " .. stack:get_count() .. " items")
-- ^ will be 80
-- ^ will be 80
-- min(50+100, stack_max) - 19 = 80
-- where stack_max = 99
```
@ -239,9 +292,9 @@ inv:set_stack(listname, 0, stack)
## Wear
Tools can have wear; wear shows a progress bar and makes the tool break when completely worn.
Wear is a number out of 65535, the higher it is, the more worn the tool is.
Wear is a number out of 65535; the higher it is, the more worn the tool is.
Wear can be manipulated using `add_wear()`, `get_wear()` and `set_wear(wear)`.
Wear can be manipulated using `add_wear()`, `get_wear()`, and `set_wear(wear)`.
```lua
local stack = ItemStack("default:pick_mese")
@ -252,7 +305,7 @@ local max_uses = 10
stack:add_wear(65535 / (max_uses - 1))
```
When digging a node, the amount of wear a tool gets may depends on the node
When digging a node, the amount of wear a tool gets may depend on the node
being dug. So max_uses varies depending on what is being dug.
## Lua Tables

View File

@ -7,40 +7,41 @@ description: Guide to all drawtypes, including node boxes/nodeboxes and mesh nod
redirect_from: /en/chapters/node_drawtypes.html
---
## Introduction
## Introduction <!-- omit in toc -->
In this chapter we explain all the different types of node drawtypes there are.
The method by which a node is drawn is called a *drawtype*. There are many
available drawtypes. The behaviour of a drawtype can be controlled
by providing properties in the node type definition. These properties
are fixed for all instances of this node. It is possible to control some properties
per-node using something called `param2`.
First of all, what is a drawtype?
A drawtype defines how the node is to be drawn.
A torch looks different to water, water looks different to stone.
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
register a node - it registers a new *type* of node.
The string you use to determine the drawtype in the node definition is the same as
the title of the sections, except in lower case.
The node params are used to control how a node is individually rendered.
`param1` is used to store the lighting of a node, and the meaning of
`param2` depends on the `paramtype2` property of the node type definition.
* [Normal](#normal)
* [Airlike](#airlike)
* [Liquid](#liquid)
* [FlowingLiquid](#flowingliquid)
* [Glasslike](#glasslike)
* [Glasslike_Framed](#glasslike_framed)
* [Glasslike_Framed_Optional](#glasslike_framed_optional)
* [Allfaces](#allfaces)
* [Allfaces_Optional](#allfaces_optional)
* [Torchlike](#torchlike)
* [Nodebox](#nodebox)
* [Mesh](#mesh)
* [Signlike](#signlike)
* [Plantlike](#plantlike)
* [Firelike](#firelike)
- [Cubic Nodes: Normal and Allfaces](#cubic-nodes-normal-and-allfaces)
- [Glasslike Nodes](#glasslike-nodes)
- [Glasslike_Framed](#glasslike_framed)
- [Airlike Nodes](#airlike-nodes)
- [Lighting and Sunlight Propagation](#lighting-and-sunlight-propagation)
- [Liquid Nodes](#liquid-nodes)
- [Node Boxes](#node-boxes)
- [Wallmounted Node Boxes](#wallmounted-node-boxes)
- [Mesh Nodes](#mesh-nodes)
- [Signlike Nodes](#signlike-nodes)
- [Plantlike Nodes](#plantlike-nodes)
- [Firelike Nodes](#firelike-nodes)
- [More Drawtypes](#more-drawtypes)
This article is not complete yet. The following drawtypes are missing:
* Fencelike
* Plantlike rooted
* Raillike
## Normal
## Cubic Nodes: Normal and Allfaces
<figure class="right_image">
<img src="{{ page.root }}//static/drawtype_normal.png" alt="Normal Drawtype">
@ -49,47 +50,104 @@ This article is not complete yet. The following drawtypes are missing:
</figcaption>
</figure>
This is, well, the normal drawtypes.
Nodes that use this will be cubes with textures for each side, simple-as.\\
Here is the example from the [Nodes, Items and Crafting](nodes_items_crafting.html#registering-a-basic-node) chapter.
Notice how you don't need to declare the drawtype.
The normal drawtype is typically used to render a cubic node.
If the side of a normal node is against a solid side, then that side won't be rendered,
resulting in a large performance gain.
In contrast, the allfaces drawtype will still render the inner side when up against
a solid node. This is good for nodes with partially transparent sides, such as
leaf nodes. You can use the allfaces_optional drawtype to allow users to opt-out
of the slower drawing, in which case it'll act like a normal node.
```lua
minetest.register_node("mymod:diamond", {
core.register_node("mymod:diamond", {
description = "Alien Diamond",
tiles = {
"mymod_diamond_up.png",
"mymod_diamond_down.png",
"mymod_diamond_right.png",
"mymod_diamond_left.png",
"mymod_diamond_back.png",
"mymod_diamond_front.png"
},
is_ground_content = true,
tiles = {"mymod_diamond.png"},
groups = {cracky = 3},
drop = "mymod:diamond_fragments"
})
core.register_node("default:leaves", {
description = "Leaves",
drawtype = "allfaces_optional",
tiles = {"default_leaves.png"}
})
```
Note: the normal drawtype is the default drawtype, so you don't need to explicitly
specify it.
## Glasslike Nodes
The difference between glasslike and normal nodes is that placing a glasslike node
next to a normal node won't cause the side of the normal node to be hidden.
This is useful because glasslike nodes tend to be transparent, and so using a normal
drawtype would result in the ability to see through the world.
<figure>
<img src="{{ page.root }}//static/drawtype_glasslike_edges.png" alt="Glasslike's Edges">
<figcaption>
Glasslike's Edges
</figcaption>
</figure>
```lua
core.register_node("default:obsidian_glass", {
description = "Obsidian Glass",
drawtype = "glasslike",
tiles = {"default_obsidian_glass.png"},
paramtype = "light",
is_ground_content = false,
sunlight_propagates = true,
sounds = default.node_sound_glass_defaults(),
groups = {cracky=3,oddly_breakable_by_hand=3},
})
```
### Glasslike_Framed
This makes the node's edge go around the whole thing with a 3D effect, rather
than individual nodes, like the following:
<figure>
<img src="{{ page.root }}//static/drawtype_glasslike_framed.png" alt="Glasslike_framed's Edges">
<figcaption>
Glasslike_Framed's Edges
</figcaption>
</figure>
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", {
description = "Glass",
drawtype = "glasslike_framed",
tiles = {"default_glass.png", "default_glass_detail.png"},
inventory_image = core.inventorycube("default_glass.png"),
paramtype = "light",
sunlight_propagates = true, -- Sunlight can shine through block
groups = {cracky = 3, oddly_breakable_by_hand = 3},
sounds = default.node_sound_glass_defaults()
})
```
## Airlike
## Airlike Nodes
These nodes are see through and thus have no textures.
These nodes are not rendered and thus have no textures.
```lua
minetest.register_node("myair:air", {
core.register_node("myair:air", {
description = "MyAir (you hacker you!)",
drawtype = "airlike",
paramtype = "light",
-- ^ Allows light to propagate through the node with the
-- light value falling by 1 per node.
sunlight_propagates = true,
sunlight_propagates = true, -- Sunlight shines through
walkable = false, -- Would make the player collide with the air node
pointable = false, -- You can't select the node
diggable = false, -- You can't dig the node
buildable_to = true, -- Nodes can be replace this node.
buildable_to = true, -- Nodes can replace this node.
-- (you can place a node and remove the air node
-- that used to be there)
@ -99,7 +157,27 @@ minetest.register_node("myair:air", {
})
```
## Liquid
## Lighting and Sunlight Propagation
The lighting of a node is stored in param1. In order to work out how to shade
a node's side, the light value of the neighbouring node is used.
Because of this, solid nodes don't have light values because they block light.
By default, a node type won't allow light to be stored in any node instances.
It's usually desirable for some nodes such as glass and air to be able to
let light through. To do this, there are two properties which need to be defined:
```lua
paramtype = "light",
sunlight_propagates = true,
```
The first line means that param1 does, in fact, store the light level.
The second line means that sunlight should go through this node without decreasing in value.
## Liquid Nodes
<figure class="right_image">
<img src="{{ page.root }}//static/drawtype_liquid.png" alt="Liquid Drawtype">
@ -108,18 +186,17 @@ minetest.register_node("myair:air", {
</figcaption>
</figure>
These nodes are complete liquid nodes, the liquid flows outwards from position
using the flowing liquid drawtype.
For each liquid node you should also have a flowing liquid node.
Each type of liquid requires two node definitions - one for the liquid source, and
another for flowing liquid.
```lua
-- Some properties have been removed as they are beyond
-- the scope of this chapter.
minetest.register_node("default:water_source", {
core.register_node("default:water_source", {
drawtype = "liquid",
paramtype = "light",
inventory_image = minetest.inventorycube("default_water.png"),
inventory_image = core.inventorycube("default_water.png"),
-- ^ this is required to stop the inventory image from being animated
tiles = {
@ -173,142 +250,15 @@ minetest.register_node("default:water_source", {
-- ^ how far
post_effect_color = {a=64, r=100, g=100, b=200},
-- ^ color of screen when the player is submerged
-- ^ colour of screen when the player is submerged
})
```
### FlowingLiquid
Flowing nodes have a similar definition, but with a different name and animation.
See default:water_flowing in the default mod in minetest_game for a full example.
See default:water_flowing in the default mod in minetest_game, it is mostly
the same as the above example.
## Glasslike
<figure class="right_image">
<img src="{{ page.root }}//static/drawtype_glasslike.png" alt="Glasslike Drawtype">
<figcaption>
Glasslike Drawtype
</figcaption>
</figure>
When you place multiple glasslike nodes together, you'll notice that the internal
edges are hidden, like this:
<figure>
<img src="{{ page.root }}//static/drawtype_glasslike_edges.png" alt="Glasslike's Edges">
<figcaption>
Glasslike's Edges
</figcaption>
</figure>
```lua
minetest.register_node("default:obsidian_glass", {
description = "Obsidian Glass",
drawtype = "glasslike",
tiles = {"default_obsidian_glass.png"},
paramtype = "light",
is_ground_content = false,
sunlight_propagates = true,
sounds = default.node_sound_glass_defaults(),
groups = {cracky=3,oddly_breakable_by_hand=3},
})
```
## Glasslike_Framed
This makes the node's edge go around the whole thing with a 3D effect, rather
than individual nodes, like the following:
<figure>
<img src="{{ page.root }}//static/drawtype_glasslike_framed.png" alt="Glasslike_framed's Edges">
<figcaption>
Glasslike_Framed's Edges
</figcaption>
</figure>
```lua
minetest.register_node("default:glass", {
description = "Glass",
drawtype = "glasslike_framed",
tiles = {"default_glass.png", "default_glass_detail.png"},
inventory_image = minetest.inventorycube("default_glass.png"),
paramtype = "light",
sunlight_propagates = true, -- Sunlight can shine through block
is_ground_content = false, -- Stops caves from being generated over this node.
groups = {cracky = 3, oddly_breakable_by_hand = 3},
sounds = default.node_sound_glass_defaults()
})
```
### Glasslike_Framed_Optional
"optional" drawtypes need less rendering time if deactivated on the client's side.
## Allfaces
<figure class="right_image">
<img src="{{ page.root }}//static/drawtype_allfaces.png" alt="Allfaces drawtype">
<figcaption>
Allfaces drawtype
</figcaption>
</figure>
Allfaces are nodes which show all of their faces, even if they're against
another node. This is mainly used by leaves as you don't want a gaping space when
looking through the transparent holes, but instead a nice leaves effect.
```lua
minetest.register_node("default:leaves", {
description = "Leaves",
drawtype = "allfaces_optional",
tiles = {"default_leaves.png"}
})
```
### Allfaces_Optional
Allows clients to disable it using `new_style_leaves = 0`, requiring less rendering time.
TorchLike
---------
TorchLike nodes are 2D nodes which allow you to have different textures
depending on whether they are placed against a wall, on the floor, or on the ceiling.
TorchLike nodes are not restricted to torches, you could use them for switches or other
items which need to have different textures depending on where they are placed.
```lua
minetest.register_node("foobar:torch", {
description = "Foobar Torch",
drawtype = "torchlike",
tiles = {
{"foobar_torch_floor.png"},
{"foobar_torch_ceiling.png"},
{"foobar_torch_wall.png"}
},
inventory_image = "foobar_torch_floor.png",
wield_image = "default_torch_floor.png",
light_source = LIGHT_MAX-1,
-- Determines how the torch is selected, ie: the wire box around it.
-- each value is { x1, y1, z1, x2, y2, z2 }
-- (x1, y1, y1) is the bottom front left corner
-- (x2, y2, y2) is the opposite - top back right.
-- Similar to the nodebox format.
selection_box = {
type = "wallmounted",
wall_top = {-0.1, 0.5-0.6, -0.1, 0.1, 0.5, 0.1},
wall_bottom = {-0.1, -0.5, -0.1, 0.1, -0.5+0.6, 0.1},
wall_side = {-0.5, -0.3, -0.1, -0.5+0.3, 0.3, 0.1},
}
})
```
## Nodebox
## Node Boxes
<figure class="right_image">
<img src="{{ page.root }}//static/drawtype_nodebox.gif" alt="Nodebox drawtype">
@ -317,11 +267,11 @@ minetest.register_node("foobar:torch", {
</figcaption>
</figure>
Nodeboxes allow you to create a node which is not cubic, but is instead made out
Node boxes allow you to create a node which is not cubic, but is instead made out
of as many cuboids as you like.
```lua
minetest.register_node("stairs:stair_stone", {
core.register_node("stairs:stair_stone", {
drawtype = "nodebox",
paramtype = "light",
node_box = {
@ -334,7 +284,7 @@ minetest.register_node("stairs:stair_stone", {
})
```
The most important part is the nodebox table:
The most important part is the node box table:
```lua
{-0.5, -0.5, -0.5, 0.5, 0, 0.5},
@ -346,18 +296,16 @@ The first three numbers are the co-ordinates, from -0.5 to 0.5 inclusive, of
the bottom front left most corner, the last three numbers are the opposite corner.
They are in the form X, Y, Z, where Y is up.
`paramtype = "light"` allows light to propagate from or through the node
with light value.
You can use the [NodeBoxEditor](https://forum.minetest.net/viewtopic.php?f=14&t=2840) to
create node boxes by dragging the edges, it is more visual than doing it by hand.
### Wallmounted Nodebox
### Wallmounted Node Boxes
Sometimes you want different nodeboxes for when it is placed on the floor, wall, or ceiling like with torches.
```lua
minetest.register_node("default:sign_wall", {
core.register_node("default:sign_wall", {
drawtype = "nodebox",
node_box = {
type = "wallmounted",
@ -380,7 +328,7 @@ minetest.register_node("default:sign_wall", {
})
```
## Mesh
## Mesh Nodes
Whilst node boxes are generally easier to make, they are limited in that
they can only consist of cuboids. Node boxes are also unoptimised;
@ -393,7 +341,7 @@ invisible but still rendered.
You can register a mesh node as so:
```lua
minetest.register_node("mymod:meshy", {
core.register_node("mymod:meshy", {
drawtype = "mesh",
-- Holds the texture for each "material"
@ -402,17 +350,18 @@ minetest.register_node("mymod:meshy", {
},
-- Path to the mesh
mesh = "mymod_meshy.b3d",
mesh = "mymod_meshy.b3d",
})
```
Make sure that the mesh is available in a `models` directory.
Most of the time the mesh should be in your mod's folder, however it's okay to
Most of the time the mesh should be in your mod's folder, however, it's okay to
share a mesh provided by another mod you depend on. For example, a mod that
adds more types of furniture may want to share the model provided by a basic
furniture mod.
## Signlike
## Signlike Nodes
Signlike nodes are flat nodes with can be mounted on the sides of other nodes.
@ -421,7 +370,7 @@ instead use the `nodebox` drawtype to provide a 3D effect. The `signlike` drawty
is, however, commonly used by ladders.
```lua
minetest.register_node("default:ladder_wood", {
core.register_node("default:ladder_wood", {
drawtype = "signlike",
tiles = {"default_ladder_wood.png"},
@ -435,7 +384,8 @@ minetest.register_node("default:ladder_wood", {
})
```
## Plantlike
## Plantlike Nodes
<figure class="right_image">
<img src="{{ page.root }}//static/drawtype_plantlike.png" alt="Plantlike Drawtype">
@ -447,7 +397,7 @@ minetest.register_node("default:ladder_wood", {
Plantlike nodes draw their tiles in an X like pattern.
```lua
minetest.register_node("default:papyrus", {
core.register_node("default:papyrus", {
drawtype = "plantlike",
-- Only one texture used
@ -460,7 +410,7 @@ minetest.register_node("default:papyrus", {
})
```
## Firelike
## Firelike Nodes
Firelike is similar to plantlike, except that it is designed to "cling" to walls
and ceilings.
@ -473,10 +423,24 @@ and ceilings.
</figure>
```lua
minetest.register_node("mymod:clingere", {
core.register_node("mymod:clingere", {
drawtype = "firelike",
-- Only one texture used
tiles = { "mymod:clinger" },
})
```
## More Drawtypes
This is not a comprehensive list, there are more types including:
* Fencelike
* Plantlike rooted - for underwater plants
* Raillike - for cart tracks
* Torchlike - for 2D wall/floor/ceiling nodes.
The torches in Minetest Game actually use two different node definitions of
mesh nodes (default:torch and default:torch_wall).
As always, read the [Lua API documentation](https://minetest.gitlab.io/minetest/nodes/#node-drawtypes)
for the complete list.

View File

@ -7,81 +7,83 @@ description: Learn how to register node, items, and craft recipes using register
redirect_from: /en/chapters/nodes_items_crafting.html
---
## Introduction
## Introduction <!-- omit in toc -->
Registering new nodes and craftitems, and creating craft recipes, are
basic requirements for many mods.
* [What are Nodes and Items?](#what-are-nodes-and-items)
* [Registering Items](#registering-items)
* [Item Names and Aliases](#item-names-and-aliases)
* [Textures](#textures)
* [Registering a Basic Node](#registering-a-basic-node)
* [Actions and Callbacks](#actions-and-callbacks)
* [on_use](#on_use)
* [Crafting](#crafting)
* [Fuel](#fuel)
* [Groups](#groups)
* [Tools, Capabilities, and Dig Types](#tools-capabilities-and-dig-types)
- [What are Nodes and Items?](#what-are-nodes-and-items)
- [Registering Items](#registering-items)
- [Item Names](#item-names)
- [Item Aliases](#item-aliases)
- [Textures](#textures)
- [Registering a basic node](#registering-a-basic-node)
- [Crafting](#crafting)
- [Shaped](#shaped)
- [Shapeless](#shapeless)
- [Cooking and Fuel](#cooking-and-fuel)
- [Groups](#groups)
- [Tools, Capabilities, and Dig Types](#tools-capabilities-and-dig-types)
## What are Nodes and Items?
Nodes, craftitems, and tools are all Items.
An item is something that could be found in an inventory -
even though it may not be possible through normal game play.
Nodes, craftitems, and tools are all Items. An item is something that could be
found in an inventory - even if it isn't possible through normal gameplay.
A node is an item which can be placed or be found in the world.
Every position in the world must be occupied with one and only one node -
seemingly blank positions are usually air nodes.
A node is an item that can be placed or be found in the world. Every position
in the world must be occupied with one and only one node - seemingly blank
positions are usually air nodes.
A craftitem can't be placed and is only found in inventories or as a dropped item
in the world.
A tool has the ability to wear and typically has non-default digging capabilities.
In the future, it's likely that craftitems and tools will merge into one type of
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
item, as the distinction between them is rather artificial.
## Registering Items
Item definitions consist of an *item name* and a *definition table*.
The definition table contains attributes which affect the behaviour of the item.
The definition table contains attributes that affect the behaviour of the item.
```lua
minetest.register_craftitem("modname:itemname", {
core.register_craftitem("modname:itemname", {
description = "My Special Item",
inventory_image = "modname_itemname.png"
})
```
### Item Names and Aliases
### Item Names
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.
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:
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:
* 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
minetest.register_alias("dirt", "default:dirt")
core.register_alias("dirt", "default:dirt")
```
Mods need to make sure to resolve aliases before dealing directly with item names,
@ -89,7 +91,7 @@ as the engine won't do this.
This is pretty simple though:
```lua
itemname = minetest.registered_aliases[itemname] or itemname
itemname = core.registered_aliases[itemname] or itemname
```
### Textures
@ -100,16 +102,18 @@ JPEG textures are supported, but they do not support transparency and are genera
bad quality at low resolutions.
It is often better to use the PNG format.
Textures in Minetest are usually 16 by 16 pixels.
They can be any resolution, but it is recommended that they are in the order of 2,
for example 16, 32, 64, or 128.
This is because other resolutions may not be supported correctly on older devices,
resulting in decreased performance.
Textures in Minetest are usually 16 by 16 pixels. They can be any resolution,
but it is recommended that they are in the order of 2, for example, 16, 32, 64,
or 128. This is because other resolutions may not be supported correctly on
older devices, especially phones, resulting in degraded performance.
## Registering a basic node
Registering nodes is similar to registering items, just with a different
function:
```lua
minetest.register_node("mymod:diamond", {
core.register_node("mymod:diamond", {
description = "Alien Diamond",
tiles = {"mymod_diamond.png"},
is_ground_content = true,
@ -117,6 +121,9 @@ minetest.register_node("mymod:diamond", {
})
```
Node definitions can contain any property in an item definition, and also
contain additional properties specific to nodes.
The `tiles` property is a table of texture names the node will use.
When there is only one texture, this texture is used on every side.
To give a different texture per-side, supply the names of 6 textures in this order:
@ -125,10 +132,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
3D computer graphics.
most 3D computer games.
```lua
minetest.register_node("mymod:diamond", {
core.register_node("mymod:diamond", {
description = "Alien Diamond",
tiles = {
"mymod_diamond_up.png", -- y+
@ -145,53 +152,10 @@ minetest.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`
@ -214,7 +178,7 @@ pattern to work. In the example below, the fragments need to be in a
chair-like pattern for the craft to work.
```lua
minetest.register_craft({
core.register_craft({
type = "shaped",
output = "mymod:diamond_chair 99",
recipe = {
@ -232,7 +196,7 @@ If this empty column shouldn't be required, then the empty strings can be left
out like so:
```lua
minetest.register_craft({
core.register_craft({
output = "mymod:diamond_chair 99",
recipe = {
{"mymod:diamond_fragments", "" },
@ -242,7 +206,7 @@ minetest.register_craft({
})
```
The type field isn't actually needed for shapeless crafts, as shapeless is the
The type field isn't actually needed for shaped crafts, as shaped is the
default craft type.
### Shapeless
@ -251,7 +215,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
minetest.register_craft({
core.register_craft({
type = "shapeless",
output = "mymod:diamond 3",
recipe = {
@ -268,7 +232,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
minetest.register_craft({
core.register_craft({
type = "cooking",
output = "mymod:diamond_fragments",
recipe = "default:coalblock",
@ -283,14 +247,14 @@ defines how long the item takes to cook.
If this is not set, it defaults to 3.
The recipe above works when the coal block is in the input slot,
with some form of a fuel below it.
with some form of fuel below it.
It creates diamond fragments after 10 seconds!
This type is an accompaniment to the cooking type, as it defines
what can be burned in furnaces and other cooking tools from mods.
```lua
minetest.register_craft({
core.register_craft({
type = "fuel",
recipe = "mymod:diamond",
burntime = 300,
@ -304,7 +268,7 @@ So, the diamond is good as fuel for 300 seconds!
## Groups
Items can be members of many groups and groups can have many members.
Groups are defined using the `groups` property in the definition table,
Groups are defined using the `groups` property in the definition table
and have an associated value.
```lua
@ -314,16 +278,17 @@ groups = {cracky = 3, wood = 1}
There are several reasons you use groups.
Firstly, groups are used to describe properties such as dig types and flammability.
Secondly, groups can be used in a craft recipe instead of an item name to allow
any item in group to be used.
any item in the group to be used.
```lua
minetest.register_craft({
core.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
@ -357,7 +322,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
minetest.register_tool("mymod:tool", {
core.register_tool("mymod:tool", {
description = "My Tool",
inventory_image = "mymod_tool.png",
tool_capabilities = {

View File

@ -1,77 +0,0 @@
---
title: Active Block Modifiers
layout: default
root: ../..
idx: 3.2
description: Learn how to make ABMs to change blocks.
redirect_from: /en/chapters/abms.html
---
## Introduction
An Active Block Modifier (ABM) is a method of periodically running a
function on nodes matching a criteria.
As the name implies, this only works on loaded MapBlocks.
ABMs are best suited for nodes which are frequently found in the world,
such as grass.
ABMs have a high CPU overhead, as Minetest needs to scan all Active Blocks
to find matching nodes, but they have a low memory and storage overhead.
For nodes which are uncommon or already use metadata, such as furnaces
and machines, node timers should be used instead.
Node timers do not require scanning a MapBlock, but they do require slightly
more memory and storage to keep track of running timers.
* [Registering an ABM](#registering-an-abm)
* [Your Turn](#your-turn)
## Registering an ABM
Alien grass, for the purposes of this chapter, is a type of grass which
has a chance to appear near water.
```lua
minetest.register_node("aliens:grass", {
description = "Alien Grass",
light_source = 3, -- The node radiates light. Min 0, max 14
tiles = {"aliens_grass.png"},
groups = {choppy=1},
on_use = minetest.item_eat(20)
})
minetest.register_abm({
nodenames = {"default:dirt_with_grass"},
neighbors = {"default:water_source", "default:water_flowing"},
interval = 10.0, -- Run every 10 seconds
chance = 50, -- Select every 1 in 50 nodes
action = function(pos, node, active_object_count,
active_object_count_wider)
local pos = {x = pos.x, y = pos.y + 1, z = pos.z}
minetest.set_node(pos, {name = "aliens:grass"})
end
})
```
This ABM runs every ten seconds.
There is a 1 in 50 chance of the ABM running on each node that has the
correct name and the correct neighbours.
If the ABM runs on a node, an alien grass node is placed above it.
Please be warned, this will delete any node previously located in that position.
To prevent this you should include a check using minetest.get_node to make sure there is space for the grass.
Specifying a neighbour is optional.
If you specify multiple neighbours, only one of them needs to be
present to meet the requirements.
Specifying chance is also optional.
If you don't specify the chance, the ABM will always run when the other conditions are met.
## Your Turn
* Midas touch: Make water turn to gold blocks with a 1 in 100 chance, every 5 seconds.
* Decay: Make wood turn into dirt when water is a neighbour.
* Burnin': Make every air node catch on fire. (Tip: "air" and "fire:basic_flame").
Warning: expect the game to crash.

View File

@ -7,35 +7,38 @@ description: Basic operations like set_node and get_node
redirect_from: /en/chapters/environment.html
---
## Introduction
## Introduction <!-- omit in toc -->
In this chapter you will learn how to perform basic actions on the map.
In this chapter, you will learn how to perform basic actions on the map, such as
adding, removing, and finding nodes.
* [Map Structure](#map-structure)
* [Reading](#reading)
* [Reading Nodes](#reading-nodes)
* [Finding Nodes](#finding-nodes)
* [Writing](#writing)
* [Writing Nodes](#writing-nodes)
* [Removing Nodes](#removing-nodes)
* [Loading Blocks](#loading-blocks)
* [Deleting Blocks](#deleting-blocks)
- [Map Structure](#map-structure)
- [Reading](#reading)
- [Reading Nodes](#reading-nodes)
- [Finding Nodes](#finding-nodes)
- [Writing](#writing)
- [Writing Nodes](#writing-nodes)
- [Removing Nodes](#removing-nodes)
- [Loading Blocks](#loading-blocks)
- [Deleting Blocks](#deleting-blocks)
## Map Structure
The Minetest map is split into MapBlocks, each MapBlocks being a cube of size 16.
As players travel around the map, MapBlocks are created, loaded, and unloaded.
Areas of the map which are not yet loaded are full of *ignore* nodes, an impassable
unselectable placeholder node. Empty space is full of *air* nodes, an invisible node
you can walk through.
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.
Loaded map blocks are often referred to as *active blocks*. Active Blocks can be
read from or written to by mods or players, 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
at its maximum value, 31000, by default. Existing MapBlocks can however be
to its maximum value, 31000, by default. Existing MapBlocks can, however, be
loaded from the world database outside of the generation limit.
## Reading
@ -45,21 +48,21 @@ loaded from the world database outside of the generation limit.
You can read from the map once you have a position:
```lua
local node = minetest.get_node({ x = 1, y = 3, z = 4 })
local node = core.get_node({ x = 1, y = 3, z = 4 })
print(dump(node)) --> { name=.., param1=.., param2=.. }
```
If the position is a decimal, it will be rounded to the containing node.
The function will always return a table containing the node information:
* `name` - The node name, will be ignored when the area is unloaded.
* `param1` - See the node definition, will commonly be light.
* `name` - The node name, which will be *ignore* when the area is unloaded.
* `param1` - See the node definition. This will commonly be light.
* `param2` - See the node definition.
It's worth noting that the function won't load the containing block if the block
is inactive, but will instead return a table with `name` being `ignore`.
You can use `minetest.get_node_or_nil` instead, which will return `nil` rather
You can use `core.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
@ -71,51 +74,56 @@ Minetest offers a number of helper functions to speed up common map actions.
The most commonly used of these are for finding nodes.
For example, say we wanted to make a certain type of plant that grows
better near mese. You would need to search for any nearby mese nodes,
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 = minetest.find_node_near(pos, 5, { "default:mese" })
local node_pos = core.find_node_near(pos, 5, { "default:mese" })
if node_pos then
minetest.chat_send_all("Node found at: " .. dump(node_pos))
core.chat_send_all("Node found at: " .. dump(node_pos))
grow_speed = 2
end
```
Let's say, for example, that the growth rate increases the more mese there is
nearby. You should then use a function which can find multiple nodes in area:
nearby. You should then use a function that can find multiple nodes in the area:
```lua
local pos1 = vector.subtract(pos, { x = 5, y = 5, z = 5 })
local pos2 = vector.add(pos, { x = 5, y = 5, z = 5 })
local pos_list =
minetest.find_nodes_in_area(pos1, pos2, { "default:mese" })
core.find_nodes_in_area(pos1, pos2, { "default:mese" })
local grow_speed = 1 + #pos_list
```
The above code doesn't quite do what we want, as it checks based on area, whereas
`find_node_near` checks based on range. In order to fix this we will,
unfortunately, need to manually check the range ourselves.
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:
```lua
local pos1 = vector.subtract(pos, { x = 5, y = 5, z = 5 })
local pos2 = vector.add(pos, { x = 5, y = 5, z = 5 })
local pos_list =
minetest.find_nodes_in_area(pos1, pos2, { "default:mese" })
core.find_nodes_in_area(pos1, pos2, { "default:mese" })
local grow_speed = 1
for i=1, #pos_list do
local delta = vector.subtract(pos_list[i], pos)
if delta.x*delta.x + delta.y*delta.y <= 5*5 then
if delta.x*delta.x + delta.y*delta.y + delta.z*delta.z <= 5*5 then
grow_speed = grow_speed + 1
end
end
```
Now your code will correctly increase `grow_speed` based on mese nodes in range.
Now the code will correctly increase `grow_speed` based on mese nodes in range.
Note how we compared the squared distance from the position, rather than square
rooting it to obtain the actual distance. This is because computers find square
roots computationally expensive, so you should avoid them as much as possible.
roots computationally expensive, so they should avoided 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
@ -126,26 +134,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, which means that set_node is fairly slow for large
numbers of nodes.
lighting to be recalculated and node callbacks to run, which means that set_node
is fairly slow for large numbers of nodes.
```lua
minetest.set_node({ x = 1, y = 3, z = 4 }, { name = "default:mese" })
core.set_node({ x = 1, y = 3, z = 4 }, { name = "default:mese" })
local node = minetest.get_node({ x = 1, y = 3, z = 4 })
local node = core.get_node({ x = 1, y = 3, z = 4 })
print(node.name) --> default:mese
```
set_node will remove any associated metadata or inventory from that position.
This isn't desirable in all circumstances, especially if you're using multiple
node definitions to represent one conceptual node. An example of this is the
furnace node - whilst you think conceptually of it as one node, it's actually
furnace node - whilst you conceptually think of it as one node, it's actually
two.
You can set a node without deleting metadata or the inventory like so:
```lua
minetest.swap_node({ x = 1, y = 3, z = 4 }, { name = "default:mese" })
core.swap_node({ x = 1, y = 3, z = 4 }, { name = "default:mese" })
```
### Removing Nodes
@ -155,15 +163,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
minetest.remove_node(pos)
minetest.set_node(pos, { name = "air" })
core.remove_node(pos)
core.set_node(pos, { name = "air" })
```
In fact, remove_node will call set_node with name being air.
In fact, remove_node is just a helper function that calls set_node with `"air"`.
## Loading Blocks
You can use `minetest.emerge_area` to load map blocks. Emerge area is asynchronous,
You can use `core.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.
@ -174,7 +182,7 @@ local pos1 = vector.subtract(pos, halfsize)
local pos2 = vector.add (pos, halfsize)
local context = {} -- persist data between callback calls
minetest.emerge_area(pos1, pos2, emerge_callback, context)
core.emerge_area(pos1, pos2, emerge_callback, context)
```
Minetest will call `emerge_callback` whenever it loads a block, with some
@ -194,17 +202,18 @@ local function emerge_callback(pos, action,
-- Send progress message
if context.total_blocks == context.loaded_blocks then
minetest.chat_send_all("Finished loading blocks!")
end
core.chat_send_all("Finished loading blocks!")
else
local perc = 100 * context.loaded_blocks / context.total_blocks
local msg = string.format("Loading blocks %d/%d (%.2f%%)",
context.loaded_blocks, context.total_blocks, perc)
minetest.chat_send_all(msg)
core.chat_send_all(msg)
end
end
```
This is not the only way of loading blocks; Using a LVM will also cause the
This is not the only way of loading blocks; using an
[Lua Voxel Manipulator (LVM)](../advmap/lvm.html) will also cause the
encompassed blocks to be loaded synchronously.
## Deleting Blocks
@ -217,7 +226,7 @@ local halfsize = { x = 10, y = 10, z = 10 }
local pos1 = vector.subtract(pos, halfsize)
local pos2 = vector.add (pos, halfsize)
minetest.delete_area(pos1, pos2)
core.delete_area(pos1, pos2)
```
This will delete all map blocks in that area, *inclusive*. This means that some

View File

@ -2,27 +2,36 @@
title: Objects, Players, and Entities
layout: default
root: ../..
idx: 3.35
idx: 3.4
description: Using an ObjectRef
degrad:
level: warning
title: Degrees and Radians
message: Attachment rotation is set in degrees, whereas object rotation is in radians.
Make sure to convert to the correct angle measurement.
---
## Introduction
## Introduction <!-- omit in toc -->
In this chapter, you will learn how to manipulate objects and how to define your
own.
* [What are Objects, Players, and Entities?](#objects_players_and_entities)
* [Position and Velocity](#position_and_velocity)
* [Object Properties](#object_properties)
* [Entities](#entities)
* [Attachments](#attachments)
* [Your Turn](#your_turn)
- [What are Objects, Players, and Entities?](#what-are-objects-players-and-entities)
- [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.
An Object is not an item, and they have their own separate registration system.
Objects aren't items, and they have their own separate registration system.
There are a few differences between Players and Entities.
The biggest one is that Players are player-controlled, whereas Entities are mod-controlled.
@ -31,15 +40,15 @@ and entities are server-side.
Another difference is that Players will cause map blocks to be loaded, whereas Entities
will just be saved and become inactive.
Entities are sometimes known as Lua Entities.
Don't be fooled though, all entities are Lua entities.
This distinction is muddied by the fact that Entities are controlled using a table
which is referred to as a Lua entity, as discussed later.
## Position and Velocity
`get_pos` and `set_pos` exist to allow you to get and set an entity's position.
```lua
local object = minetest.get_player_by_name("bob")
local object = core.get_player_by_name("bob")
local pos = object:get_pos()
object:set_pos({ x = pos.x, y = pos.y + 1, z = pos.z })
```
@ -65,6 +74,10 @@ you need to be aware of.
## Object Properties
Object properties are used to tell the client how to render and deal with an
object. It's not possible to define custom properties, because the properties are
for the engine to use, by definition.
Unlike nodes, objects have a dynamic rather than set appearance.
You can change how an object looks, among other things, at any time by updating
its properties.
@ -82,16 +95,17 @@ The updated properties will be sent to all players in range.
This is very useful to get a large amount of variety very cheaply, such as having
different skins per-player.
As shown in the next section, entities can have a default set of properties defined
in their definition.
As shown in the next section, entities can have initial properties
provided in their definition.
The default Player properties are defined in the engine, however, so you'll
need to use `set_properties()` in `on_joinplayer` to set the properties for newly
joined players.
## Entities
An Entity has a type table much like an item does.
This table can contain callback methods, default object properties, and custom elements.
An Entity has a definition table that resembles an item definition table.
This table can contain callback methods, initial object properties, and custom
members.
```lua
local MyEntity = {
@ -115,20 +129,34 @@ function MyEntity:set_message(msg)
end
```
When an entity is 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.
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.
-->
Both an ObjectRef and an entity table provide ways to get the counterpart:
```lua
local entity = object:get_luaentity()
local object = entity.object
print("entity is at " .. minetest.pos_to_string(object:get_pos()))
print("entity is at " .. core.pos_to_string(object:get_pos()))
```
There are a number of available callbacks for use with entities.
A complete list can be found in [lua_api.txt]({{ page.root }}/lua_api.html#registered-entities)
A complete list can be found in [lua_api.md](https://minetest.gitlab.io/minetest/minetest-namespace-reference/#registered-definition-tables).
```lua
function MyEntity:on_step(dtime)
@ -136,9 +164,9 @@ function MyEntity:on_step(dtime)
local pos_down = vector.subtract(pos, vector.new(0, 1, 0))
local delta
if minetest.get_node(pos_down).name == "air" then
if core.get_node(pos_down).name == "air" then
delta = vector.new(0, -1, 0)
elseif minetest.get_node(pos).name == "air" then
elseif core.get_node(pos).name == "air" then
delta = vector.new(0, 0, 1)
else
delta = vector.new(0, 1, 0)
@ -150,7 +178,7 @@ function MyEntity:on_step(dtime)
end
function MyEntity:on_punch(hitter)
minetest.chat_send_player(hitter:get_player_name(), self.message)
core.chat_send_player(hitter:get_player_name(), self.message)
end
```
@ -159,25 +187,25 @@ would be forgotten when the entity becomes inactive then active again.
This is because the message isn't saved.
Rather than saving everything in the entity table, Minetest gives you control over
how to save things.
Staticdata is a string which contains all of the custom information that
Staticdata is a string which contains all the custom information that
needs to stored.
```lua
function MyEntity:get_staticdata()
return minetest.write_json({
return core.write_json({
message = self.message,
})
end
function MyEntity:on_activate(staticdata, dtime_s)
if staticdata ~= "" and staticdata ~= nil then
local data = minetest.parse_json(staticdata) or {}
local data = core.parse_json(staticdata) or {}
self:set_message(data.message)
end
end
```
Minetest may call `get_staticdata()` as many times as it once and at any time.
Minetest may call `get_staticdata()` as many times as it wants and at any time.
This is because Minetest doesn't wait for a MapBlock to become inactive to save
it, as this would result in data loss. MapBlocks are saved roughly every 18
seconds, so you should notice a similar interval for `get_staticdata()` being called.
@ -189,14 +217,14 @@ This means that staticdata could be empty.
Finally, you need to register the type table using the aptly named `register_entity`.
```lua
minetest.register_entity("mymod:entity", MyEntity)
core.register_entity("mymod:entity", MyEntity)
```
The entity can be spawned by a mod like so:
```lua
local pos = { x = 1, y = 2, z = 3 }
local obj = minetest.add_entity(pos, "mymod:entity", nil)
local obj = core.add_entity(pos, "mymod:entity", nil)
```
The third parameter is the initial staticdata.
@ -211,6 +239,90 @@ 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 -
@ -221,20 +333,29 @@ 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.
So `0,5,0` would be half a node above the parent's origin.
So, `0,5,0` would be half a node above the parent's origin.
{% include notice.html notice=page.degrad %}
For 3D models with animations, the bone argument is used to attach the entity
to a bone.
3D animations are based on skeletons - a network of bones in the model where
each bone can be given a position and rotation to change the model, for example
each bone can be given a position and rotation to change the model, for example,
to move the arm.
Attaching to a bone is useful if you want to make a character hold something.
Attaching to a bone is useful if you want to make a character hold something:
```lua
obj:set_attach(player,
"Arm_Right", -- default bone
{x=0.2, y=6.5, z=3}, -- default position
{x=-100, y=225, z=90}) -- default rotation
```
## Your Turn
* Make a windmill by combining nodes and an entity.
* Make a mob of your choice (without using any other mods).
* Make a mob of your choice (using just the entity API, and without using any other mods).

View File

@ -9,22 +9,22 @@ redirect_from:
- /en/map/node_metadata.html
---
## Introduction
## Introduction <!-- omit in toc -->
In this chapter you will learn how you can store data.
In this chapter, you will learn how you can store data.
* [Metadata](#metadata)
* [What is Metadata?](#what-is-metadata)
* [Obtaining a Metadata Object](#obtaining-a-metadata-object)
* [Reading and Writing](#reading-and-writing)
* [Special Keys](#special-keys)
* [Private Metadata](#private-metadata)
* [Storing Tables](#storing-tables)
* [Lua Tables](#lua-tables)
* [Mod Storage](#mod-storage)
* [Databases](#databases)
* [Deciding Which to Use](#deciding-which-to-use)
* [Your Turn](#your-turn)
- [Metadata](#metadata)
- [What is Metadata?](#what-is-metadata)
- [Obtaining a Metadata Object](#obtaining-a-metadata-object)
- [Reading and Writing](#reading-and-writing)
- [Special Keys](#special-keys)
- [Storing Tables](#storing-tables)
- [Private Metadata](#private-metadata)
- [Lua Tables](#lua-tables)
- [Mod Storage](#mod-storage)
- [Databases](#databases)
- [Deciding Which to Use](#deciding-which-to-use)
- [Your Turn](#your-turn)
## Metadata
@ -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 = minetest.get_meta({ x = 1, y = 2, z = 3 })
local meta = core.get_meta({ x = 1, y = 2, z = 3 })
```
Player and ItemStack metadata are obtained using `get_meta()`:
@ -95,11 +95,11 @@ print(meta:get_string("count")) --> "3"
### Special Keys
`infotext` is used in Node Metadata to show a tooltip when hovering the crosshair over a node.
This is useful in order to show the owner of the node or the status.
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 colors by encoding them with `minetest.colorize()`.
You can use colours by encoding them with `core.colorize()`.
`owner` is a common key used to store the username of the player that owns the
item or node.
@ -109,22 +109,22 @@ item or node.
Tables must be converted to strings before they can be stored.
Minetest offers two formats for doing this: Lua and JSON.
The Lua method tends to be a lot faster and exactly matches the format Lua
uses for tables.
JSON is a more standard format and is better structured, and so is well suited
when you need to exchange information with another program.
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
with another program.
```lua
local data = { username = "player1", score = 1234 }
meta:set_string("foo", minetest.serialize(data))
meta:set_string("foo", core.serialize(data))
data = minetest.deserialize(minetest:get_string("foo"))
data = core.deserialize(meta:get_string("foo"))
```
### Private Metadata
Entries in Node Metadata can be marked as private, and not sent to the client.
Entries not marked as private will be sent to the client.
By default, all node metadata is sent to the client.
You can mark keys as private to prevent this.
```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 = minetest.get_mod_storage()
local storage = core.get_mod_storage()
```
You can now manipulate the storage just like metadata:
@ -164,17 +164,15 @@ If the mod is likely to be used on a server and will store lots of data,
it's a good idea to offer a database storage method.
You should make this optional by separating how the data is stored and where
it is used.
Using a database such as sqlite requires using the insecure environment, and
can be painful for the user to set up.
```lua
local backend
if use_database then
backend =
dofile(minetest.get_modpath("mymod") .. "/backend_sqlite.lua")
dofile(core.get_modpath("mymod") .. "/backend_sqlite.lua")
else
backend =
dofile(minetest.get_modpath("mymod") .. "/backend_storage.lua")
dofile(core.get_modpath("mymod") .. "/backend_storage.lua")
end
backend.get_foo("a")
@ -184,15 +182,15 @@ backend.set_foo("a", { score = 3 })
The backend_storage.lua file should include a mod storage implementation:
```lua
local storage = minetest.get_mod_storage()
local storage = core.get_mod_storage()
local backend = {}
function backend.set_foo(key, value)
storage:set_string(key, minetest.serialize(value))
storage:set_string(key, core.serialize(value))
end
function backend.get_foo(key, value)
return minetest.deserialize(storage:get_string(key))
function backend.get_foo(key)
return core.deserialize(storage:get_string(key))
end
return backend
@ -200,11 +198,17 @@ return backend
The backend_sqlite would do a similar thing, but use the Lua *lsqlite3* library
instead of mod storage.
You'll need to request an insecure environment and require the library:
Using a database such as SQLite requires using an insecure environment.
An insecure environment is a table that is only available to mods
explicitly whitelisted by the user, and it contains a less restricted
copy of the Lua API which could be abused if available to malicious mods.
Insecure environments will be covered in more detail in the
[Security](../quality/security.html) chapter.
```lua
local ie = minetest.request_insecure_environment()
assert(ie, "Please add mymod to secure.trusted_mods")
local ie = core.request_insecure_environment()
assert(ie, "Please add mymod to secure.trusted_mods in the settings")
local _sql = ie.require("lsqlite3")
-- Prevent other mods from using the global sqlite3 library
@ -214,8 +218,6 @@ end
```
Teaching about SQL or how to use the lsqlite3 library is out of scope for this book.
Insecure environments will be covered in more detail in the
[security](../quality/security.html) chapter.
## Deciding Which to Use
@ -224,18 +226,22 @@ how it is formatted, and how large it is.
As a guideline, small data is up to 10K, medium data is up to 10MB, and large
data is any size above that.
Node metadata is a good choice when the data is related to the node.
Node metadata is a good choice when you need to store node-related data.
Storing medium data is fairly efficient if you make it private.
Item metadata should not be used to store anything but small amounts of data as it is not
possible to avoid sending it to the client.
The data will also be copied every time the stack is moved, or is accessed from Lua.
The data will also be copied every time the stack is moved, or accessed from Lua.
Mod storage is good for medium data but writing large data may be inefficient.
It's better to use a database for large data, to avoid having to write all the
It's better to use a database for large data to avoid having to write all the
data out on every save.
Databases are only viable for servers due to the
need to whitelist the mod to access an insecure environment.
They're well suited for large data sets.
## Your Turn
* Make a node which disappears after it has been punched five times.
(Use `on_punch` in the node definition and `minetest.set_node`.)
(Use `on_punch` in the node definition and `core.set_node`.)

110
_en/map/timers.md Normal file
View File

@ -0,0 +1,110 @@
---
title: Node Timers and ABMs
layout: default
root: ../..
idx: 3.2
description: Learn how to make ABMs to change blocks.
redirect_from:
- /en/chapters/abms.html
- /en/map/abms.html
---
## Introduction <!-- omit in toc -->
Periodically running a function on certain nodes is a common task.
Minetest provides two methods of doing this: Active Block Modifiers (ABMs) and node timers.
ABMs scan all loaded MapBlocks looking for nodes that match a criteria.
They are best suited for nodes which are frequently found in the world,
such as grass.
They have a high CPU overhead, but a low memory and storage overhead.
For nodes that are uncommon or already use metadata, such as furnaces
and machines, node timers should be used instead.
Node timers work by keeping track of pending timers in each MapBlock, and then
running them when they expire.
This means that timers don't need to search all loaded nodes to find matches,
but instead require slightly more memory and storage for the tracking
of pending timers.
- [Node Timers](#node-timers)
- [Active Block Modifiers](#active-block-modifiers)
- [Your Turn](#your-turn)
## Node Timers
Node timers are directly tied to a single node.
You can manage node timers by obtaining a NodeTimerRef object.
```lua
local timer = core.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:
```lua
core.register_node("autodoors:door_open", {
on_timer = function(pos)
core.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.
## Active Block Modifiers
Alien grass, for the purposes of this chapter, is a type of grass which
has a chance to appear near water.
```lua
core.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)
})
core.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
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"})
end
})
```
This ABM runs every ten seconds, and for each matching node, there is
a 1 in 50 chance of it running.
If the ABM runs on a node, an alien grass node is placed above it.
Please be warned, this will delete any node previously located in that position.
To prevent this you should include a check using core.get_node to make sure there is space for the grass.
Specifying a neighbour is optional.
If you specify multiple neighbours, only one of them needs to be
present to meet the requirements.
Specifying chance is also optional.
If you don't specify the chance, the ABM will always run when the other conditions are met.
## Your Turn
* Midas touch: Make water turn to gold blocks with a 1 in 100 chance, every 5 seconds.
* Decay: Make wood turn into dirt when water is a neighbour.
* Burnin': Make every air node catch on fire. (Tip: "air" and "fire:basic_flame").
Warning: expect the game to crash.

View File

@ -8,39 +8,46 @@ redirect_from: /en/chapters/chat.html
cmd_online:
level: warning
title: Offline players can run commands
message: <p>A player name is passed instead of a player object, because mods
can run commands on behalf of offline players. For example, the IRC
bridge allows players to run commands without joining the game.</p>
message: |
A player name is passed instead of a player object because mods
can run commands on behalf of offline players. For example, the IRC
bridge allows players to run commands without joining the game.
<p>So make sure that you don't assume that the player is online.
You can check by seeing if minetest.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 `core.get_player_by_name` returns a player.
cb_cmdsprivs:
level: warning
title: Privileges and Chat Commands
message: The shout privilege isn't needed for a player to trigger this callback.
This is because chat commands are implemented in Lua, and are just
chat messages that begin with a /.
message: |
The shout privilege isn't needed for a player to trigger this callback.
This is because chat commands are implemented in Lua, and are just
chat messages that begin with a /.
---
## Introduction
## Introduction <!-- omit in toc -->
Mods can interact with player chat, including
sending messages, intercepting messages and registering chat commands.
sending messages, intercepting messages, and registering chat commands.
* [Sending Messages to All Players](#sending-messages-to-all-players)
* [Sending Messages to Specific Players](#sending-messages-to-specific-players)
* [Chat Commands](#chat-commands)
* [Complex Subcommands](#complex-subcommands)
* [Intercepting Messages](#intercepting-messages)
- [Sending Messages](#sending-messages)
- [To All Players](#to-all-players)
- [To Specific Players](#to-specific-players)
- [Chat Commands](#chat-commands)
- [Accepting Multiple Arguments](#accepting-multiple-arguments)
- [Using string.split](#using-stringsplit)
- [Using Lua patterns](#using-lua-patterns)
- [Intercepting Messages](#intercepting-messages)
## Sending Messages to All Players
## Sending Messages
To send a message to every player in the game, call the chat_send_all function.
### To All Players
To send a message to every player in the game, call the `chat_send_all` function.
```lua
minetest.chat_send_all("This is a chat message to all players")
core.chat_send_all("This is a chat message to all players")
```
Here is an example of how this appears in-game:
@ -51,112 +58,123 @@ Here is an example of how this appears in-game:
The message appears on a separate line to distinguish it from in-game player chat.
## Sending Messages to Specific Players
### To Specific Players
To send a message to a specific player, call the chat_send_player function:
To send a message to a specific player, call the `chat_send_player` function:
```lua
minetest.chat_send_player("player1", "This is a chat message for player1")
core.chat_send_player("player1", "This is a chat message for player1")
```
This message displays in the same manner as messages to all players, but is
only visible to the named player, in this case player1.
only visible to the named player, in this case, player1.
## Chat Commands
To register a chat command, for example /foo, use register_chatcommand:
To register a chat command, for example `/foo`, use `register_chatcommand`:
```lua
minetest.register_chatcommand("foo", {
core.register_chatcommand("foo", {
privs = {
interact = true
interact = true,
},
func = function(name, param)
return true, "You said " .. param .. "!"
end
end,
})
```
Calling /foo bar will display `You said bar!` in the chat console.
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.
You can restrict which players are able to run commands:
`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`.
```lua
privs = {
interact = true
},
```
This means only players with the `interact` [privilege](privileges.html) can run the
command. Other players will see an error message informing them of which
privilege they're missing. If the player has the necessary privileges, the command
will run and the message will be sent:
```lua
return true, "You said " .. param .. "!"
```
This returns two values, a Boolean which shows the command succeeded
and the chat message to send to the player.
Chat commands can return up to two values,
the first being a Boolean indicating success, and the second being a
message to send to the user.
{% include notice.html notice=page.cmd_online %}
## Complex Subcommands
### Accepting Multiple Arguments
It is often required to make complex chat commands, such as:
<a name="complex-subcommands"></a>
* `/msg <to> <message>`
* `/team join <teamname>`
* `/team leave <teamname>`
* `/team list`
`param` gives you all the arguments to a chat command in a single string. It's
common for chat commands to need to extract multiple arguments. There are two
ways of doing this, either using Minetest's string split or Lua patterns.
This is usually done using [Lua patterns](https://www.lua.org/pil/20.2.html).
Patterns are a way of extracting stuff from text using rules.
#### Using string.split
A string can be split up into words using `string.split(" ")`:
```lua
local to, msg = string.match(param, "^([%a%d_-]+) (*+)$")
local parts = param:split(" ")
local cmd = parts[1]
if cmd == "join" then
local team_name = parts[2]
team.join(name, team_name)
return true, "Joined team!"
elseif cmd == "max_users" then
local team_name = parts[2]
local max_users = tonumber(parts[3])
if team_name and max_users then
return true, "Set max users of team " .. team_name .. " to " .. max_users
else
return false, "Usage: /team max_users <team_name> <number>"
end
else
return false, "Command needed"
end
```
The above implements `/msg <to> <message>`. Let's go through left to right:
#### Using Lua patterns
[Lua patterns](https://www.lua.org/pil/20.2.html) are a way of extracting stuff
from text using rules. They're best suited for when there are arguments that can
contain spaces or more control is needed on how parameters are captured.
```lua
local to, msg = param:match("^([%a%d_-]+) (.+)$")
```
The above code implements `/msg <to> <message>`. Let's go through left to right:
* `^` means match the start of the string.
* `()` is a matching group - anything that matches stuff in here will be
returned from string.match.
* `[]` means accept characters in this list.
* `%a` means accept any letter and `%d` means any digit.
* `[%d%a_-]` means accept any letter or digit or `_` or `-`.
* `+` means match the last thing one or more times.
* `*` means match any character in this context.
* `%a` means accept any letter and `%d` means accept any digit.
* `[%a%d_-]` means accept any letter or digit or `_` or `-`.
* `+` means match the thing before one or more times.
* `.` means match any character in this context.
* `$` means match the end of the string.
Put simply, this matches the name (a word with only letters/numbers/-/_),
then a space, then the message (one of more of any character). The name and
message are returned, as they're surrounded in parentheses.
Put simply, the pattern matches the name (a word with only letters/numbers/-/_),
then a space, then the message (one or more of any character). The name and
message are returned, because they're surrounded by parentheses.
That's how most mods implement complex chat commands. A better guide to Lua
Patterns would probably be the
[lua-users.org tutorial](http://lua-users.org/wiki/PatternsTutorial)
or the [PIL documentation](https://www.lua.org/pil/20.2.html).
There is also a library written by the author of this book which can be used
to make complex chat commands without Patterns called
[ChatCmdBuilder](chat_complex.html).
## Intercepting Messages
To intercept a message, use register_on_chat_message:
```lua
minetest.register_on_chat_message(function(name, message)
core.register_on_chat_message(function(name, message)
print(name .. " said " .. message)
return false
end)
```
By returning false, you allow the chat message to be sent by the default
handler. You can actually remove the line `return false`, and it would still
work the same.
handler. You can actually remove the line `return false` and it would still
work the same, because `nil` is returned implicitly and is treated like false.
{% include notice.html notice=page.cb_cmdsprivs %}
@ -164,10 +182,10 @@ You should make sure you take into account that it may be a chat command,
or the user may not have `shout`.
```lua
minetest.register_on_chat_message(function(name, message)
core.register_on_chat_message(function(name, message)
if message:sub(1, 1) == "/" then
print(name .. " ran chat command")
elseif minetest.check_player_privs(name, { shout = true }) then
elseif core.check_player_privs(name, { shout = true }) then
print(name .. " said " .. message)
else
print(name .. " tried to say " .. message ..

View File

@ -1,183 +0,0 @@
---
title: Chat Command Builder
layout: default
root: ../..
idx: 4.3
description: Use ChatCmdBuilder to make a complex chat command
redirect_from: /en/chapters/chat_complex.html
---
## Introduction
This chapter will show you how to make complex chat commands with ChatCmdBuilder,
such as `/msg <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 commnds](chat.html#complex-subcommands) chapter.
* Why ChatCmdBuilder?
* Routes.
* Subcommand functions.
* Installing ChatCmdBuilder.
* 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
sub commands. Each `cmd:sub(route, func)` is a sub command.
A sub command is a particular response to an input param. When a player runs
the chat command, the first sub command 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 there:
* `name` - type of `word` as no type is specified. Accepts any string without spaces.
* `hp` - type of `int`
## Subcommand functions
The first argument is the caller's name. The variables are then passed to the
function in order.
```lua
cmd:sub(":target :hp:int", function(name, target, hp)
-- subcommand function
end)
```
## Installing ChatCmdBuilder
The source code can be found and downloaded on
[Github](https://github.com/rubenwardy/ChatCmdBuilder/).
There are two ways to install:
1. Install ChatCmdBuilder as a mod and depend on it.
2. Include the init.lua file in ChatCmdBuilder as chatcmdbuilder.lua in your mod,
and dofile it.
## Admin complex command
Here is an example that creates a chat command that allows us to do this:
* `/admin kill <username>` - kill user
* `/admin move <username> to <pos>` - teleport user
* `/admin log <username>` - show report log
* `/admin log <username> <message>` - log to report log
```lua
local admin_log
local function load()
admin_log = {}
end
local function save()
-- todo
end
load()
ChatCmdBuilder.new("admin", function(cmd)
cmd:sub("kill :name", function(name, target)
local player = minetest.get_player_by_name(target)
if player then
player:set_hp(0)
return true, "Killed " .. target
else
return false, "Unable to find " .. target
end
end)
cmd:sub("move :name to :pos:pos", function(name, target, pos)
local player = minetest.get_player_by_name(target)
if player then
player:setpos(pos)
return true, "Moved " .. target .. " to " ..
minetest.pos_to_string(pos)
else
return false, "Unable to find " .. target
end
end)
cmd:sub("log :username", function(name, target)
local log = admin_log[target]
if log then
return true, table.concat(log, "\n")
else
return false, "No entries for " .. target
end
end)
cmd:sub("log :username :message", function(name, target, message)
local log = admin_log[target] or {}
table.insert(log, message)
admin_log[target] = log
save()
return true, "Logged"
end)
end, {
description = "Admin tools",
privs = {
kick = true,
ban = true
}
})
```

View File

@ -1,8 +1,9 @@
---
title: Formspecs
title: GUIs (Formspecs)
layout: default
root: ../..
idx: 4.5
description: Learn how to display GUIs using formspecs
redirect_from: /en/chapters/formspecs.html
submit_vuln:
level: warning
@ -13,7 +14,7 @@ submit_vuln:
and make sure that they should be allowed to perform the action.
---
## Introduction
## Introduction <!-- omit in toc -->
<figure class="right_image">
<img src="{{ page.root }}//static/formspec_example.png" alt="Furnace Inventory">
@ -24,269 +25,326 @@ submit_vuln:
In this chapter we will learn how to create a formspec and display it to the user.
A formspec is the specification code for a form.
In Minetest, forms are windows such as the player inventory, which can contain labels,
buttons and fields to allow you to enter information.
* [Formspec Syntax](#formspec-syntax)
* [Displaying Forms](#displaying-forms)
* [Callbacks](#callbacks)
* [Contexts](#contexts)
* [Node Meta Formspecs](#node-meta-formspecs)
In Minetest, forms are windows such as the player inventory and can contain a
variety of elements, such as labels, buttons and fields.
Note that if you do not need to get user input, for example when you only need
to provide information to the player, you should consider using Heads Up Display
(HUD) elements instead of forms, because unexpected windows tend to disrupt gameplay.
to provide information to the player, you should consider using
[Heads Up Display (HUD)](hud.html) elements instead of forms, because
unexpected windows tend to disrupt gameplay.
## Formspec Syntax
- [Real or Legacy Coordinates](#real-or-legacy-coordinates)
- [Anatomy of a Formspec](#anatomy-of-a-formspec)
- [Elements](#elements)
- [Header](#header)
- [Guessing Game](#guessing-game)
- [Padding and Spacing](#padding-and-spacing)
- [Receiving Formspec Submissions](#receiving-formspec-submissions)
- [Contexts](#contexts)
- [Formspec Sources](#formspec-sources)
- [Node Meta Formspecs](#node-meta-formspecs)
- [Player Inventory Formspecs](#player-inventory-formspecs)
- [Your Turn](#your-turn)
Formspecs have an unusual syntax.
They consist of a series of tags which are in the following form:
element_type[param1;param2;...]
## Real or Legacy Coordinates
Firstly the element type is declared, and then the attributes are given
in square brackets.
In older versions of Minetest, formspecs were inconsistent. The way that different
elements were positioned varied in unexpected ways; it was hard to predict the
placement of elements and align them. Minetest 5.1.0 contains a feature
called real coordinates which aims to rectify this by introducing a consistent
coordinate system. The use of real coordinates is highly recommended, and so
this chapter will use them exclusively.
Elements are items such as text boxes or buttons, or can be metadata such
as size or background.
Using a formspec_version of 2 or above will enable real coordinates.
Here are two elements, of types foo and bar.
## Anatomy of a Formspec
### Elements
Formspec is a domain-specific language with an unusual format.
It consists of a number of elements with the following form:
type[param1;param2]
The element type is declared and then any parameters are given
in square brackets. Multiple elements can be joined together, or placed
on multiple lines, like so:
foo[param1]bar[param1]
bo[param1]
### Size[w, h]
Nearly all forms have a size tag. This declares the size of the form window. Note that
**forms don't use pixels as co-ordinates; they use a grid based on inventories**.
A size of (1, 1) means the form is big enough to host a 1x1 inventory.
This means the size of the form is independent of screen resolution and it should work
just as well on large screens as small screens.
You can use decimals in sizes and co-ordinates.
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/)
for a list of all possible elements.
size[5,2]
Co-ordinates and sizes only use one attribute.
The x and y values are separated by a comma, as you can see above.
### Header
### Field[x, y; w, h; name; label; default]
The header of a formspec contains information which must appear first. This
includes the size of the formspec, the position, the anchor, and whether the
game-wide theme should be applied.
This is a textbox element. Most other elements have a similar style of attributes.
The name attribute is used in callbacks to get the submitted information.
The x and y attributes determine the position of the element, and
the w and h attributes provide the size.
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.
field[1,1;3,1;firstname;Firstname;]
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:
It is perfectly valid to not define an attribute.
formspec_version[4]
size[2,2]
### Other Elements
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.
You should refer to [lua_api.txt](https://github.com/minetest/minetest/blob/master/doc/lua_api.txt#L1019)
for a list of all possible elements. Search for "Formspec" to locate the correct part of the document.
At the time of writing, formspec information begins on line 1765.
The position and anchor elements are used to place the formspec on the screen.
The position sets where on the screen the formspec will be, and defaults to
the center (`0.5,0.5`). The anchor sets where on the formspec the position is,
allowing you to line the formspec up with the edge of the screen. The formspec
can be placed to the left of the screen like so:
## Displaying Formspecs
formspec_version[4]
size[2,2]
position[0,0.5]
anchor[0,0.5]
Here is a generalized way to show a formspec:
This sets the anchor to the left middle edge of the formspec box, and then the
position of that anchor to the left of the screen.
minetest.show_formspec(playername, formname, formspec)
Formnames should be itemnames; however, this is not enforced.
There is no need to override a formspec, because formspecs are not registered like
nodes and items are. The formspec code is sent to the player's client for them
to see, along with the formname.
Formnames are used in callbacks to identify which form has been submitted,
and to see if the callback is relevant.
### Example
This example shows a formspec to a player when they use the /formspec command.
## Guessing Game
<figure class="right_image">
<img src="{{ page.root }}//static/formspec_name.png" alt="Name Formspec">
<img src="{{ page.root }}/static/formspec_guessing.png" alt="Guessing Formspec">
<figcaption>
The formspec generated by<br />
the example's code
The guessing game formspec.
</figcaption>
</figure>
The best way to learn is to make something, so let's make a guessing game.
The principle is simple: the mod decides on a number, then the player makes
guesses on the number. The mod then says if the guess is higher or lower then
the actual number.
First, let's make a function to create the formspec code. It's good practice to
do this, as it makes it easier to reuse elsewhere.
<div style="clear: both;"></div>
```lua
-- Show form when the /formspec command is used.
minetest.register_chatcommand("formspec", {
func = function(name, param)
minetest.show_formspec(name, "mymod:form",
"size[4,3]" ..
"label[0,0;Hello, " .. name .. "]" ..
"field[1,1.5;3,1;name;Name;]" ..
"button_exit[1,2;2,1;exit;Save]")
end
guessing = {}
function guessing.get_formspec(name)
-- TODO: display whether the last guess was higher or lower
local text = "I'm thinking of a number... Make a guess!"
local formspec = {
"formspec_version[4]",
"size[6,3.476]",
"label[0.375,0.5;", core.formspec_escape(text), "]",
"field[0.375,1.25;5.25,0.8;number;Number;]",
"button[1.5,2.3;3,0.8;guess;Guess]"
}
-- table.concat is faster than string concatenation - `..`
return table.concat(formspec, "")
end
```
In the above code, we place a field, a label, and a button. A field allows text
entry, and a button is used to submit the form. You'll notice that the elements
are positioned carefully in order to add padding and spacing, this will be explained
later.
Next, we want to allow the player to show the formspec. The main way to do this
is using `show_formspec`:
```lua
function guessing.show_to(name)
core.show_formspec(name, "guessing:game", guessing.get_formspec(name))
end
core.register_chatcommand("game", {
func = function(name)
guessing.show_to(name)
end,
})
```
Note: the .. is used to join two strings together. The following two lines are equivalent:
The `show_formspec` function accepts a player name, the formspec name, and the
formspec itself. The formspec name should be a valid itemname, ie: in the format
`modname:itemname`.
### Padding and Spacing
<figure class="right_image">
<img src="{{ page.root }}/static/formspec_padding_spacing.png" alt="Padding and spacing">
<figcaption>
The guessing game formspec.
</figcaption>
</figure>
Padding is the gap between the edge of the formspec and its contents, or between unrelated
elements, shown in red. Spacing is the gap between related elements, shown in blue.
It is fairly standard to have a padding of `0.375` and a spacing of `0.25`.
<div style="clear: both;"></div>
### Receiving Formspec Submissions
When `show_formspec` is called, the formspec is sent to the client to be displayed.
For formspecs to be useful, information needs to be returned from the client to server.
The method for this is called formspec field submission, and for `show_formspec`, that
submission is received using a global callback:
```lua
"foobar"
"foo" .. "bar"
```
## Callbacks
It's possible to expand the previous example with a callback:
```lua
-- Show form when the /formspec command is used.
minetest.register_chatcommand("formspec", {
func = function(name, param)
minetest.show_formspec(name, "mymod:form",
"size[4,3]" ..
"label[0,0;Hello, " .. name .. "]" ..
"field[1,1.5;3,1;name;Name;]" ..
"button_exit[1,2;2,1;exit;Save]")
end
})
-- Register callback
minetest.register_on_player_receive_fields(function(player,
formname, fields)
if formname ~= "mymod:form" then
-- Formname is not mymod:form,
-- exit callback.
return false
core.register_on_player_receive_fields(function(player, formname, fields)
if formname ~= "guessing:game" then
return
end
-- Send message to player.
minetest.chat_send_player(player:get_player_name(),
"You said: " .. fields.name .. "!")
-- Return true to stop other callbacks from
-- receiving this submission.
return true
if fields.guess then
local pname = player:get_player_name()
core.chat_send_all(pname .. " guessed " .. fields.number)
end
end)
```
The function given in minetest.register_on_player_receive_fields is called
every time a user submits a form. Most callbacks will check the formname given
The function given in `core.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 all forms - it depends on what you
want to do.
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.
{% include notice.html notice=page.submit_vuln %}
### Fields
So, now the formspec is sent to the client and the client sends information back.
The next step is to somehow generate and remember the target value, and to update
the formspec based on guesses. The way to do this is using a concept called
"contexts".
The `fields` parameter to the function is a table, index by string, of the values
submitted by the user. You can access values in the table via fields.name,
where 'name' is the name of the element.
As well as retrieving the values of each element, you can also get which button
was clicked. In this case, the button called 'exit' was clicked, so fields.exit
will be true.
### Contexts
Some elements can submit the form without the user clicking a button,
such as a check box. You can detect these cases by looking
for a clicked button.
```lua
-- An example of what fields could contain,
-- using the above code
{
name = "Foo Bar",
exit = true
}
```
## Contexts
In many cases you want minetest.show_formspec to give information
In many cases you want core.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.
what a chat command was called with, or what the dialog is about. In this case,
the target value that needs to be remembered.
For example, you might make a form to handle land protection information:
A context is a per-player table to store information, and the contexts for all
online players are stored in a file-local variable:
```lua
--
-- Step 1) set context when player requests the formspec
--
local _contexts = {}
local function get_context(name)
local context = _contexts[name] or {}
_contexts[name] = context
return context
end
-- land_formspec_context[playername] gives the player's context.
local land_formspec_context = {}
minetest.register_chatcommand("land", {
func = function(name, param)
if param == "" then
minetest.chat_send_player(name,
"Incorrect parameters - supply a land ID")
return
end
-- Save information
land_formspec_context[name] = {id = param}
minetest.show_formspec(name, "mylandowner:edit",
"size[4,4]" ..
"field[1,1;3,1;plot;Plot Name;]" ..
"field[1,2;3,1;owner;Owner;]" ..
"button_exit[1,3;2,1;exit;Save]")
end
})
--
-- Step 2) retrieve context when player submits the form
--
minetest.register_on_player_receive_fields(function(player,
formname, fields)
if formname ~= "mylandowner:edit" then
return false
end
-- Load information
local context = land_formspec_context[player:get_player_name()]
if context then
minetest.chat_send_player(player:get_player_name(), "Id " ..
context.id .. " is now called " .. fields.plot ..
" and owned by " .. fields.owner)
-- Delete context if it is no longer going to be used
land_formspec_context[player:get_player_name()] = nil
return true
else
-- Fail gracefully if the context does not exist.
minetest.chat_send_player(player:get_player_name(),
"Something went wrong, try again.")
end
core.register_on_leaveplayer(function(player)
_contexts[player:get_player_name()] = nil
end)
```
## Node Meta Formspecs
Next, we need to modify the show code to update the context
before showing the formspec:
minetest.show_formspec is not the only way to show a formspec; you can also
add formspecs to a [node's meta data](node_metadata.html). For example,
```lua
function guessing.show_to(name)
local context = get_context(name)
context.target = context.target or math.random(1, 10)
local fs = guessing.get_formspec(name, context)
core.show_formspec(name, "guessing:game", fs)
end
```
We also need to modify the formspec generation code to use the context:
```lua
function guessing.get_formspec(name, context)
local text
if not context.guess then
text = "I'm thinking of a number... Make a guess!"
elseif context.guess == context.target then
text = "Hurray, you got it!"
elseif context.guess > context.target then
text = "Too high!"
else
text = "Too low!"
end
```
Note that it's good practice for `get_formspec` to only read the context, and not
update it at all. This can make the function simpler, and also easier to test.
And finally, we need to update the handler to update the context with the guess:
```lua
if fields.guess then
local name = player:get_player_name()
local context = get_context(name)
context.guess = tonumber(fields.number)
guessing.show_to(name)
end
```
## Formspec Sources
There are three different ways that a formspec can be delivered to the client:
* [show_formspec](#guessing-game): the method used above, fields are received by `register_on_player_receive_fields`.
* [Node Meta Formspecs](#node-meta-formspecs): the node contains a formspec in its meta data, and the client
shows it *immediately* when the player rightclicks. Fields are received by a
method in the node definition called `on_receive_fields`.
* [Player Inventory Formspecs](#player-inventory-formspecs): the formspec is sent to the client at some point, and then
shown immediately when the player presses `i`. Fields are received by
`register_on_player_receive_fields`.
### Node Meta Formspecs
`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,
this is used with chests to allow for faster opening times -
you don't need to wait for the server to send the player the chest formspec.
```lua
minetest.register_node("mymod:rightclick", {
core.register_node("mymod:rightclick", {
description = "Rightclick me!",
tiles = {"mymod_rightclick.png"},
groups = {cracky = 1},
after_place_node = function(pos, placer)
-- This function is run when the chest node is placed.
-- This function is run when the chest node is placed.
-- The following code sets the formspec for chest.
-- Meta is a way of storing data onto a node.
local meta = minetest.get_meta(pos)
local meta = core.get_meta(pos)
meta:set_string("formspec",
"size[5,5]"..
"label[1,1;This is shown on right click]"..
"formspec_version[4]" ..
"size[5,5]" ..
"label[1,1;This is shown on right click]" ..
"field[1,2;2,1;x;x;]")
end,
on_receive_fields = function(pos, formname, fields, player)
if(fields.quit) then return end
if fields.quit then
return
end
print(fields.x)
end
})
@ -297,6 +355,25 @@ receive form input for meta formspecs, you must include an
`on_receive_fields` entry when registering the node.
This style of callback triggers when you press enter
in a field, which is impossible with `minetest.show_formspec`;
in a field, which is impossible with `core.show_formspec`;
however, this kind of form can only be shown by right-clicking on a
node. It cannot be triggered programmatically.
### Player Inventory Formspecs
The player inventory formspec is the one shown when the player presses i.
The global callback is used to receive events from this formspec, and the
formname is `""`.
There are a number of different mods which allow multiple mods to customise the
player inventory. Minetest Game uses
[SFINV](https://github.com/rubenwardy/sfinv/blob/master/Tutorial.md).
### Your Turn
* Extend the Guessing Game to keep track of each player's top score, where the
top score is how many guesses it took.
* Make a node called "Inbox" where users can open up a formspec and leave messages.
This node should store the placers' name as `owner` in the meta, and should use
`show_formspec` to show different formspecs to different players.

View File

@ -3,28 +3,29 @@ title: HUD
layout: default
root: ../..
idx: 4.6
description: Learn how to display HUD elements
redirect_from: /en/chapters/hud.html
---
## Introduction
## Introduction <!-- omit in toc -->
Heads Up Display (HUD) elements allow you to show text, images, and other graphical elements.
The HUD doesn't accept user input. For that, you should use a [Formspec](formspecs.html).
The HUD doesn't accept user input; for that, you should use a [formspec](formspecs.html).
* [Positioning](#positioning)
* [Position and Offset](#position-and-offset)
* [Alignment](#alignment)
* [Scoreboard](#scoreboard)
* [Text Elements](#text-elements)
* [Parameters](#parameters)
* [Our Example](#our-example)
* [Image Elements](#image-elements)
* [Parameters](#parameters-1)
* [Scale](#scale)
* [Changing an Element](#changing-an-element)
* [Storing IDs](#storing-ids)
* [Other Elements](#other-elements)
- [Positioning](#positioning)
- [Position and Offset](#position-and-offset)
- [Alignment](#alignment)
- [Scoreboard](#scoreboard)
- [Text Elements](#text-elements)
- [Parameters](#parameters)
- [Our Example](#our-example)
- [Image Elements](#image-elements)
- [Parameters](#parameters-1)
- [Scale](#scale)
- [Changing an Element](#changing-an-element)
- [Storing IDs](#storing-ids)
- [Other Elements](#other-elements)
## Positioning
@ -43,8 +44,8 @@ the HUD needs to work well on all screen types.
Minetest's solution to this is to specify the location of an element using both
a percentage position and an offset.
The percentage position is relative to the screen size, so to place an element
in the center of the screen you would need to provide a percentage position of half
the screen, eg (50%, 50%), and an offset of (0, 0).
in the centre of the screen, you would need to provide a percentage position of half
the screen, e.g. (50%, 50%), and an offset of (0, 0).
The offset is then used to move an element relative to the percentage position.
@ -55,7 +56,7 @@ The offset is then used to move an element relative to the percentage position.
Alignment is where the result of position and offset is on the element -
for example, `{x = -1.0, y = 0.0}` will make the result of position and offset point
to the left of the element's bounds. This is particularly useful when you want to
make a text element left, center, or right justified.
make a text element aligned to the left, centre, or right.
<figure>
<img
@ -65,7 +66,7 @@ make a text element left, center, or right justified.
</figure>
The above diagram shows 3 windows (blue), each with a single HUD element (yellow)
with a different alignment each time. The arrow is the result of the position
and a different alignment each time. The arrow is the result of the position
and offset calculation.
### Scoreboard
@ -79,7 +80,7 @@ score panel like so:
alt="screenshot of the HUD we're aiming for">
</figure>
In the above screenshot all the elements have the same percentage position -
In the above screenshot, all the elements have the same percentage position
(100%, 50%) - but different offsets. This allows the whole thing to be anchored
to the right of the window, but to resize without breaking.
@ -88,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 = minetest.get_player_by_name("username")
local player = core.get_player_by_name("username")
local idx = player:hud_add({
hud_elem_type = "text",
position = {x = 0.5, y = 0.5},
@ -107,16 +108,21 @@ or remove a HUD element.
The element's type is given using the `hud_elem_type` property in the definition
table. The meaning of other properties varies based on this type.
`scale` is the maximum bounds of text, text outside these bounds is cropped, eg: `{x=100, y=100}`.
`scale` is the maximum bounds of text; text outside these bounds is cropped, e.g.: `{x=100, y=100}`.
`number` is the text's colour, and is in [Hexadecimal form](http://www.colorpicker.com/), eg: `0xFF0000`.
`number` is the text's colour, and is in [hexadecimal form](http://www.colorpicker.com/), e.g.: `0xFF0000`.
### Our Example
Let's go ahead, and place all the text in our score panel:
Let's go ahead and place all the text in our score panel:
```lua
-- Get the dig and place count from storage, or default to 0
local meta = player:get_meta()
local digs_text = "Digs: " .. meta:get_int("score:digs")
local places_text = "Places: " .. meta:get_int("score:places")
player:hud_add({
hud_elem_type = "text",
position = {x = 1, y = 0.5},
@ -127,12 +133,6 @@ player:hud_add({
number = 0xFFFFFF,
})
-- Get the dig and place count from storage, or default to 0
local digs = tonumber(player:get_attribute("score:digs") or 0)
local digs_text = "Digs: " .. digs
local places = tonumber(player:get_attribute("score:digs") or 0)
local places_text = "Places: " .. places
player:hud_add({
hud_elem_type = "text",
position = {x = 1, y = 0.5},
@ -191,8 +191,8 @@ You will now have this:
The `text` field is used to provide the image name.
If a co-ordinate is positive, then it is a scale factor with 1 being the
original image size, and 2 being double the size, and so on.
However, if a co-ordinate is negative it is a percentage of the screensize.
original image size, 2 being double the size, and so on.
However, if a co-ordinate is negative, it is a percentage of the screen size.
For example, `x=-100` is 100% of the width.
### Scale
@ -200,7 +200,7 @@ For example, `x=-100` is 100% of the width.
Let's make the progress bar for our score panel as an example of scale:
```lua
local percent = tonumber(player:get_attribute("score:score") or 0.2)
local percent = tonumber(meta:get("score:score") or 0.2)
player:hud_add({
hud_elem_type = "image",
@ -226,7 +226,7 @@ There is one problem however, it won't update when the stats change.
## Changing an Element
You can use the ID returned by the hud_add method to update or remove it later.
You can use the ID returned by the `hud_add` method to update it or remove it later.
```lua
local idx = player:hud_add({
@ -240,7 +240,7 @@ player:hud_remove(idx)
```
The `hud_change` method takes the element ID, the property to change, and the new
value. The above call changes the `text` property from "Hello World" to "Test".
value. The above call changes the `text` property from "Hello World" to "New text".
This means that doing the `hud_change` immediately after the `hud_add` is
functionally equivalent to the following, in a rather inefficient way:
@ -261,12 +261,11 @@ local saved_huds = {}
function score.update_hud(player)
local player_name = player:get_player_name()
local digs = tonumber(player:get_attribute("score:digs") or 0)
local digs_text = "Digs: " .. digs
local places = tonumber(player:get_attribute("score:digs") or 0)
local places_text = "Places: " .. places
local percent = tonumber(player:get_attribute("score:score") or 0.2)
-- Get the dig and place count from storage, or default to 0
local meta = player:get_meta()
local digs_text = "Digs: " .. meta:get_int("score:digs")
local places_text = "Places: " .. meta:get_int("score:places")
local percent = tonumber(meta:get("score:score") or 0.2)
local ids = saved_huds[player_name]
if ids then
@ -282,9 +281,9 @@ function score.update_hud(player)
end
end
minetest.register_on_joinplayer(score.update_hud)
core.register_on_joinplayer(score.update_hud)
minetest.register_on_leaveplayer(function(player)
core.register_on_leaveplayer(function(player)
saved_huds[player:get_player_name()] = nil
end)
```
@ -292,4 +291,4 @@ end)
## Other Elements
Read [lua_api.txt]({{ page.root }}/lua_api.html#hud-element-types) for a complete list of HUD elements.
Read [lua_api.md](https://minetest.gitlab.io/minetest/hud/) for a complete list of HUD elements.

View File

@ -3,20 +3,24 @@ title: Player Physics
layout: default
root: ../..
idx: 4.4
description: Learn how to make a player run faster, jump higher or simply float
redirect_from: /en/chapters/player_physics.html
---
## Introduction
## Introduction <!-- omit in toc -->
Player physics can be modified using physics overrides. Physics overrides can set the
walking speed, jump speed and gravity constants. Physics overrides are set on a player
by player basis, and are multipliers. For example, a value of 2 for gravity would make
gravity twice as strong.
Player physics can be modified using physics overrides.
Physics overrides can set the walking speed, jump speed,
and gravity constants.
Physics overrides are set on a player-by-player basis
and are multipliers.
For example, a value of 2 for gravity would make gravity twice as strong.
* [Basic Example](#basic_example)
* [Available Overrides](#available_overrides)
* [Mod Incompatibility ](#mod_incompatibility)
* [Your Turn](#your_turn)
- [Basic Example](#basic-example)
- [Available Overrides](#available-overrides)
- [Old Movement Behaviour](#old-movement-behaviour)
- [Mod Incompatibility](#mod-incompatibility)
- [Your Turn](#your-turn)
## Basic Example
@ -24,27 +28,27 @@ Here is an example of how to add an antigravity command, which
puts the caller in low G:
```lua
minetest.register_chatcommand("antigravity", {
core.register_chatcommand("antigravity", {
func = function(name, param)
local player = minetest.get_player_by_name(name)
local player = core.get_player_by_name(name)
player:set_physics_override({
gravity = 0.1 -- set gravity to 10% of its original value
-- (0.1 * 9.81)
gravity = 0.1, -- set gravity to 10% of its original value
-- (0.1 * 9.81)
})
end
end,
})
```
## Available Overrides
player:set_physics_override() is given a table of overrides.\\
According to [lua_api.txt]({{ page.root }}/lua_api.html#player-only-no-op-for-other-objects),
`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),
these can be:
* speed: multiplier to default walking speed value (default: 1)
* jump: multiplier to default jump value (default: 1)
* gravity: multiplier to default gravity value (default: 1)
* sneak: whether player can sneak (default: true)
* sneak: whether the player can sneak (default: true)
### Old Movement Behaviour
@ -57,7 +61,7 @@ unintended, it has been preserved in overrides due to its use on many servers.
Two overrides are needed to fully restore old movement behaviour:
* new_move: whether the player uses new movement (default: true)
* sneak_glitch: whether the player can use "sneak elevators" (default: false)
* sneak_glitch: whether the player can use 'sneak elevators' (default: false)
## Mod Incompatibility
@ -69,5 +73,5 @@ player's speed, only the last one to run will be in effect.
## Your Turn
* **Sonic**: Set the speed multiplier to a high value (at least 6) when a player joins the game.
* **Super bounce**: Increase the jump value so that the player can jump 20 meters (1 meter is 1 node).
* **Super bounce**: Increase the jump value so that the player can jump 20 metres (1 metre is 1 node).
* **Space**: Make gravity decrease as the player gets higher.

View File

@ -7,17 +7,17 @@ description: Registering privs.
redirect_from: /en/chapters/privileges.html
---
## Introduction
## Introduction <!-- omit in toc -->
Privileges, often called privs for short, give players the ability to perform
certain actions. Server owners can grant and revoke privileges to control
which abilities each player has.
* [When to use Privileges](#when-to-use-privileges)
* [Declaring Privileges](#declaring-privileges)
* [Checking for Privileges](#checking-for-privileges)
* [Getting and Setting Privileges](#getting-and-setting-privileges)
* [Adding Privileges to basic_privs](#adding-privileges-to-basic-privs)
- [When to use Privileges](#when-to-use-privileges)
- [Declaring Privileges](#declaring-privileges)
- [Checking for Privileges](#checking-for-privileges)
- [Getting and Setting Privileges](#getting-and-setting-privileges)
- [Adding Privileges to basic_privs](#adding-privileges-to-basicprivs)
## When to use Privileges
@ -48,7 +48,7 @@ Privileges are **not** for indicating class or status.
Use `register_privilege` to declare a new privilege:
```lua
minetest.register_privilege("vote", {
core.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 = minetest.check_player_privs(player_or_name, {
local has, missing = core.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 = minetest.check_player_privs(name, {
local has, missing = core.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 minetest.check_player_privs(name, { interact=true }) then
if not core.check_player_privs(name, { interact=true }) then
return false, "You need interact for this!"
end
```
@ -99,11 +99,11 @@ being online.
```lua
local privs = minetest.get_player_privs(name)
local privs = core.get_player_privs(name)
print(dump(privs))
privs.vote = true
minetest.set_player_privs(name, privs)
core.set_player_privs(name, privs)
```
Privileges are always specified as a key-value table with the key being
@ -120,13 +120,12 @@ the privilege name and the value being a boolean.
## Adding Privileges to basic_privs
Players with the `basic_privs` privilege are able to grant and revoke a limited
set of privileges. It's common to give this privilege to moderators, so that
set of privileges. It's common to give this privilege to moderators so that
they can grant and revoke `interact` and `shout`, but can't grant themselves or other
players privileges such as `give` and `server`, which have greater potential for abuse.
players privileges with greater potential for abuse such as `give` and `server`.
To add a privilege to `basic_privs` and adjust which privileges your moderators can
To add a privilege to `basic_privs`, and adjust which privileges your moderators can
grant and revoke from other players, you must change the `basic_privs` setting.
To do this, you must edit the minetest.conf file.
By default, `basic_privs` has the following value:

View File

@ -1,244 +1,5 @@
---
title: "SFINV: Inventory Formspec"
layout: default
root: ../..
idx: 4.7
sitemap: false
redirect_from: /en/chapters/sfinv.html
redirect_to: "https://github.com/rubenwardy/sfinv/blob/master/Tutorial.md"
---
## Introduction
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" as
it's entirely possible that a mod or game decides to show them in
some other format instead.
For example, multiple pages could be shown on one view.
* [Registering a Page](#registering-a-page)
* [Receiving events](#receiving-events)
* [Conditionally showing to players](#conditionally-showing-to-players)
* [on_enter and on_leave callbacks](#on_enter-and-on_leave-callbacks)
## 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">
<figcaption>
The player admin page created above.
</figcaption>
</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,
```
Fields is the exact same as the fields given to the subscribers of
`minetest.register_on_player_receive_fields`. The return value of
`on_player_receive_fields` is the same as a normal player receive fields.
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 it using SFINV's API.
This means that you need to manually request that SFINV regenerates the inventory
formspec on any events that may change `is_in_nav`'s result. In our case,
we need to do that whenever kick or ban is granted or revoked to a player:
```lua
local function on_grant_revoke(grantee, granter, priv)
if priv ~= "kick" and priv ~= "ban" then
return
end
local player = minetest.get_player_by_name(grantee)
if not player then
return
end
local context = sfinv.get_or_create_context(player)
if context.page ~= "myadmin:myadmin" then
return
end
sfinv.set_player_inventory_formspec(player, context)
end
minetest.register_on_priv_grant(on_grant_revoke)
minetest.register_on_priv_revoke(on_grant_revoke)
```
## on_enter and on_leave callbacks
A player *enters* a tab when the tab is selected, and *leaves* a
tab when another tab is about to be selected.
It's possible for multiple pages to be selected if a custom theme is
used.
Note that these events may not be triggered by the player.
The player may not even have the formspec open at that time.
For example, on_enter is called for the home page when a player
joins the game even before they open their inventory.
It's not possible to cancel a page change, as that would potentially
confuse the player.
```lua
on_enter = function(self, player, context)
end,
on_leave = function(self, player, context)
end,
```
## Adding to an existing page
To add content to an existing page, you will need to override the page
and modify the returned formspec.
```lua
local old_func = sfinv.registered_pages["sfinv:crafting"].get
sfinv.override_page("sfinv:crafting", {
get = function(self, player, context, ...)
local ret = old_func(self, player, context, ...)
if type(ret) == "table" then
ret.formspec = ret.formspec .. "label[0,0;Hello]"
else
-- Backwards compatibility
ret = ret .. "label[0,0;Hello]"
end
return ret
end
})
```

View File

@ -2,10 +2,10 @@
title: Intro to Clean Architectures
layout: default
root: ../..
idx: 7.3
idx: 8.4
---
## Introduction
## Introduction <!-- omit in toc -->
Once your mod reaches a respectable size, you'll find it harder and harder to
keep the code clean and free of bugs. This is an especially big problem when using
@ -17,12 +17,11 @@ and common design patterns to achieve that. Please note that this chapter isn't
meant to be prescriptive, but to instead give you an idea of the possibilities.
There is no one good way of designing a mod, and good mod design is very subjective.
* [Cohesion, Coupling, and Separation of Concerns](#cohesion-coupling-and-separation-of-concerns)
* [Model-View-Controller](#model-view-controller)
* [API-View](#api-view)
* [Observer](#observer)
* [Conclusion](#conclusion)
- [Cohesion, Coupling, and Separation of Concerns](#cohesion-coupling-and-separation-of-concerns)
- [Observer](#observer)
- [Model-View-Controller](#model-view-controller)
- [API-View](#api-view)
- [Conclusion](#conclusion)
## Cohesion, Coupling, and Separation of Concerns
@ -33,7 +32,7 @@ code is thrown in together with no clear boundaries. This ultimately makes a
project completely unmaintainable, ending in its abandonment.
The opposite of this is to design your project as a collection of interacting
smaller programs or areas of code.
smaller programs or areas of code. <!-- Weird wording? -->
> Inside every large program, there is a small program trying to get out.
>
@ -51,15 +50,57 @@ a low amount of coupling, as this means that changing the APIs of certain areas
will be more feasible.
Note that these apply both when thinking about the relationship between mods,
and the relationship between areas inside a mod. In both cases you should try
to get high cohesion and low coupling.
and the relationship between areas inside a mod.
## Observer
A simple way to separate different areas of code is to use the Observer pattern.
Let's take the example of unlocking an achievement when a player first kills a
rare animal. The naïve approach would be to have achievement code in the mob
kill function, checking the mob name and unlocking the award if it matches.
This is a bad idea, however, as it makes the mobs mod coupled to the achievements
code. If you kept on doing this - for example, adding XP to the mob death code -
you could end up with a lot of messy dependencies.
Enter the Observer pattern. Instead of the mymobs mod caring about awards,
the mymobs mod exposes a way for other areas of code to register their
interest in an event and receive data about the event.
```lua
mymobs.registered_on_death = {}
function mymobs.register_on_death(func)
table.insert(mymobs.registered_on_death, func)
end
-- in mob death code
for i=1, #mymobs.registered_on_death do
mymobs.registered_on_death[i](entity, reason)
end
```
Then the other code registers its interest:
```lua
mymobs.register_on_death(function(mob, reason)
if reason.type == "punch" and reason.object and
reason.object:is_player() then
awards.notify_mob_kill(reason.object, mob.name)
end
end)
```
You may be thinking - wait a second, this looks awfully familiar. And you're right!
The Minetest API is heavily Observer-based to stop the engine having to care about
what is listening to something.
## Model-View-Controller
In the next chapter we will discuss how to automatically test your code, and one
of the problems we will have is how to separate your logic
(calculations, what should be done) from API calls (`minetest.*`, other mods)
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)
as much as possible.
One way to do this is to think about:
@ -75,7 +116,7 @@ and any associated metadata. Actions you can take are `create`, `edit`, or
receive fields. These are 3 areas that can usually be separated pretty well.
In your tests, you will be able to make sure that an action when triggered does
the right thing to the data, but you won't need to test that an event calls an
the right thing to the data. You won't need to test that an event calls an
action (as this would require using the Minetest API, and this area of code
should be made as small as possible anyway.)
@ -98,8 +139,8 @@ function land.get_by_name(area_name)
end
```
Your actions should also be pure, however calling other functions is more
acceptable.
Your actions should also be pure, but calling other functions is more
acceptable than in the above.
```lua
-- Controller
@ -117,7 +158,7 @@ end
```
Your event handlers will have to interact with the Minetest API. You should keep
the amount of calculations to a minimum, as you won't be able to test this area
the number of calculations to a minimum, as you won't be able to test this area
very easily.
```lua
@ -132,14 +173,14 @@ function land.show_create_formspec(name)
]]
end
minetest.register_chatcommand("/land", {
core.register_chatcommand("/land", {
privs = { land = true },
func = function(name)
land.handle_creation_request(name)
end,
})
minetest.register_on_player_receive_fields(function(player,
core.register_on_player_receive_fields(function(player,
formname, fields)
land.handle_create_submit(player:get_player_name(),
fields.area_name)
@ -155,7 +196,7 @@ most of the calculations are made.
The controller should have no knowledge about the Minetest API - notice how
there are no Minetest calls or any view functions that resemble them.
You should *NOT* have a function like `view.hud_add(player, def)`.
Instead, the view defines some actions the controller can tell the view to do,
Instead, the view defines some actions that the controller can tell the view to do,
like `view.add_hud(info)` where info is a value or table which doesn't relate
to the Minetest API at all.
@ -167,27 +208,27 @@ to the Minetest API at all.
</figure>
It is important that each area only communicates with its direct neighbours,
as shown above, in order to reduce how much you needs to change if you modify
as shown above, in order to reduce how much you need to change if you modify
an area's internals or externals. For example, to change the formspec you
would only need to edit the view. To change the view API, you would only need to
change the view and the controller, but not the model at all.
In practice, this design is rarely used because of the increased complexity
and because it doesn't give many benefits for most types of mods. Instead,
you tend to see a lot more of a less formal and strict kind of design -
varients of the API-View.
you will commonly see a less formal and strict kind of design -
variants of the API-View.
### API-View
In an ideal world, you'd have the above 3 areas perfectly separated with all
events going into the controller before going back to the normal view. But
this isn't the real world. A good half-way house is to reduce the mod into 2
this isn't the real world. A good compromise is to reduce the mod into two
parts:
* **API** - what was the model and controller. There should be no uses of
`minetest.` here.
* **View** - the view as before. It's a good idea to structure this into separate
* **API** - This was the model and controller above. There should be no uses of
`core.` here.
* **View** - This was also the view above. It's a good idea to structure this into separate
files for each type of event.
rubenwardy's [crafting mod](https://github.com/rubenwardy/crafting) roughly
@ -201,57 +242,9 @@ as it doesn't use any Minetest APIs - as shown in the
[next chapter](unit_testing.html) and seen in the crafting mod.
## Observer
Reducing coupling may seem hard to do to begin with, but you'll make a lot of
progress by splitting your code up well using a design like the one given above.
It's not always possible to remove the need for one area to communicate with
another, but there are ways to decouple anyway - one such way being the Observer
pattern.
Let's take the example of unlocking an achievement when a player first kills a
rare animal. The naive approach would be to have achievement code in the mob
kill function, checking the mob name and unlocking the award if it matches.
This is a bad idea however, as it makes the mobs mod coupled to the achievements
code. If you kept on doing this - for example, adding XP to the mob death code -
you could end up with a lot of messy dependencies.
Enter the Observer pattern. Instead of the mobs mod caring about awards, mobs
exposes a way for other areas of code to register their interest in an event
and receive data about the event.
```lua
mobs.registered_on_death = {}
function mobs.register_on_death(func)
table.insert(mobs.registered_on_death, func)
end
-- mob death code
for i=1, #mobs.registered_on_death do
mobs.registered_on_death[i](entity, reason)
end
```
Then the other code registers its interest:
```lua
-- awards
mobs.register_on_death(function(mob, reason)
if reason.type == "punch" and reason.object and
reason.object:is_player() then
awards.notify_mob_kill(reason.object, mob.name)
end
end)
```
You may be thinking - wait a second, this looks awfully familiar. And you're right!
The Minetest API is heavily Observer based to stop the engine having to care about
what is listening to something.
## Conclusion
Good code design is subjective, and depends on the project you're making. As a
Good code design is subjective, and highly depends on the project you're making. As a
general rule, try to keep cohesion high and coupling low. Phrased differently,
keep related code together and unrelated code apart, and keep dependencies simple.

View File

@ -2,79 +2,52 @@
title: Common Mistakes
layout: default
root: ../..
idx: 7.1
idx: 8.1
redirect_from: /en/chapters/common_mistakes.html
---
## Introduction
## Introduction <!-- omit in toc -->
This chapter details common mistakes, and how to avoid them.
* [Never Store ObjectRefs (ie: players or entities)](#never-store-objectrefs-ie-players-or-entities)
* [Don't Trust Formspec Submissions](#dont-trust-formspec-submissions)
* [Set ItemStacks After Changing Them](#set-itemstacks-after-changing-them)
- [Be Careful When Storing ObjectRefs (ie: players or entities)](#be-careful-when-storing-objectrefs-ie-players-or-entities)
- [Don't Trust Formspec Submissions](#dont-trust-formspec-submissions)
- [Set ItemStacks After Changing Them](#set-itemstacks-after-changing-them)
## Never Store ObjectRefs (ie: players or entities)
## Be Careful When Storing ObjectRefs (ie: players or entities)
If the object an ObjectRef represents is deleted - for example, if the player goes
offline or the entity is unloaded - then calling methods on that object
will result in a crash.
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.
For example, don't do this:
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:
```lua
minetest.register_on_joinplayer(function(player)
local function func()
local pos = player:get_pos() -- BAD!
-- `player` is stored then accessed later.
-- If the player leaves in that second, the server *will* crash.
end
minetest.after(1, func)
foobar[player:get_player_name()] = player
-- RISKY
-- It's not recommended to do this.
-- Use minetest.get_connected_players() and
-- minetest.get_player_by_name() instead.
end)
```
Do this:
```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)
-- This only works in Minetest 5.2+
if obj:get_pos() then
-- is valid!
end
```
## Don't Trust Formspec Submissions
Malicious clients can submit formspecs whenever they like with whatever content
they like.
Malicious clients can submit formspecs whenever they like, with
whatever content they like.
For example, the following code has a vulnerability which will allow players to
For example, the following code has a vulnerability which allows players to
give themselves moderator privileges:
```lua
local function show_formspec(name)
if not minetest.check_player_privs(name, { privs = true }) then
if not core.check_player_privs(name, { privs = true }) then
return false
end
minetest.show_formspec(name, "modman:modman", [[
core.show_formspec(name, "modman:modman", [[
size[3,2]
field[0,0;3,1;target;Name;]
button_exit[0,1;3,1;sub;Promote]
@ -82,14 +55,14 @@ local function show_formspec(name)
return true
})
minetest.register_on_player_receive_fields(function(player,
core.register_on_player_receive_fields(function(player,
formname, fields)
-- BAD! Missing privilege check here!
local privs = minetest.get_player_privs(fields.target)
local privs = core.get_player_privs(fields.target)
privs.kick = true
privs.ban = true
minetest.set_player_privs(fields.target, privs)
core.set_player_privs(fields.target, privs)
return true
end)
```
@ -97,9 +70,9 @@ end)
Add a privilege check to solve this:
```lua
minetest.register_on_player_receive_fields(function(player,
core.register_on_player_receive_fields(function(player,
formname, fields)
if not minetest.check_player_privs(name, { privs = true }) then
if not core.check_player_privs(name, { privs = true }) then
return false
end
@ -123,7 +96,7 @@ stack:get_meta():set_string("description", "Partially eaten")
-- BAD! Modification will be lost
```
Do this:
Do this instead:
```lua
local inv = player:get_inventory()
@ -137,10 +110,8 @@ The behaviour of callbacks is slightly more complicated. Modifying an `ItemStack
are given will change it for the caller too, and any subsequent callbacks. However,
it will only be saved in the engine if the callback caller sets it.
Avoid this:
```lua
minetest.register_on_item_eat(function(hp_change, replace_with_item,
core.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
@ -149,12 +120,12 @@ end)
```
If no callbacks cancel this, the stack will be set and the description will be updated,
but if a callback cancels this, then the update may be lost.
but if a callback does cancel this, then the update may be lost.
It's better to do this instead:
```lua
minetest.register_on_item_eat(function(hp_change, replace_with_item,
core.register_on_item_eat(function(hp_change, replace_with_item,
itemstack, user, pointed_thing)
itemstack:get_meta():set_string("description", "Partially eaten")
user:get_inventory():set_stack("main", user:get_wield_index(),

View File

@ -2,24 +2,24 @@
title: Automatic Error Checking
layout: default
root: ../..
idx: 7.2
idx: 8.2
description: Use LuaCheck to find errors
redirect_from: /en/chapters/luacheck.html
---
## Introduction
## Introduction <!-- omit in toc -->
In this chapter you will learn how to use a tool called LuaCheck to automatically
In this chapter, you will learn how to use a tool called LuaCheck to automatically
scan your mod for any mistakes. This tool can be used in combination with your
editor to provide alerts to any mistakes.
* [Installing LuaCheck](#installing-luacheck)
* [Windows](#windows)
* [Linux](#linux)
* [Running LuaCheck](#running-luacheck)
* [Configuring LuaCheck](#configuring-luacheck)
* [Troubleshooting](#troubleshooting)
* [Checking Commits with Travis](#checking-commits-with-travis)
- [Installing LuaCheck](#installing-luacheck)
- [Windows](#windows)
- [Linux](#linux)
- [Running LuaCheck](#running-luacheck)
- [Configuring LuaCheck](#configuring-luacheck)
- [Troubleshooting](#troubleshooting)
- [Using with editor](#using-with-editor)
## Installing LuaCheck
@ -30,7 +30,7 @@ Simply download luacheck.exe from
### Linux
First you'll need to install LuaRocks:
First, you'll need to install LuaRocks:
sudo apt install luarocks
@ -80,20 +80,20 @@ read_globals = {
}
```
Next you'll need to test that it works by running LuaCheck. You should get a lot
less errors this time. Starting at the first error you get, either modify the
configuration to take it into account, or if there's a bug then fix it - take
a look at the list below.
Next, you'll need to test that it works by running LuaCheck. You should get a lot
fewer errors this time. Starting at the first error you get, modify the code to
remove the issue, or modify the configuration if the code is correct. See the list
below.
### Troubleshooting
* **accessing undefined variable foobar** - If `foobar` is meant to be a global,
then add it to `read_globals`. Otherwise, add any missing `local`s to the mod.
add it to `read_globals`. Otherwise, add any missing `local`s to the mod.
* **setting non-standard global variable foobar** - If `foobar` is meant to be a global,
then add it to `globals`. Remove from `read_globals` if present there.
Otherwise add any missing `local`s to the mod.
add it to `globals`. Remove from `read_globals` if present.
Otherwise, add any missing `local`s to the mod.
* **mutating read-only global variable 'foobar'** - Move `foobar` from `read_globals` to
`globals`.
`globals`, or stop writing to foobar.
## Using with editor
@ -101,50 +101,7 @@ It is highly recommended that you find and install a plugin for your editor of c
to show you errors without running a command. Most editors will likely have a plugin
available.
* **Atom** - `linter-luacheck`
* **VSCode** - Ctrl+P, then paste: `ext install dwenegar.vscode-luacheck`
* **Sublime** - Install using package-control:
[SublimeLinter](https://github.com/SublimeLinter/SublimeLinter),
[SublimeLinter-luacheck](https://github.com/SublimeLinter/SublimeLinter-luacheck)
## Checking Commits with Travis
If your project is public and is on Github, you can use TravisCI - a free service
to run jobs on commits to check them. This means that every commit you push will
be checked against LuaCheck, and a green tick or red cross 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 --no-color .
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 --no-color 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 a while 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.
[SublimeLinter-luacheck](https://github.com/SublimeLinter/SublimeLinter-luacheck).

View File

@ -2,7 +2,7 @@
title: Read More
layout: default
root: ../..
idx: 7.6
idx: 8.7
redirect_from: /en/chapters/readmore.html
---
@ -12,15 +12,13 @@ After you've read this book, take a look at the following.
### Minetest Modding
* Minetest's Lua API Reference - [HTML version]({{ page.root }}/lua_api.html) |
[Text version](https://github.com/minetest/minetest/blob/master/doc/lua_api.txt).
* Explore the [Developer Wiki](http://dev.minetest.net/Main_Page).
* 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).
* Look at [existing mods](https://forum.minetest.net/viewforum.php?f=11).
### Lua Programming
* [Programming in Lua (PIL)](http://www.lua.org/pil/).
* [Lua Crash Course](http://luatut.com/crash_course.html).
### 3D Modelling

View File

@ -2,21 +2,29 @@
title: Releasing a Mod
layout: default
root: ../..
idx: 7.5
idx: 8.6
redirect_from: /en/chapters/releasing.html
---
## Introduction
## Introduction <!-- omit in toc -->
Releasing, or publishing, a mod allows other people to make use of it. Once a mod has been
released it might be used in singleplayer games or on servers, including public servers.
* [License Choices](#license-choices)
* [Packaging](#packaging)
* [Uploading](#uploading)
* [Forum Topic](#forum-topic)
- [Choosing a License](#choosing-a-license)
- [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)
- [screenshot.png](#screenshotpng)
- [Uploading](#uploading)
- [Version Control Systems](#version-control-systems)
- [Releasing on ContentDB](#releasing-on-contentdb)
- [Forum Topic](#forum-topic)
## License Choices
## Choosing a License
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
@ -29,72 +37,75 @@ but can be suitable choices for artistic works such as images, text and meshes.
You are allowed any license; however, mods which disallow derivatives are banned from the
official Minetest forum. (For a mod to be allowed on the forum, other developers must be
able modify it and release the modified version.)
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:
Minetest and Minetest Game use.
You license your code under LGPL 2.1 and your art under CC-BY-SA.
This means that:
* Anyone can modify, redistribute and sell modified or unmodified versions.
* If someone modifies your mod, they must give their version the same license.
* Your copyright notice must be kept.
### WTFPL and CC0
### CC0
These licenses allows anyone to do what they want with your mod.
This 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.
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.
### MIT
This is a common license for mod code. The only restriction it places on users
of your mod is that they must include the same copyright notice and license
in any copies of the mod or of substantial parts of the mod.
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.
## Packaging
There are some files it is recommended to include in your mod
when you release it.
There are some files that are recommended to include in your mod or game
before you release it.
### README.txt
The readme file should state:
The README file should state:
* What the mod does.
* What the mod/game does, how to use it.
* What the license is.
* Current version of mod.
* How to install the mod.
* What dependencies there are / what the user needs to install.
* Where to report problems/bugs or get help.
* Optionally:
* where to report problems or get help.
* credits
### description.txt
### mod.conf / game.conf
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.
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.
Good example:
Adds soup, cakes, bakes and juices.
description = Adds soup, cakes, bakes and juices.
Don't do this:
Avoid this:
(BAD) The food mod for Minetest.
description = The food mod for Minetest. (<-- BAD! It's vague)
### screenshot.png
Screenshots should be 3:2 (3 pixels of width for every 2 pixels of height)
and have a minimum size of 300 x 200px.
The screenshot is displayed in the mod store.
The screenshot is displayed inside of Minetest as a thumbnail for the content.
## Uploading
@ -104,109 +115,46 @@ 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 on the forum and download the file
* **Direct link** - You should be able to click a link and download the file
without having to view another page.
* **Virus Free** - Mods with malicious content will be removed from the forum.
* **Virus Free** - Scammy upload hosts may contain insecure adverts.
ContentDB allows you to upload zip files, and meets these criteria.
### Version Control Systems
It is recommended that you use a version control system which:
A Version Control System (VCS) is software that manages changes to software,
often making it easier to distribute and receive contributed changes.
* Allows other developers to easily submit changes.
* Allows the code to be previewed before downloading.
* Allows users to submit bug reports.
The majority of Minetest modders use Git and a website like GitHub to distribute
their code.
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:
Using git can be difficult at first. If you need help with this please see:
* [Pro Git book](http://git-scm.com/book/en/v1/Getting-Started) - Free to read online.
* [GitHub for Windows app](https://help.github.com/articles/getting-started-with-github-for-windows/) -
Using a graphical interface on Windows to upload your code.
### Forum Attachments
## Releasing on ContentDB
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).
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.
You need to zip the files for the mod into a single file. How to do this varies from
operating system to operating system.
If you use Windows, go to the mod's folder and select all the files.
Right click, Send To > Compressed (zipped) folder.
Rename the resulting zip file to the name of your mod.
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>
Sign up to [ContentDB](https://content.minetest.net) and add your content.
Make sure to read the guidance given in the Help section.
## Forum Topic
You can now create a forum topic. You should create it in
the ["WIP Mods"](https://forum.minetest.net/viewforum.php?f=9) (Work In Progress)
forum.\\
When you consider your mod no longer a work in progress, you can
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.
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."
### Content
The requirements of a forum topic are mostly the same as the recommendations for
a readme file. The topic should include:
* What the mod does.
* What the license is.
* Current version of mod.
* How to install the mod.
* What dependencies there are.
* Where to report problems/bugs or get help.
* Link to download, or an attachment.
You should also include screenshots of your mod in action, if relevant.
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]http://wiki.minetest.com/wiki/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 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 subject of topic must be in one of these formats:

View File

@ -2,30 +2,31 @@
title: Security
layout: default
root: ../..
idx: 7.25
idx: 8.3
---
## Introduction
## Introduction <!-- omit in toc -->
Security is very important in making sure that your mod doesn't cause the server
owner to lose data or control.
* [Core Concepts](#core-concepts)
* [Formspecs](#formspecs)
* [Never Trust Submissions](#never-trust-submissions)
* [Time of Check isn't Time of Use](#time_of_check_isnt_time_of_use)
* [(Insecure) Environments](#insecure-environments)
- [Core Concepts](#core-concepts)
- [Formspecs](#formspecs)
- [Never Trust Submissions](#never-trust-submissions)
- [Time of Check isn't Time of Use](#time-of-check-isnt-time-of-use)
- [(Insecure) Environments](#insecure-environments)
## Core Concepts
The most important concept in security is to **never trust the user**.
Anything the user submits should be treated as malicious.
This means that you should always check that the user has the correct permissions,
that the give valid information, and they are otherwise allowed to do that action
(ie: in range or an owner)
This means that you should always check that the information they
enter is valid, that the user has the correct permissions,
and that they are otherwise allowed to do that action
(ie: in range or an owner).
A malicious action isn't necessarily the modification or destruction of data,
but can be accessing data they're not supposed to such as password hashes or
but can be accessing sensitive data, such as password hashes or
private messages.
This is especially bad if the server stores information such as emails or ages,
which some may do for verification purposes.
@ -39,14 +40,8 @@ Any users can submit almost any formspec with any values at any time.
Here's some real code found in a mod:
```lua
minetest.register_on_player_receive_fields(function(player,
core.register_on_player_receive_fields(function(player,
formname, fields)
-- Todo: fix security issue here
local name = player:get_player_name()
if formname ~= "mymod:fs" then
return
end
for key, field in pairs(fields) do
local x,y,z = string.match(key,
"goto_([%d-]+)_([%d-]+)_([%d-]+)")
@ -59,14 +54,14 @@ minetest.register_on_player_receive_fields(function(player,
end
```
Can you spot the issue? A malicious user could submit a formspec containing
Can you spot the problem? A malicious user could submit a formspec containing
their own position values, allowing them to teleport to anywhere they wish to.
This could even be automated using client modifications to essentially replicate
the `/teleport` command with no need for a privilege.
The solution for this kind of issue is to use a
[Context](../players/formspecs.html#contexts), as shown in
the formspecs chapter.
[Context](../players/formspecs.html#contexts), as shown previously in
the Formspecs chapter.
### Time of Check isn't Time of Use
@ -77,8 +72,8 @@ engine forbids it:
* From 5.0 onward, named formspecs will be blocked if they haven't been shown yet.
This means that you should check in the handler that the user meets the
conditions for showing the formspec in the first place, and any corresponding
actions.
conditions for showing the formspec in the first place, as well as any
corresponding actions.
The vulnerability caused by checking for permissions in the show formspec but not
in the handle formspec is called Time Of Check is not Time Of Use (TOCTOU).
@ -92,20 +87,20 @@ to the full Lua API.
Can you spot the vulnerability in the following?
```lua
local ie = minetest.request_insecure_environment()
local ie = core.request_insecure_environment()
ie.os.execute(("path/to/prog %d"):format(3))
```
`String.format` is a function in the global shared table `String`.
`string.format` is a function in the global shared table `string`.
A malicious mod could override this function and pass stuff to os.execute:
```lua
String.format = function()
string.format = function()
return "xdg-open 'http://example.com'"
end
```
The mod could pass something a lot more malicious than opening a website, such
The mod could pass something much more malicious than opening a website, such
as giving a remote user control over the machine.
Some rules for using an insecure environment:

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

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

View File

@ -2,31 +2,33 @@
title: Automatic Unit Testing
layout: default
root: ../..
idx: 7.4
idx: 8.5
---
## Introduction
## Introduction <!-- omit in toc -->
Unit tests are an essential tool in proving and reassuring yourself that your code
is correct. This chapter will show you how to write tests for Minetest mods and
games using busted. Writing unit tests for functions where you call Minetest
functions is quite difficult, but luckily [in the previous chapter](clean_arch.html)
we discussed how to make your code avoid this.
games using Busted. Writing unit tests for functions where you call Minetest
functions is quite difficult, but luckily [in the previous chapter](clean_arch.html),
we discussed how to structure your code avoid this.
* [Installing Busted](#installing-busted)
* [Your First Test](#your-first-test)
* [Mocking: Using External Functions](#mocking-using-external-functions)
* [Checking Commits with Travis](#checking-commits-with-travis)
* [Conclusion](#conclusion)
- [Installing Busted](#installing-busted)
- [Your First Test](#your-first-test)
- [init.lua](#initlua)
- [api.lua](#apilua)
- [tests/api_spec.lua](#testsapi_speclua)
- [Mocking: Using External Functions](#mocking-using-external-functions)
- [Conclusion](#conclusion)
## Installing Busted
First you'll need to install LuaRocks.
First, you'll need to install LuaRocks.
* Windows: Follow the [installation instructions on LuaRock's wiki](https://github.com/luarocks/luarocks/wiki/Installation-instructions-for-Windows).
* Debian/Ubuntu Linux: `sudo apt install luarocks`
Next you should then install Busted globally:
Next, you should install Busted globally:
sudo luarocks install busted
@ -52,7 +54,7 @@ names ending in `_spec`, and then executes them in a standalone Lua environment.
```lua
mymod = {}
dofile(minetest.get_modpath("mymod") .. "/api.lua")
dofile(core.get_modpath("mymod") .. "/api.lua")
```
@ -83,7 +85,7 @@ describe("add", function()
end)
it("supports negatives", function()
assert.equals(0, mymod.add(-1, 1))
assert.equals(0, mymod.add(-1, 1))
assert.equals(-2, mymod.add(-1, -1))
end)
end)
@ -104,13 +106,13 @@ functions not inside of it. You tend to only write tests for a single file at on
## Mocking: Using External Functions
Mocking is the practice of replacing functions that the thing you're testing depends
on. This can have two purposes - firstly, the function may not be available in the
test environment. Secondly, you may want to capture calls to the function and any
on. This can have two purposes; one, the function may not be available in the
test environment, and two, you may want to capture calls to the function and any
passed arguments.
If you follow the advice in the [Clean Architectures](clean_arch.html) chapter,
you'll already have a pretty clean file to test. You will still have to mock
things not in your area however - for example, you'll have to mock the view when
things not in your area, however - for example, you'll have to mock the view when
testing the controller/API. If you didn't follow the advice, then things are a
little harder as you may have to mock the Minetest API.
@ -120,7 +122,7 @@ _G.minetest = {}
-- Define the mock function
local chat_send_all_calls = {}
function minetest.chat_send_all(name, message)
function core.chat_send_all(name, message)
table.insert(chat_send_all_calls, { name = name, message = message })
end
@ -161,28 +163,6 @@ end)
```
## Checking Commits with Travis
The Travis script from the [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 --no-color .
- $HOME/.luarocks/bin/busted .
notifications:
email: false
```
## Conclusion
Unit tests will greatly increase the quality and reliability of your project if used

View File

@ -4,7 +4,7 @@
{% assign notice=include %}
{% endif %}
<div class="notice notice-{{ notice.level }}">
<div class="notice notice-{{ notice.level }} {{ notice.classes }}">
{% if notice.level == "warning" %}
<span></span>
{% else if notice.level == "tip" %}
@ -16,6 +16,6 @@
{% if notice.title %}
<h2>{{ notice.title }}</h2>
{% endif %}
{{ notice.message }}
{{ notice.message | markdownify }}
</div>
</div>

209
_it/advmap/biomesdeco.md Executable file
View File

@ -0,0 +1,209 @@
---
title: Biomi e decorazioni
author: Shara
layout: default
root: ../..
idx: 6.1
description: Crea biomi e decorazioni per personalizzare la mappa
---
## Introduzione <!-- omit in toc -->
L'abilità di registrare biomi e decorazioni è vitale quando si vuole creare un ambiente di gioco variegato e interessante.
Questo capitolo mostra come registrare nuovi biomi, come controllarne la distribuzione, e come aggiungerci decorazioni.
- [Cosa sono i biomi?](#cosa-sono-i-biomi)
- [Collocare un bioma](#collocare-un-bioma)
- [Calore e umidità](#calore-e-umidità)
- [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)
- [Cosa sono le decorazioni?](#cosa-sono-le-decorazioni)
- [Registrare una decorazione semplice](#registrare-una-decorazione-semplice)
- [Registrare una decorazione composta (schematic)](#registrare-una-decorazione-composta-schematic)
- [Alias del generatore mappa](#alias-del-generatore-mappa)
## Cosa sono i biomi?
In Minetest, un bioma è un ambiente di gioco specifico. Quando viene registrato, se ne possono determinare i vari tipi di nodi che vi appariranno durante la generazione della mappa.
Alcuni dei tipi più comuni - che possono variare da bioma a bioma - sono:
* Nodo superficie: il nodo che si ha più probabilità di trovare sulla superficie.
Un esempio noto ai più è "Dirt with Grass" in Minetest Game.
* Nodo riempitivo: il livello immediatamente sotto al precedente.
Nei biomi con l'erba, corrisponde solitamente alla terra.
* Nodo di pietra: il nodo che si ha più probabilità di trovare sottoterra.
* Nodo d'acqua: è solitamente un liquido, ed è il nodo che appare dove ci si aspetterebbe di trovare masse d'acqua.
Si possono incontrare anche altri tipi di nodi tra i biomi, dando la possibilità di creare ambienti altamente variegati all'interno dello stesso gioco.
## Collocare un bioma
### Calore e umidità
Non è sufficiente registrare un bioma; bisogna anche decidere dove deve apparire.
Per farlo, si assegna un valore di calore e umidità a ognuno di essi.
Dovresti pensarci bene prima di inserire questi valori: essi determinano quali biomi
possono confinare tra di loro.
Decisioni frettolose potrebbero risultare in un torrido deserto che condivide i suoi confini con un ghiacciaio, e altre improbabili combinazioni che potresti voler evitare.
In gioco, calore e umidità vanno da un minimo di 0 a un massimo di 100.
Questi valori cambiano gradualmente, aumentando o diminuendo man mano che ci si sposta per la mappa.
Quale bioma apparirà viene determinato prendendo il bioma registrato che ha i valori di calore e umidità più simili a quel punto della mappa.
Dato che i cambiamenti di calore e umidità sono graduali, è buona norma assegnare questi valori ai biomi basandosi su cosa ci si aspetta realisticamente di trovare in un determinato bioma.
Per esempio:
* Un deserto potrebbe avere alte temperature e poca umidità;
* Una foresta innevata potrebbe avere basse temperature e un'umidità moderata;
* Una palude ha senso se ha un'umidità elevata
Così facendo, questo significa che, finché si hanno più biomi, sarà più probabile trovare biomi confinanti che seguono una progressione logica.
### Visualizzare i confini usando i diagrammi di Voronoi
<figure class="right_image">
<img src="{{ page.root }}/static/biomes_voronoi.png" alt="Voronoi">
<figcaption>
Diagramma di Voronoi che mostra il punto più vicino.
<span class="credit">Di <a href="https://en.wikipedia.org/wiki/Voronoi_diagram#/media/File:Euclidean_Voronoi_diagram.svg">Balu Ertl</a>, CC BY-SA 4.0.</span>
</figcaption>
</figure>
Regolare i valori di calore e umidità risulta più facile se si riesce a visualizzare come i biomi entrano in relazione l'un con l'altro.
Questo è importante soprattutto se si sta creando un set completo di nuovi biomi personalizzati, ma può essere utile anche quando se ne vuole aggiungere soltanto uno a un set già predefinito.
Il modo più semplice per vedere quali biomi potrebbero condividere un confine è creare un diagramma di Voronoi, che può essere usato per mostrare in un diagramma 2D quali sono, date più posizioni (in nero nell'immagine), i punti nello spazio a loro più vicini (i bordi delle aree colorate).
Un diagramma di Voronoi è utile sia per rivelare eventuali accoppiamenti non desiderati che per dar un'idea generale della distribuzione dei biomi.
Per far ciò, viene segnato un punto per ogni bioma basandosi sui valori di calore e umidità, dove l'asse X è il calore e l'asse Y l'umidità.
Il diagramma è poi suddiviso in aree, in modo che ogni posizione in un'area specifica sia più vicina a un punto che a tutti gli altri.
Ogni area rappresenta un bioma. Se due aree condividono un confine, i biomi a loro associati possono essere trovati a confinare in gioco.
La lunghezza del confine condiviso tra due aree, comparata alla lunghezza condivisa con le altre, ti dirà quanto frequentemente due biomi sono propensi a essere trovati vicini.
### Creare un diagramma di Voronoi usando Geogebra
Oltre che farli a mano, per creare dei diagrammi di Voronoi si possono usare programmi come [Geogebra](https://www.geogebra.org/calculator).
1. Crea dei punti selezionando lo strumento per i punti dall'apposita interfaccia (l'icona del punto con la A) e cliccando per il piano.
Puoi trascinare i punti dove vuoi o impostare la loro posizione dal menù laterale a sinistra.
Dovresti anche rinominare ogni punto per rendere il tutto più chiaro.
2. Poi, crea il voronoi inserendo la seguente funzione nel menù laterale a sinistra, sotto i punti:
```cpp
Voronoi({ A, B, C, D, E })
```
Dove ogni punto è contenuto nelle graffe, separato da virgole.
3. Tadaan! Dovresti ora avere un diagramma di Voronoi con tutti i punti trascinabili.
## Registrare un bioma
Il seguente codice registra un semplice bioma chiamato "distesa_erbosa":
```lua
core.register_biome({
name = "distesa_erbosa",
node_top = "default:dirt_with_grass",
depth_top = 1,
node_filler = "default:dirt",
depth_filler = 3,
y_max = 1000,
y_min = -3,
heat_point = 50,
humidity_point = 50,
})
```
Questo bioma ha uno strato di "Dirt with Grass" sulla superficie, e tre strati di terra al di sotto.
Non specifica tuttavia un nodo di pietra, quindi il nodo definito nella registrazione dell'alias del generatore della mappa (*mapgen*) in `mapgen_stone` sarà presente sotto la terra.
Ci sono molte opzioni da personalizzare quando si registra un bioma, e le si possono trovare documentate nella [API](https://minetest.gitlab.io/minetest/definition-tables/#biome-definition) come al solito.
Non c'è bisogno di definire tutte le opzioni ogni volta che si crea un bioma, seppur in certi casi il dimenticarsi un'opzione specifica o un'alias di generazione della mappa appropriato porti a deli errori nella generazione.
## Cosa sono le decorazioni?
Le decorazioni sono o dei nodi o degli insiemi di nodi (*schematic*) che possono essere piazzati nella mappa durante la generazione.
Alcuni esempi comuni sono i fiori, i cespugli e gli alberi.
Altri usi più creativi possono includere stalattiti e stalagmiti nelle grotte, formazione di cristalli sottoterra o addirittura la collocazione di piccoli edifici.
Le decorazioni possono essere limitate a biomi o ad altezze specifiche, o ancora a determinati nodi.
Sono spesso usate per sviluppare l'atmosfera di un bioma, inserendo piante, alberi o altre caratteristiche che lo rendono particolare.
## Registrare una decorazione semplice
Le decorazioni semplici sono usate per piazzare un singolo nodo nella mappa durante la generazione.
Ricordati che devi specificare il nodo che vuoi usare in quanto decorazione, i dettagli di dove può essere piazzato, e quanto di frequente deve apparire.
Per esempio:
```lua
core.register_decoration({
deco_type = "simple",
place_on = {"base:dirt_with_grass"},
sidelen = 16,
fill_ratio = 0.1,
biomes = {"distesa_erbosa"},
y_max = 200,
y_min = 1,
decoration = "piante:erba",
})
```
In questo caso, il nodo chiamato `piante:erba` verrà piazzato nel bioma "distesa_erbosa" in cima ai nodi a mo' di prato (`base:dirt_with_grass`) tra altitudine `y = 1` e `y = 20`.
Il valore `fill_ratio` determina quanto di frequente dovrà apparire, con valori più alti di 1 equivalenti a un grande numero di decorazioni piazzate.
È possibile, sennò, usare i parametri di disturbo (*noise parameters*) per determinare la collocazione.
## Registrare una decorazione composta (schematic)
Le schematic sono molto simili alle decorazioni semplici, solo che piazzano più nodi invece che uno solo.
Per esempio:
```lua
core.register_decoration({
deco_type = "schematic",
place_on = {"base:desert_sand"},
sidelen = 16,
fill_ratio = 0.0001,
biomes = {"desert"},
y_max = 200,
y_min = 1,
schematic = core.get_modpath("plants") .. "/schematics/cactus.mts",
flags = "place_center_x, place_center_z",
rotation = "random",
})
```
In quest'esempio, viene piazzata la schematic cactus.mts nel bioma del deserto.
C'è bisogno di fornire il percorso nel quale andare a pescare il file, che in questo caso si trova in una cartella chiamata "schematics" all'interno della mod.
Sempre nell'esempio, inoltre, vengono impostati i contrassegni (le *flag*) per centrare il posizionamento della schematic, e la rotazione è impostata randomicamente.
Quest'ultima opzione agevola l'introduzione di una maggior variazione quando vengono usate schematic asimmetriche.
## Alias del generatore mappa
I giochi disponibili dovrebbero già includere un alias del generatore mappa (*mapgen*) adeguato, quindi devi solo prendere in considerazione se registrarne di personali alla creazione di un nuovo gioco.
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")
```
Almeno almeno dovresti registrare:
* mapgen_stone
* mapgen_water_source
* mapgen_river_water_source
Se non stai definendo nodi liquidi per le caverne di tutti i biomi, dovresti aggiungere anche:
* mapgen_lava_source

186
_it/advmap/lvm.md Executable file
View File

@ -0,0 +1,186 @@
---
title: Manipolatori di voxel Lua
layout: default
root: ../..
idx: 6.2
description: Impara come usare gli LVM per accelerare le operazioni nella mappa.
redirect_from:
- /it/chapters/lvm.html
- /it/map/lvm.html
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")`.
---
## 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.
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.
- [Concetti](#concetti)
- [Lettura negli LVM](#lettura-negli-lvm)
- [Lettura dei nodi](#lettura-dei-nodi)
- [Scrittura dei nodi](#scrittura-dei-nodi)
- [Esempio](#esempio)
- [Il tuo turno](#il-tuo-turno)
## Concetti
Un LVM permette di caricare grandi pezzi di mappa nella memoria della mod che ne ha bisogno.
Da lì si possono leggere e modificare i dati immagazzinati senza dover interagire ulteriormente col motore di gioco, e senza eseguire callback; in altre parole, l'operazione risulta molto più veloce.
Una volta fatto ciò, si può passare l'area modificata al motore di gioco ed eseguire eventuali calcoli riguardo la luce.
## Lettura negli LVM
Si possono caricare solamente aree cubiche negli LVM, quindi devi capire da te quali sono le posizioni minime e massime che ti servono per l'area da modificare.
Fatto ciò, puoi creare l'LVM:
```lua
local vm = core.get_voxel_manip()
local emin, emax = vm:read_from_map(pos1, pos2)
```
Per questioni di performance, un LVM non leggerà quasi mai l'area esatta che gli è stata passata.
Al contrario, è molto probabile che ne leggerà una maggiore. Quest'ultima è data da `emin` ed `emax`, che stanno per posizione minima/massima emersa (*emerged min/max pos*).
Inoltre, un LVM caricherà in automatico l'area passatagli - che sia da memoria, da disco o dal generatore di mappa.
{% include notice.html notice=page.mapgen_object %}
## Lettura dei nodi
Per leggere il tipo dei nodi in posizioni specifiche, avrai bisogno di usare `get_data()`.
Questo metodo ritorna un array monodimensionale dove ogni voce rappresenta il tipo.
```lua
local data = vm:get_data()
```
Si possono ottenere param2 e i dati della luce usando i metodi `get_light_data()` e `get_param2_data()`.
Avrai bisogno di usare `emin` e `emax` per capire dove si trova un nodo nei metodi sopraelencati.
C'è una classe di supporto per queste cose chiamate `VoxelArea` che gestisce i calcoli al posto tuo.
```lua
local a = VoxelArea:new{
MinEdge = emin,
MaxEdge = emax
}
-- Ottiene l'indice del nodo
local idx = a:index(x, y, z)
-- Legge il nodo
print(data[idx])
```
All'eseguire ciò, si noterà che `data[idx]` è un intero.
Questo perché il motore di gioco non salva i nodi come stringhe per motivi di performance; al contrario, usa un intero chiamato "ID di contenuto" (*content ID*).
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")
```
Si può ora controllare se un nodo è effettivamente di pietra:
```lua
local idx = a:index(x, y, z)
if data[idx] == c_pietra then
print("è pietra!")
end
```
Gli ID di contenuto di un nodo potrebbero cambiare durante la fase di caricamento, quindi è consigliato non tentare di ottenerli durante tale fase.
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:
```lua
for z = min.z, max.z do
for y = min.y, max.y do
for x = min.x, max.x do
local idx = a:index(x, y, z)
if data[idx] == c_pietra then
print("è pietra!")
end
end
end
end
```
Per capire la ragione di tale iterazione, bisogna parlare un attimo di architettura dei computer: leggere dalla RAM - la memoria principale - è alquanto dispendioso, quindi i processori hanno molteplici livelli di memoria a breve termine (la *cache*).
Se i dati richiesti da un processo sono in quest'ultima memoria, si possono ottenere velocemente.
Al contrario, se i dati lì non ci sono, verranno pescati dalla RAM *e* inseriti in quella a breve termine, nel caso dovessero servire di nuovo.
Questo significa che una buona regola per l'ottimizzazione è quella di iterare in modo che i dati vengano letti in sequenza, evitando di arrivare fino alla RAM ogni volta (*cache thrashing*).
## Scrittura dei nodi
Prima di tutto, bisogna impostare il nuovo ID nell'array:
```lua
for z = min.z, max.z do
for y = min.y, max.y do
for x = min.x, max.x do
local idx = a:index(x, y, z)
if data[idx] == c_pietra then
data[idx] = c_aria
end
end
end
end
```
Una volta finito con le operazioni nell'LVM, bisogna passare l'array al motore di gioco:
```lua
vm:set_data(data)
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`.
## Esempio
```lua
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 emin, emax = vm:read_from_map(pos1, pos2)
local a = VoxelArea:new{
MinEdge = emin,
MaxEdge = emax
}
local data = vm:get_data()
-- modifica i dati
for z = pos1.z, pos2.z do
for y = pos1.y, pos2.y do
for x = pos1.x, pos2.x do
local idx = a:index(x, y, z)
if data[idx] == c_erba then
data[idx] = c_terra
end
end
end
end
-- scrive i dati
vm:set_data(data)
vm:write_to_map(true)
end
```
## Il tuo turno
* Crea una funzione `rimpiazza_in_area(da, a, pos1, pos2)`, che sostituisce tutte le istanze di `da` con `a` nell'area data, dove `da` e `a` sono i nomi dei nodi;
* Crea una funzione che ruota tutte le casse di 90&deg;;
* Crea una funzione che usa un LVM per far espandere i nodi di muschio sui nodi di pietra e pietrisco confinanti.
La tua implementazione fa espandere il muschio di più di un blocco alla volta? Se sì, come puoi prevenire ciò?

View File

@ -0,0 +1,169 @@
---
title: Per iniziare
layout: default
root: ../..
idx: 1.1
description: Impara come si crea la cartella di una mod, un file init.lua, mod.conf e altro.
redirect_from:
- /it/chapters/folders.html
- /it/basics/folders.html
---
## Introduzione <!-- omit in toc -->
Capire la struttura base della cartella di una mod è un requisito essenziale per creare qualsivoglia contenuto.
- [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)
- [Pacchetti mod](#pacchetti-mod-mod-pack)
- [Esempio](#esempio)
- [Cartella mod](#cartella-mod-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.
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.
È 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.
Indipendentemente da come le si voglia usare (specifiche per un gioco o come estensioni generiche) 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.
## 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*).
Per un dato mondo/salvataggio, vengono controllati tre percorsi.
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.
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.
La posizione di ogni percorso dipende da quale sistema operativo si sta usando, e da 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.
* **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.
* Per le installazioni portatili, guarda nella cartella di build.
* Per installazioni Flatpak, guarda in `~/.var/app/net.minetest.Minetest/.minetest/mods/`.
* **MacOS**
* Guarda in `~/Library/Application Support/minetest/`.
Attenzione che `~` equivale alla cartella home dell'utente, per esempio `/Users/USERNAME/`.
## Cartella mod
![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
[content.minetest.net](https://content.minetest.net).
lamiamod
├── init.lua (necessario) - Viene eseguito al lancio del gioco.
├── mod.conf (consigliato) - Contiene la descrizione e le dipendneze.
├── textures (opzionale)
│   └── ... qualsiasi texture o immagine
├── sounds (opzionale)
│   └── ... 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.
## 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
### 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.
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à.
Le dipendenze sono specificate in un elenco separato da virgole in mod.conf.
depends = mod1, mod2
optional_depends = mod3
## 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.
pacchettomod1
├── modpack.lua (necessario) - segnala che è un pacchetto mod
├── mod1
│   └── ... file mod
└── 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.
## Esempio
Segue un esempio che mette insieme tutto ciò discusso finora:
### Cartella mod
lamiamod
├── textures
│   └── lamiamod_nodo.png
├── init.lua
└── mod.conf
### init.lua
```lua
print("Questo file parte all'avvio!")
core.register_node("lamiamod:nodo", {
description = "Questo è un nodo",
tiles = {"lamiamod_nodo.png"},
groups = {cracky = 1}
})
```
### mod.conf
name = lamiamod
descriptions = Aggiunge un nodo
depends = default
Questa mod ha come nome "lamiamod". Ha due file di testo: init.lua e mod.conf.\\
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.\\
C'è anche una texture in textures/ per il nodo.

176
_it/basics/lua.md Normal file
View File

@ -0,0 +1,176 @@
---
title: Programmare in Lua
layout: default
root: ../..
idx: 1.2
description: Un'introduzione a Lua, con inclusa una guida alla portata globale/locale.
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.
- [Programmare](#programmare)
- [Programmare in Lua](#programmare-in-lua)
- [Editor di codice](#editor-di-codice)
- [Portata locale e globale](#portata-locale-e-globale)
- [Precedenza alla portata locale](#precedenza-alla-portata-locale)
- [Inclusione di altri script Lua](#inclusione-di-altri-script-lua)
## Programmare
Programmare è l'azione di prendere un problema, come ordinare una lista di oggetti, e tramutarlo in dei passaggi che il computer può comprendere.
Insegnarti i processi logici della programmazione non rientra nell'ambito di questo libro; tuttavia, i seguenti siti sono alquanto utili per approfondire l'argomento:
* [Codecademy](http://www.codecademy.com/) è una delle migliori risorse per imparare come scrivere codice; offre un'esperienza guidata interattiva.
* [Scratch](https://scratch.mit.edu) è una buona risorsa quando si comincia dalle basi assolute, imparando le tecniche di problem solving necessarie per la programmazione.\\
Scratch è *ideato per insegnare ai bambini* e non è un linguaggio serio di programmazione.
* [Programming with Mosh](https://www.youtube.com/user/programmingwithmosh) is
a good YouTube series to learn programming.
### Programmare in Lua
Neanche insegnarti come programmare in lua rientra nell'ambito di questo libro.
Tuttavia, se mastichi l'inglese puoi rifarti a quest'altro libro, ["Programming in Lua"](https://www.lua.org/pil/contents.html), per un'eccellente infarinatura sull'argomento. Se invece l'inglese non è il tuo forte, troverai comunque svariate guide in italiano in giro per la rete.
## Editor di codice
Un editor di codice con evidenziamento delle parole chiave è sufficiente per scrivere script in Lua.
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:
```lua
function ctf.post(team,msg)
if not ctf.team(team) then
return false
end
if not ctf.team(team).log then
ctf.team(team).log = {}
end
table.insert(ctf.team(team).log,1,msg)
ctf.save()
return true
end
```
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`.
Tra gli editor più famosi che ben si prestano a lavorare in Lua, troviamo:
* [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
(ne esistono ovviamente anche altri)
## Portata locale e globale
L'essere locale o globale di una variabile determina da dove è possibile accederci.
Una variabile locale è accessibile soltanto da dove viene definita. Ecco alcuni esempi:
```lua
-- Accessibile dall'interno dello script
local one = 1
function myfunc()
-- Accessibile dall'interno della funzione
local two = one + one
if two == one then
-- Accessible dall'interno del costrutto if
local three = one + two
end
end
```
Mentre le variabili globali sono accessibili da qualsiasi script di qualsiasi mod.
```lua
function one()
foo = "bar"
end
function two()
print(dump(foo)) -- Output: "bar"
end
one()
two()
```
### Precedenza alla portata locale
Le variabili locali dovrebbero venire usate il più possibile, con le mod che creano al massimo una globale corrispondente al nome della mod.
Crearne di ulteriori è considerato cattiva programmazione, e Minetest ci avviserà di ciò:
Assignment to undeclared global 'foo' inside function at init.lua:2
Per ovviare, usa `local`:
```lua
function one()
local foo = "bar"
end
function two()
print(dump(foo)) -- Output: nil
end
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`)
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.
```lua
local function foo(bar)
return bar * 2
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.
```lua
mymod = {}
function mymod.foo(bar)
return "foo" .. bar
end
-- In un'altra mod o script:
mymod.foo("foobar")
```
## Inclusione di altri script Lua
Il metodo consigliato per includere in una mod altri script Lua è usare *dofile*.
```lua
dofile(core.get_modpath("modname") .. "/script.lua")
```
Uno script può ritornare un valore, che è utile per condividere variabili locali private:
```lua
-- script.lua
return "Hello world!"
-- init.lua
local ret = dofile(core.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.

75
_it/games/games.md Normal file
View File

@ -0,0 +1,75 @@
---
title: Creare giochi
layout: default
root: ../..
idx: 7.1
---
## Introduzione <!-- omit in toc -->
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)
- [Cartella di un gioco](#cartella-di-un-gioco)
- [Compatibilità tra giochi](#compatibilità-tra-giochi)
- [Compatibilità delle API](#compatibilità-delle-api)
- [Gruppi e alias](#gruppi-e-alias)
- [Il tuo turno](#il-tuo-turno)
## Cos'è un gioco?
I giochi sono una collezione di mod che lavorano insieme per creare un gioco coerente.
Un buon gioco ha una base consistente e una direzione: per esempio, potrebbe essere il classico gioco survival dove picconare e fabbricare oggetti, come potrebbe essere un simulatore spaziale con estetiche steampunk.
Il design di un gioco, tuttavia, è un argomento complesso, tanto che è una branca di specializzazione a parte.
L'intento del libro è giusto farne un accenno.
## Cartella di un gioco
La struttura e la collocazione di un gioco dovrebbero sembrare alquanto familiari dopo aver pasticciato con le mod.
Le cartelle dei giochi si trovano in `minetest/games/` e sono strutturate come segue:
mio_gioco
├── game.conf
├── menu
│   ├── header.png
│   ├── background.png
│   └── icon.png
├── minetest.conf
├── mods
│   └── ... mods
├── README.txt
└── settingtypes.txt
L'unica cosa necessaria è la cartella `mods`, ma è raccomandato anche l'inserimento di `game.conf` e `menu/icon.png`.
## Compatibilità tra giochi
### Compatibilità delle API
È buona norma provare a mantenere le API compatibili con quelle di Minetest Game quanto possibile, in quanto renderebbe il porting delle mod (in un altro gioco) molto più semplice.
Il modo migliore per mantenere la compatibilità tra un gioco e l'altro è di mantenere la stessa compatibilità nelle API delle mod che hanno lo stesso nome.
Cosicché, se una mod usa lo stesso nome di un'altra (come fare una mod chiamata `doors`, che già esiste in Minetest Game), non ci saranno problemi.
Questa compatibilità per le mod si traduce in due punti:
* Tabella API Lua - tutte le funzioni nella tabella globale (`mia_mod.funzionivarie`) che condividono lo stesso nome;
* Nodi e oggetti registrati.
Eventuali piccole rotture non sono la fine del mondo (come non avere una funzione che tanto veniva usata solo internamente), ma quando saltano le funzioni principali è un altro paio di maniche.
È difficile mantenere queste compatibilità con modpack disgustatamente grosse come la *default* in Minetest Game, dacché si dovrebbe evitare di chiamare una mod "default".
Infine, le compatibilità delle API si applicano anche a mod e giochi esterni, quindi assicurati che una mod nuova abbia un nome unico.
Per controllare se un nome è già stato preso, prova a cercarlo su [content.minetest.net](https://content.minetest.net/).
### Gruppi e alias
I gruppi e gli alias sono entrambi strumenti utili per mantenere la compatibilità tra giochi, in quanto permettono ai nomi degli oggetti di variare a seconda del gioco.
Nodi comuni come pietra e legno dovrebbero avere dei gruppi per indicarne il materiale.
È anche buona norma fornire degli alias che vanno dai nodi di base a qualsiasi eventuale rimpiazzo.
## Il tuo turno
* Crea un semplice gioco dove il giocatore guadagna punti allo scavare alcuni nodi speciali.

35
_it/index.md Normal file
View File

@ -0,0 +1,35 @@
---
title: Copertina
description: An easy guide to learn how to create mods for Minetest
layout: default
homepage: true
no_header: true
root: ..
idx: 0.1
---
<header>
<h1>Minetest: Libro del Moddaggio</h1>
<span>di <a href="https://rubenwardy.com" rel="author">rubenwardy</a></span>
<span>con modifiche di <a href="http://rc.minetest.tv/">Shara</a></span>
<span>traduzione italiana di <a href="https://liberapay.com/Zughy/">Zughy</a></span>
</header>
## 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.
Oltre che [leggere questo libro su internet](https://rubenwardy.com/minetest_modding_book),
puoi anche [scaricarlo in HTML](https://github.com/rubenwardy/minetest_modding_book/releases).
### Riscontri e Contributi
Hai notato un errore o vuoi dirmi la tua? 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).
* [Contattami (in inglese)](https://rubenwardy.com/contact/).
* Voglia di contribuire?
[Leggi il README](https://gitlab.com/rubenwardy/minetest_modding_book/-/blob/master/README.md).

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

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

View File

@ -0,0 +1,72 @@
---
title: Creare le texture
layout: default
root: ../..
idx: 2.2
description: Un'introduzione sul come creare texture nel tuo editor di fiducia, e una guida a GIMP.
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.
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)
- [Usare la matita](#usare-la-matita)
- [Piastrellatura (tiling)](#piastrellatura-tiling)
- [Trasparenza](#trasparenza)
- [Programmi](#programmi)
- [MS Paint](#ms-paint)
- [GIMP](#gimp)
## Tecniche
### 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.
### 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à.
<!-- IMAGE NEEDED - cobblestone that tiles correctly -->
Se non riesci nell'allineamento, il risultato sarà molto meno
gradevole da vedere.
<!-- IMAGE NEEDED - node that doesn't tile correctly -->
### 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.
## Programmi
### MS Paint
MS Paint è un programma di disegno davvero semplice che può rivelarsi utile
per la creazione di texture base; tuttavia, non supporta la trasparenza.
Ciò di solitò non farà differenza finché ci si limiterà alle facce di un nodo (a parte nodi come il vetro),
tuttavia se la trasparenza è un requisito nelle tue texture dovresti guardare oltre.
### 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.
Quando usi GIMP, puoi selezionare la matita dalla Barra degli Strumenti:
<figure>
<img src="{{ page.root }}//static/pixel_art_gimp_pencil.png" alt="La matita su GIMP">
</figure>
È anche consigliato spuntare l'opzione "Margine netto" per la gomma.

319
_it/items/inventories.md Executable file
View File

@ -0,0 +1,319 @@
---
title: ItemStack e inventari
layout: default
root: ../..
idx: 2.4
description: Manipola gli InvRef e gli ItemStack
redirect_from:
- /it/chapters/inventories.html
- /it/chapters/itemstacks.html
- /it/inventories/inventories.html
- /it/inventories/itemstacks.html
---
## 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.
- [Cosa sono gli ItemStack e gli inventari?](#cosa-sono-gli-itemstack-e-gli-inventari)
- [ItemStack](#itemstack)
- [Collocazione inventari](#collocazione-inventari)
- [Liste](#liste)
- [Dimensione e ampiezza](#dimensione-e-ampiezza)
- [Controllare il contenuto](#controllare-il-contenuto)
- [Modificare inventari e ItemStack](#modificare-inventari-e-itemstack)
- [Aggiungere a una lista](#aggiungere-a-una-lista)
- [Rimuovere oggetti](#rimuovere-oggetti)
- [Manipolazione pile](#manipolazione-pile)
- [Usura](#usura)
- [Tabelle Lua](#tabelle-lua)
## Cosa sono gli ItemStack e gli inventari?
Un ItemStack ( lett. "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.
## 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.
```lua
print(stack:get_name())
stack:set_name("default:dirt")
if not stack:is_known() then
print("È un oggetto sconosciuto!")
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.
```lua
print(stack:get_stack_max())
```
Un ItemStack può essere vuoto, nel qual caso avrà come quantità 0.
```lua
print(stack:get_count())
stack:set_count(10)
```
Gli ItemStack possono poi essere creati in diversi modi usando l'omonima funzione.
```lua
ItemStack() -- name="", count=0
ItemStack("default:pick_stone") -- count=1
ItemStack("default:stone 30")
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.
Questo sarà trattato più in dettaglio nel capitolo Storaggio e Metadati.
## Collocazione inventari
La collocazione di un inventario è dove e come un inventario viene conservato.
Ci sono tre tipi di collocazione: giocatore, nodo e separata.
Un inventario è direttamente legato a una e a una sola collocazione.
Gli inventari collocati nei nodi sono associati alle coordinate di un nodo specifico, come le casse.
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} })
```
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.
In questo modo, modificando "inv", stiamo in verità modificando l'inventario.
La collocazione di tali riferimenti può essere ottenuta nel seguente modo:
```lua
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.
```lua
local inv = core.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.
```lua
local inv = core.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")
```
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", {
allow_move = function(inv, from_list, from_index, to_list, to_index, count, player)
return count -- permette di spostare gli oggetti
end,
allow_put = function(inv, listname, index, stack, player)
return stack:get_count() -- permette di inserirli
end,
allow_take = function(inv, listname, index, stack, player)
return 0 -- non permette di rimuoverli
end,
on_put = function(inv, listname, index, stack, player)
core.chat_send_all(player:get_player_name() ..
" ha messo " .. stack:to_string() ..
" nella cassa delle donazioni da " .. core.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 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.
### 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.
```lua
if inv:set_size("main", 32) then
inv:set_width("main", 8)
print("dimensione: " .. inv.get_size("main"))
print("ampiezza: " .. inv:get_width("main"))
else
print("Errore! Nome dell'oggetto o dimensione non validi")
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.
### Controllare il contenuto
`is_empty` può essere usato per vedere se una lista contiene o meno degli oggetti:
```lua
if inv:is_empty("main") then
print("La lista è vuota!")
end
```
`contains_item` può invece essere usato per vedere se la lista contiene un oggetto specifico:
```lua
if inv:contains_item("main", "default:stone") then
print("Ho trovato della pietra!")
end
```
## Modificare inventari e ItemStack
### 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:
```lua
local stack = ItemStack("default:stone 99")
local leftover = inv:add_item("main", stack)
if leftover:get_count() > 0 then
print("L'inventario è pieno! " ..
leftover:get_count() .. " oggetti non sono stati aggiunti")
end
```
### Rimuovere oggetti
Per rimuovere oggetti da una lista, `remove_item`:
```lua
local taken = inv:remove_item("main", stack)
print("Rimossi " .. taken:get_count())
```
### Manipolare pile
Puoi modificare le singole pile prima ottenendole:
```lua
local stack = inv:get_stack(listname, 0)
```
E poi modificandole impostando le nuove proprietà o usando i metodi che rispettano `stack_size`:
```lua
local pila = ItemStack("default:stone 50")
local da_aggiungere = ItemStack("default:stone 100")
local resto = pila:add_item(da_aggiungere)
local rimossi = pila:take_item(19)
print("Impossibile aggiungere " .. resto:get_count() .. " degli oggetti.")
-- ^ sarà 51
print("Hai " .. pila:get_count() .. " oggetti")
-- ^ sarà 80
-- min(50+100, stack_max) - 19 = 80
-- dove stack_max = 99
```
`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.
Infine, si imposta la pila modificata:
```lua
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.
Il livello di usura può essere manipolato usando `add_wear()`, `get_wear()`, e `set_wear(wear)`.
```lua
local pila = ItemStack("default:pick_mese")
local usi_massimi = 10
-- Questo viene fatto in automatico quando usi uno strumento che scava cose.
-- Aumenta l'usura dell'oggetto dopo un uso
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 è.
Di conseguenza, `usi_massimi` varia a seconda di cos'è stato scavato.
## Tabelle Lua
Gli ItemStack e gli inventari possono essere convertiti in/dalle tabelle.
Questo è utile per operazioni di copiatura e immagazzinaggio.
```lua
-- Inventario intero
local data = inv1:get_lists()
inv2:set_lists(data)
-- Una lista
local listdata = inv1:get_list("main")
inv2:set_list("main", listdata)
```
La tabella di liste ritornata da `get_lists()` sarà nel seguente formato:
```lua
{
lista_uno = {
ItemStack,
ItemStack,
ItemStack,
ItemStack,
-- inv:get_size("lista_uno") elementi
},
lista_due = {
ItemStack,
ItemStack,
ItemStack,
ItemStack,
-- inv:get_size("lista_due") elementi
}
}
```
`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à:
```lua
inv:set_list("main", {})
```

413
_it/items/node_drawtypes.md Normal file
View File

@ -0,0 +1,413 @@
---
title: Tipi di nodo
layout: default
root: ../..
idx: 2.3
description: Guida su tutti i tipi di nodo, inclusi cuboidi e mesh.
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`.
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.
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.
- [Nodi cubici: normali e a facciate piene](#nodi-cubici-normali-e-a-facciate-piene)
- [Nodi vitrei](#nodi-vitrei)
- [Vitreo incorniciato](#vitreo-incorniciato)
- [Nodi d'aria](#nodi-d-aria)
- [Luce e propagazione solare](#luce-e-propagazione-solare)
- [Nodi liquidi](#nodi-liquidi)
- [Nodi complessi](#nodi-complessi)
- [Nodi complessi a muro](#nodi-complessi-a-muro)
- [Nodi mesh](#nodi-mesh)
- [Nodi insegna](#nodi-insegna)
- [Nodi pianta](#nodi-pianta)
- [Nodi fiamma](#firelike-nodes)
- [Altri drawtype](#altri-drawtype)
## Nodi cubici: normali e a facciate piene
<figure class="right_image">
<img src="{{ page.root }}//static/drawtype_normal.png" alt="Drawtype normale">
<figcaption>
Drawtype normale
</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.
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", {
description = "Diamante alieno",
tiles = {"miamod_diamante.png"},
groups = {cracky = 3},
})
core.register_node("default:foglie", {
description = "Foglie",
drawtype = "allfaces_optional",
tiles = {"default_foglie.png"}
})
```
Attenzione: il drawtype normale è quello predefinito, quindi non c'è bisogno di specificarlo ogni volta.
## 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.
<figure>
<img src="{{ page.root }}//static/drawtype_glasslike_edges.png" alt="Bordi vitrei">
<figcaption>
Bordi vitrei
</figcaption>
</figure>
```lua
core.register_node("default:obsidian_glass", {
description = "Vetro d'ossidiana",
drawtype = "glasslike",
tiles = {"default_obsidian_glass.png"},
paramtype = "light",
is_ground_content = false,
sunlight_propagates = true,
sounds = default.node_sound_glass_defaults(),
groups = {cracky=3,oddly_breakable_by_hand=3},
})
```
### Vitreo incorniciato
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">
<figcaption>
Bordi vitrei incorniciati
</figcaption>
</figure>
```lua
core.register_node("default:glass", {
description = "Vetro",
drawtype = "glasslike_framed",
tiles = {"default_glass.png", "default_glass_detail.png"},
inventory_image = core.inventorycube("default_glass.png"),
paramtype = "light",
sunlight_propagates = true, -- Sunlight can shine through block
groups = {cracky = 3, oddly_breakable_by_hand = 3},
sounds = default.node_sound_glass_defaults()
})
```
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.
```lua
core.register_node("miaaria:aria", {
description = "Mia Aria",
drawtype = "airlike",
paramtype = "light",
sunlight_propagates = true,
walkable = false, -- Il giocatore può collidere col nodo
pointable = false, -- Non è selezionabile
diggable = false, -- Non può essere scavato
buildable_to = true, -- Può essere rimpiazzato da altri nodi
-- (basta costruire nella stessa coordinata)
air_equivalent = true,
drop = "",
groups = {not_in_creative_inventory=1}
})
```
## 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.
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:
```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.
## Nodi liquidi
<figure class="right_image">
<img src="{{ page.root }}//static/drawtype_liquid.png" alt="Drawtype liquido">
<figcaption>
Drawtype liquido
</figcaption>
</figure>
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", {
drawtype = "liquid",
paramtype = "light",
inventory_image = core.inventorycube("default_water.png"),
-- ^ questo è necessario per impedire che l'immagine nell'inventario sia animata
tiles = {
{
name = "default_water_source_animated.png",
animation = {
type = "vertical_frames",
aspect_w = 16,
aspect_h = 16,
length = 2.0
}
}
},
special_tiles = {
-- Nuovo stile per il materiale dell'acqua statica (praticamente inutilizzato)
{
name = "default_water_source_animated.png",
animation = {type = "vertical_frames", aspect_w = 16,
aspect_h = 16, length = 2.0},
backface_culling = false,
}
},
--
-- Comportamento
--
walkable = false, -- Il giocatore può attraversarlo
pointable = false, -- Il giocatore non può selezionarlo
diggable = false, -- Il giocatore non può scavarlo
buildable_to = true, -- Può essere rimpiazzato da altri nodi
alpha = 160,
--
-- Proprietà del liquido
--
drowning = 1,
liquidtype = "source",
liquid_alternative_flowing = "default:water_flowing",
-- ^ quando scorre
liquid_alternative_source = "default:water_source",
-- ^ quando è sorgente (statico)
liquid_viscosity = WATER_VISC,
-- ^ quanto veloce
liquid_range = 8,
-- ^ quanto lontano
post_effect_color = {a=64, r=100, g=100, b=200},
-- ^ colore dello schermo quando il player ne è immerso
})
```
I nodi fluidi hanno una definizione simile, ma con nome e animazione differenti.
Guarda default:water_flowing nella mod default di minetest_game per un esempio completo.
## Nodi complessi
<figure class="right_image">
<img src="{{ page.root }}//static/drawtype_nodebox.gif" alt="Drawtype complesso">
<figcaption>
Drawtype complesso
</figcaption>
</figure>
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", {
drawtype = "nodebox",
paramtype = "light",
node_box = {
type = "fixed",
fixed = {
{-0.5, -0.5, -0.5, 0.5, 0, 0.5},
{-0.5, 0, 0, 0.5, 0.5, 0.5},
},
}
})
```
La parte più importante è 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.
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.
### 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.
```lua
core.register_node("default:sign_wall", {
drawtype = "nodebox",
node_box = {
type = "wallmounted",
-- Soffitto
wall_top = {
{-0.4375, 0.4375, -0.3125, 0.4375, 0.5, 0.3125}
},
-- Pavimento
wall_bottom = {
{-0.4375, -0.5, -0.3125, 0.4375, -0.4375, 0.3125}
},
-- Muro
wall_side = {
{-0.5, -0.3125, -0.4375, -0.4375, 0.3125, 0.4375}
}
},
})
```
## 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.
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", {
drawtype = "mesh",
-- Contiene le texture di ogni materiale
tiles = {
"mymod_meshy.png"
},
-- Percorso della mesh
mesh = "mymod_meshy.b3d",
})
```
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.
## Nodi insegna
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.
```lua
core.register_node("default:ladder_wood", {
drawtype = "signlike",
tiles = {"default_ladder_wood.png"},
-- Necessario: memorizza la rotazione in param2
paramtype2 = "wallmounted",
selection_box = {
type = "wallmounted",
},
})
```
## Nodi pianta
<figure class="right_image">
<img src="{{ page.root }}//static/drawtype_plantlike.png" alt="Drawtype pianta">
<figcaption>
Drawtype pianta
</figcaption>
</figure>
I nodi pianta (*plantlike*) raffigurano la loro texture in un pattern a forma di X.
```lua
core.register_node("default:papyrus", {
drawtype = "plantlike",
-- Viene usata solo una texture
tiles = {"default_papyrus.png"},
selection_box = {
type = "fixed",
fixed = {-6 / 16, -0.5, -6 / 16, 6 / 16, 0.5, 6 / 16},
},
})
```
## Nodi fiamma
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">
<figcaption>
Drawtype fiamma
</figcaption>
</figure>
```lua
core.register_node("miamod:avvinghiatutto", {
drawtype = "firelike",
-- Viene usata solo una texture
tiles = { "miamod:avvinghiatutto" },
})
```
## Altri drawtype
Questa non è una lista esaustiva, in quanto ci sono infatti altri tipi di nodi come:
* Nodi staccionata
* Nodi pianta radicata - per quelle acquatiche
* Nodi rotaia - per i binari del carrello
* Nodi torcia - per nodi 2D su pavimenti/muri/soffitti.
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.

View File

@ -0,0 +1,296 @@
---
title: Nodi, Oggetti e Fabbricazione
layout: default
root: ../..
idx: 2.1
description: Impara come registrare nodi, oggetti e ricette di fabbricazione usando register_node, register_item e register_craft.
redirect_from: /it/chapters/nodes_items_crafting.html
---
## Introduzione <!-- omit in toc -->
Saper registrare nuovi nodi, oggetti fabbricabili e conseguenti ricette, è un requisito fondamentale per molte mod.
- [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)
- [Texture](#texture)
- [Registrare un nodo base](#registrare-un-nodo-base)
- [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à)
## 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 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.
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.
## Registrare gli oggetti
Le definizioni degli oggetti consistono in un *nome oggetto* e una *tabella di definizioni*.
La tabella di definizioni contiene attributi che influenzano il comportamento dell'oggetto.
```lua
core.register_craftitem("nomemod:nomeoggetto", {
description = "Il Mio Super Oggetto",
inventory_image = "nomemod_nomeoggetto.png"
})
```
### Nomi oggetto
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
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`.
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
itemname = core.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.
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.
## Registrare un nodo base
```lua
core.register_node("miamod:diamante", {
description = "Diamante alieno",
tiles = {"miamod_diamante.png"},
is_ground_content = true,
groups = {cracky=3, stone=1}
})
```
La proprietà `tiles` è una tabella contenente le texture che il nodo userà.
Quando è presente una sola texture, questa sarà applicata su tutte le facce.
Per assegnarne invece di diverse, bisogna fornire il nome di 6 texture in quest'ordine:
sopra (+Y), sotto (-Y), destra (+X), sinistra (-X), dietro (+Z), davanti (-Z).
(+Y, -Y, +X, -X, +Z, -Z)
Ricorda che su Minetest, come nella convenzione della computer grafica 3D, +Y punta verso l'alto.
```lua
core.register_node("miamod:diamante", {
description = "Diamante alieno",
tiles = {
"miamod_diamante_up.png", -- y+
"miamod_diamante_down.png", -- y-
"miamod_diamante_right.png", -- x+
"miamod_diamante_left.png", -- x-
"miamod_diamante_back.png", -- z+
"miamod_diamante_front.png", -- z-
},
is_ground_content = true,
groups = {cracky = 3},
drop = "miamod:diamante_frammenti"
-- ^ Al posto di far cadere diamanti, fa cadere miamod:diamante_frammenti
})
```
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.
## Fabbricazione
Ci sono diversi tipi di ricette di fabbricazione disponibili, indicate dalla proprietà `type`.
* shaped - Gli ingredienti devono essere nel giusta posizione.
* shapeless - Non importa dove sono gli ingredienti, solo che siano abbastanza.
* cooking - Ricette di cottura per la fornace.
* 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.
### 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.
```lua
core.register_craft({
type = "shaped",
output = "miamod:diamante_sedia 99",
recipe = {
{"miamod:diamante_frammenti", "", ""},
{"miamod:diamante_frammenti", "miamod:diamante_frammenti", ""},
{"miamod:diamante_frammenti", "miamod:diamante_frammenti", ""}
}
})
```
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à.
Se invece la colonna non dovesse servire, basta ometterla in questo modo:
```lua
core.register_craft({
output = "miamod:diamante_sedia 99",
recipe = {
{"miamod:diamante_frammenti", "" },
{"miamod:diamante_frammenti", "miamod:diamante_frammenti"},
{"miamod:diamante_frammenti", "miamod:diamante_frammenti"}
}
})
```
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.
```lua
core.register_craft({
type = "shapeless",
output = "miamod:diamante 3",
recipe = {
"miamod:diamante_frammenti",
"miamod:diamante_frammenti",
"miamod:diamante_frammenti",
},
})
```
### 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.
```lua
core.register_craft({
type = "cooking",
output = "miamod_diamante_frammenti",
recipe = "default:coalblock",
cooktime = 10,
})
```
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.
Il tipo "carburante" invece funge da accompagnamento alle ricette di cottura, in quanto definisce cosa può alimentare il fuoco.
```lua
core.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!
## 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.
```lua
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.
```lua
core.register_craft({
type = "shapeless",
output = "miamod:diamante_qualcosa 3",
recipe = {"group:wood", "miamod:diamante"}
})
```
## Strumenti, Capacità e Friabilità
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.
| Gruppo | Miglior strumento | Descrizione |
|---------|-------------------|-------------|
| crumbly | pala | Terra, sabbia |
| cracky | piccone | Cose dure e sgretolabili come la pietra |
| snappy | *qualsiasi* | Può essere rotto usando uno strumento adatto;<br>es. foglie, piantine, filo, lastre di metallo |
| choppy | ascia | Può essere rotto con dei fendenti; es. alberi, assi di legno |
| fleshy | spada | Esseri viventi come animali e giocatori.<br>Potrebbe implicare effetti di sangue al colpire |
| explody | ? | Predisposti ad esplodere |
| 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.
```lua
core.register_tool("miamod:strumento", {
description = "Il mio strumento",
inventory_image = "miamod_strumento.png",
tool_capabilities = {
full_punch_interval = 1.5,
max_drop_level = 1,
groupcaps = {
crumbly = {
maxlevel = 2,
uses = 20,
times = { [1]=1.60, [2]=1.20, [3]=0.80 }
},
},
damage_groups = {fleshy=2},
},
})
```
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à.

211
_it/map/environment.md Normal file
View File

@ -0,0 +1,211 @@
---
title: "Mappa: operazioni base"
layout: default
root: ../..
idx: 3.1
description: Operazioni base come set_node e get_node
redirect_from: /it/chapters/environment.html
---
## Introduzione <!-- omit in toc -->
In questo capitolo imparerai come eseguire semplici azioni sulla mappa.
- [Struttura della mappa](#struttura-della-mappa)
- [Lettura](#lettura)
- [Lettura dei nodi](#lettura-dei-nodi)
- [Ricerca dei nodi](#ricerca-dei-nodi)
- [Scrittura](#scrittura)
- [Scrittura dei nodi](#scrittura-dei-nodi)
- [Rimozione dei nodi](#rimozione-dei-nodi)
- [Caricamento blocchi](#caricamento-blocchi)
- [Cancellazione blocchi](#cancellazione-blocchi)
## Struttura della mappa
La mappa di Minetest è suddivisa in Blocchi Mappa (*MapBlocks*), cubi di 16x16x16 nodi.
Man mano che i giocatori si addentrano per la mappa, i Blocchi Mappa vengono creati, caricati e rimossi dalla memoria.
Le aree della mappa che non sono ancora caricate sono piene di nodi *ignora*, dei nodi segnaposto che non possono
essere né attraversati né selezionati. Gli spazi vuoti delle aree già caricate, invece, sono nodi *d'aria*, dei
nodi invisibili e attraversabili.
Spesso, ci si rifà ai blocchi caricati (attenzione! Blocco non vuol dire nodo, come detto qui sopra!) chiamandoli *blocchi attivi*.
I blocchi attivi possono essere letti e sovrascritti dalle mod o dai giocatori, e contenere entità attive.
Anche il motore di gioco esegue operazioni sulla mappa, come il calcolare la fisica dei liquidi.
I Blocchi Mappa possono essere sia caricati dal database del mondo che generati.
Essi vengono generati fino al limite di generazione della mappa (`mapgen_limit`), che è impostato di base al suo valore massimo, 31000.
I Blocchi Mappa esistenti, tuttavia, ignorano questo limite quando caricati dal database del mondo.
## Lettura
### Lettura dei nodi
Un nodo può essere letto da un mondo fornendone la posizione:
```lua
local nodo = core.get_node({ x = 1, y = 3, z = 4 })
print(dump(nodo)) --> { name=.., param1=.., param2=.. }
```
Se la posizione è un decimale, verrà arrotondata alle coordinate del nodo.
`get_node` ritornerà sempre una tabella contenente le informazioni del nodo:
* `name` - Il nome del nodo, che sarà `ignore` quando l'area non è caricata.
* `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`
(la funzione non caricherà comunque il nodo).
Potrebbe comunque ritornare `ignore` se un blocco contiene effettivamente `ignore`: questo succede ai limiti della mappa.
### Ricerca dei nodi
Minetest offre un numero di funzioni d'aiuto per accelerare le azioni più comuni legate alla mappa.
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" })
if pos_nodo then
core.chat_send_all("Nodo trovato a: " .. dump(pos_nodo))
vel_crescita = 2
end
```
Mettiamo ora che l'indice di crescita debba incrementare per ogni nodo di pietra nei dintorni.
Si dovrebbe quindi usare una funzione in grado di trovare più nodi in un'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 lista_pos =
core.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.
```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" })
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
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.
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.
## Scrittura
### 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.
```lua
core.set_node({ x = 1, y = 3, z = 4 }, { name = "default:stone" })
local nodo = core.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
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" })
```
### Rimozione dei nodi
Un nodo deve sempre essere presente. Per rimuoverlo, basta impostarlo uguale a `air`.
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" })
```
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.
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
-- Carica un'area 20x20x20
local mezza_dimensione = { x = 10, y = 10, z = 10 }
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 chiamerà la funzione locale definita qua sotto `mio_callback` ogni volta che carica un blocco, con delle informazioni sul progresso.
```lua
local function mio_callback(pos, action,
calls_remaining, param)
-- alla prima chiamata, registra il numero di blocchi
if not param.blocchi_totali then
param.blocchi_totali = calls_remaining + 1
param.blocchi_caricati = 0
end
-- Incrementa il numero di blocchi caricati
param.loaded_blocks = param.blocchi_caricati + 1
-- Invia messaggio indicante il progresso
if param.blocchi_totali == param.blocchi_caricati then
core.chat_send_all("Ho finito di caricare blocchi!")
else
local percentuale = 100 * param.blocchi_caricati / param.blocchi_totali
local msg = string.format("Caricamento blocchi %d/%d (%.2f%%)",
param.blocchi_caricati, param.blocchi_totali, percentuale)
core.chat_send_all(msg)
end
end
```
Questo non è l'unico modo per caricare blocchi; utilizzando un LVM (nel dettaglio nel capitolo 19) si potranno infatti caricare i blocchi selezionati in maniera sincrona.
## Cancellazione blocchi
Puoi usare `delete_area` per cancellare una serie di blocchi mappa:
```lua
-- Cancella un'area 20x20x20
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)
```
Questo cancellerà tutti i blocchi mappa in quell'area, anche quelli solo parzialmente selezionati.

315
_it/map/objects.md Normal file
View File

@ -0,0 +1,315 @@
---
title: Oggetti, giocatori ed entità
layout: default
root: ../..
idx: 3.4
description: Alla scopera degli ObjectRef
degrad:
level: warning
title: Gradi e radianti
message: La rotazione dell'oggetto figlio è in gradi, mentre quella dell'oggetto è in radianti.
Assicurati di usare il metodo di misura corretto.
---
## Introduzione <!-- omit in toc -->
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)
- [Oggetti figli](#oggetti-figli)
- [Il tuo turno](#il-tuo-turno)
## Cosa sono gli oggetti, i giocatori e le entità?
Giocatori e entità sono entrambi tipi di oggetti (ObjectRef, quindi di nuovo un riferimento). Un oggetto è qualcosa che si può muovere indipendentemente dalla griglia di nodi e che ha proprietà come velocità e scala.
Attenzione, tuttavia, a non confonderli con gli oggetti nel senso di "cose che possono essere messe in un inventario" (in inglese hanno infatti nomi diversi: *objects* e *items*), anche perché hanno un sistema di registrazione tutto loro.
Ci sono alcune differenze tra giocatori ed entità.
La più grande è che i primi sono controllati da chi gioca, mentre le seconde sono controllate dalle mod.
Ciò significa che, per esempio, la velocità di un giocatore non può essere modificata dalle mod - i giocatori appartengono al lato client, mentre le entità al lato server.
Un'altra differenza è che i giocatori fanno caricare i Blocchi Mappa che li circondano, le entità invece no: quest'ultime vengono salvate e diventano inattive quando il Blocco Mappa in cui si trovano viene rimosso dalla memoria.
Questa distinzione è resa meno chiara dal fatto che le entità sono controllate tramite una Tabella di Entità Lua che vedremo qui sotto.
## Posizione e velocità
`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 pos = giocatore:get_pos()
giocatore:set_pos({ x = pos.x, y = pos.y + 1, z = pos.z })
```
`set_pos` imposta la posizione seduta stante, senza animazione.
Se invece si desidera animare il movimento dell'oggetto verso la nuova posizione, si dovrebbe usare `move_to`.
Questo, tuttavia, funziona soltanto per le entità.
```lua
miaentita:move_to({ x = pos.x, y = pos.y + 1, z = pos.z })
```
Una cosa importante da tenere a mente quando si lavora con le entità è la latenza di rete.
In un mondo ideale, le informazioni riguardo i movimenti delle entità arriverebbero subito, nell'ordine corretto e a intervalli simili a come sono stati inviati.
Tuttavia, a meno che tu non stia giocando in locale, questo non è un mondo ideale.
Le informazioni ci mettono un attimo ad arrivare: per esempio i `set_pos` potrebbero non arrivare in ordine, saltando alcune chiamate.
O lo spazio da coprire di un `move_to` potrebbe non essere suddiviso perfettamente, rendendo l'animazione meno fluida.
Tutto ciò ha come risultato il client che vede cose leggermente diverse dal server, che è una cosa di cui dovresti essere consapevole.
## Proprietà degli oggetti
Le proprietà degli oggetti sono usate per comunicare al client come renderizzare e gestire un oggetto.
Non è possibile definire delle proprietà personalizzate, perché le proprietà sono per definizione fatte per essere usate dall'engine.
Al contrario dei nodi, gli oggetti hanno un comportamento dinamico.
Si può per esempio cambiare il loro aspetto in qualsiasi momento, aggiornandone le proprietà:
```lua
oggetto:set_properties({
visual = "mesh",
mesh = "omino.b3d",
textures = {"omino_texture.png"},
visual_size = {x=1, y=1},
})
```
Le proprietà aggiornate verranno inviate a tutti i giocatori nelle vicinanze.
Questo è molto utile per avere una vasto ammontare di varietà a basso costo, uno fra tanti l'avere diverse skin per giocatore.
Come mostrato nella prossima sezione, le entità possono avere delle proprietà iniziali, che andranno dichiarate nella loro definizione.
## Entità
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.
```lua
local MiaEntita = {
initial_properties = {
hp_max = 1,
physical = true,
collide_with_objects = false,
collisionbox = {-0.3, -0.3, -0.3, 0.3, 0.3, 0.3},
visual = "wielditem",
visual_size = {x = 0.4, y = 0.4},
textures = {""},
spritediv = {x = 1, y = 1},
initial_sprite_basepos = {x = 0, y = 0},
},
messaggio = "Messaggio predefinito",
}
function MiaEntita:imposta_messaggio(msg)
self.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()))
```
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).
```lua
function MiaEntita:on_step(dtime)
local pos = self.oggetto:get_pos()
local pos_giu = vector.subtract(pos, vector.new(0, 1, 0))
local delta
if core.get_node(pos_giu).name == "air" then
delta = vector.new(0, -1, 0)
elseif core.get_node(pos).name == "air" then
delta = vector.new(0, 0, 1)
else
delta = vector.new(0, 1, 0)
end
delta = vector.multiply(delta, dtime)
self.oggetto:move_to(vector.add(pos, delta))
end
function MiaEntita:on_punch(hitter)
core.chat_send_player(hitter:get_player_name(), self.message)
end
```
Ora, se si volesse spawnare e usare questa entità, si noterà che il messaggio andrebbe perduto quando l'entità diventa inattiva per poi ritornare attiva.
Questo succede perché il messaggio non è salvato.
Al posto di salvare tutto nella tabella dell'entità, Minetest ti permette di scegliere come salvare le cose.
Questo succede nella *Staticdata*, una stringa che contiene tutte le informazioni personalizzate che si vogliono ricordare.
```lua
function MiaEntita:get_staticdata()
return core.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 {}
self:imposta_messaggio(data.messaggio)
end
end
```
Minetest può chiamare `get_staticdata()` quando e quante volte vuole.
Questo perché non aspetta che un Blocco Mappa diventi inattivo per salvarlo, in quanto comporterebbe una perdita di informazioni.
I Blocchi Mappa sono salvati circa ogni 18 secondi, quindi dovresti notare un simile intervallo per la chiamata a `get_staticdata()`.
`on_activate()`, d'altro canto, viene chiamato solo quando un'entità diventa attiva o nel Blocco Mappa appena caricato o quando spawna.
Questo significa che il suo staticdata inizialmente potrebbe essere vuoto (dato l'intervallo di 18 secondi).
Infine, c'è bisogno di registrare la tabella usando `register_entity`.
```lua
core.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)
```
Il terzo parametro è lo staticdata inziale.
Per impostare il messaggio, puoi usare la Tabella di Entità Lua:
```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.
Un oggetto può possedere un numero illimitato di figli, ma non più di un genitore.
```lua
figlio:set_attach(parent, bone, position, rotation)
```
Il `get_pos()` di un oggetto ritornerà sempre la sua posizione globale, a prescindere dal fatto che sia figlio o meno.
`set_attach` prende invece una posizione relativa, ma non è quello che credi: la posizione del figlio è relativa a quella del genitore *amplificata quest'ultima* di 10 volte.
Quindi, `0,5,0` sarà metà nodo in alto rispetto al genitore.
{% include notice.html notice=page.degrad %}
Per i modelli 3D animati, il parametro `bone` (osso) è usato per collegare un'entità a un osso.
Le animazioni 3D sono basate su degli scheletri - una rete di ossa nel modello dove ogni osso può avere una posizione e rotazione per cambiare il modello, tipo per muovere un braccio.
Il collegamento a un osso è utile se si vuole per esempio far impugnare qualcosa al personaggio:
```lua
oggetto:set_attach(player,
"Braccio destro", -- osso predefinito
{x=0.2, y=6.5, z=3}, -- posizione predefinita
{x=-100, y=225, z=90}) -- rotazione predefinita
```
## Il tuo turno
* Fai un mulino combinando dei nodi con un'entità.
* Crea un mostro di tua scelta (usando l'API delle entità, e senza usare altre mod).

217
_it/map/storage.md Normal file
View File

@ -0,0 +1,217 @@
---
title: Storaggio e metadati
layout: default
root: ../..
idx: 3.3
description: Scopri come funziona lo spazio d'archiviazione delle mod e come usare i metadati per passare informazioni.
redirect_from:
- /it/chapters/node_metadata.html
- /it/map/node_metadata.html
---
## Introduzione <!-- omit in toc -->
In questo capitolo imparerai i vari modi per immagazzinare dati.
- [Metadati](#metadati)
- [Cos'è un metadato?](#cosè-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)
- [Database](#database)
- [Decidere quale usare](#decidere-quale-usare)
- [Il tuo turno](#il-tuo-turno)
## Metadati
### Cos'è un metadato?
In Minetest, un metadato è una coppia chiave-valore usata per collegare dei dati a qualcosa.
Puoi usare i metadati per salvare informazioni nei nodi, nei giocatori o negli ItemStack.
Ogni tipo di metadato usa la stessa identica API.
Ognuno di essi salva i valori come stringhe, ma ci sono comunque dei metodi per convertire e salvare altri tipi di primitivi.
Per evitare conflitti con altre mod, dovresti usare la nomenclatura convenzionale per le chiavi: `nomemod:nomechiave`.
Alcune chiavi hanno invece un significato speciale, come vedremo più in basso.
Ricorda che i metadati sono dati riguardo altri dati.
Il dato in sé, come il tipo di un nodo o la quantità di un ItemStack, non rientra perciò nella definizione.
### Ottenere i metadati di un oggetto
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 })
```
Quelli dei giocatori e degli ItemStack invece sono ottenuti tramite `get_meta()`:
```lua
local p_meta = player:get_meta()
local i_meta = pila:get_meta()
```
### Lettura e scrittura
Nella maggior parte dei casi, per leggere e scrivere metadati saranno usati i metodi `get_<type>()` e `set_<type>()`.
```lua
print(meta:get_string("foo")) --> ""
meta:set_string("foo", "bar")
print(meta:get_string("foo")) --> "bar"
```
Tutti i getter ritorneranno un valore di default se la chiave non esiste, rispettivamente `""` per le stringhe e `0` per gli interi.
Si può inoltre usare `get()` per ritornare o una stringa o nil.
Come gli inventari, anche i metadati sono riferimenti: ogni cambiamento applicato ad essi, cambierà la fonte originale.
Inoltre, se è possibile convertire un intero in stringa e viceversa, basterà cambiare `get_int`/`get_string` per ottenerne la versione corrispondente:
```lua
print(meta:get_int("count")) --> 0
meta:set_int("count", 3)
print(meta:get_int("count")) --> 3
print(meta:get_string("count")) --> "3"
```
### Chiavi speciali
`infotext` è usato nei nodi per mostrare una porzione di testo al passare il mirino sopra il nodo.
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.
`owner` è una chiave comune, usata per immagazzinare il nome del giocatore a cui appartiene l'oggetto o il nodo.
### Immagazzinare tabelle
Le tabelle devono essere convertite in stringhe prima di essere immagazzinate.
Minetest offre due formati per fare ciò: Lua e JSON.
Quello in Lua tende a essere molto più veloce e corrisponde al formato usato da Lua per le tabelle, mentre JSON è un formato più standard, con una miglior struttura, e che ben si presta per scambiare informazioni con un altro programma.
```lua
local data = { username = "utente1", score = 1234 }
meta:set_string("foo", core.serialize(data))
data = core.deserialize(meta: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.
```lua
meta:set_string("segreto", "asd34dn")
meta:mark_as_private("segreto")
```
### Tabelle Lua
Le tabelle possono essere convertite da/a stringhe nei metadati tramite `to_table` e `from_table`:
```lua
local tmp = meta:to_table()
tmp.foo = "bar"
meta:from_table(tmp)
```
## Storaggio Mod
Lo spazio d'archiviazione della mod (*storage*) usa la stessa identica API dei metadati, anche se non sono tecnicamente la stessa cosa.
Il primo infatti è per mod, e può essere ottenuto solo durante l'inizializzazione - appunto - della mod.
```lua
local memoria = core.get_mod_storage()
```
Nell'esempio è ora possibile manipolare lo spazio d'archiviazione come se fosse un metadato:
```lua
memoria:set_string("foo", "bar")
```
## Database
Se la mod ha buone probabilità di essere usata su un server e tenere traccia di un sacco di dati, è buona norma offrire un database come metodo di storaggio.
Dovresti rendere ciò opzionale, separando il come i dati vengono salvati e il dove vengono usati.
```lua
local backend
if use_database then
backend =
dofile(core.get_modpath("miamod") .. "/backend_sqlite.lua")
else
backend =
dofile(core.get_modpath("miamod") .. "/backend_storage.lua")
end
backend.get_foo("a")
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 backend = {}
function backend.set_foo(key, value)
memoria:set_string(key, core.serialize(value))
end
function backend.get_foo(key)
return core.deserialize(memoria:get_string(key))
end
return backend
```
Il file `backend_sqlite.lua` dovrebbe fare una cosa simile, ma utilizzando la libreria Lua *lsqlite3* al posto della memoria d'archiviazione interna.
Usare un database come SQLite richiede l'utilizzo di un ambiente non sicuro (*insecure environment*).
Un ambiente non sicuro è una tabella disponibile solamente alle mod con accesso esplicito dato dall'utente, e contiene una copia meno limitata della API Lua, che potrebbe essere abusata da mod con intenzioni malevole.
Gli ambienti non sicuri saranno trattati più nel dettaglio nel capitolo sulla [Sicurezza](../quality/security.html).
```lua
local amb_nonsicuro = core.request_insecure_environment()
assert(amb_nonsicuro, "Per favore aggiungi miamod a secure.trusted_mods nelle impostazioni")
local _sql = amb_nonsicuro.require("lsqlite3")
-- Previene che altre mod usino la libreria globale sqlite3
if sqlite3 then
sqlite3 = nil
end
```
Spiegare il funzionamento di SQL o della libreria lsqlite non rientra nell'obiettivo di questo libro.
## Decidere quale usare
Il tipo di metodo che si sceglie di utilizzare dipende dal tipo di dati trattati, come sono formattati e quanto sono grandi.
In linea di massima, i dati piccoli vanno fino ai 10KB, quelli medi 10MB, e quelli grandi oltre i 10MB.
I metadati dei nodi sono un'ottima scelta quando si vogliono immagazzinare dati relativi al nodo.
Inserirne di medi (quindi massimo 10MB) è abbastanza efficiente se si rendono privati.
Quelli degli oggetti invece dovrebbero essere usati solo per piccole quantità di dati e non è possibile evitare di inviarli al client.
I dati, poi, saranno anche copiati ogni volta che la pila viene spostata o ne viene fatto accesso tramite Lua.
La memoria interna della mod va bene per i dati di medie dimensioni, tuttavia provare a salvarne di grandi potrebbe rivelarsi inefficiente.
È meglio usare un database per le grandi porzioni di dati, onde evitare di dover sovrascrivere tutti i dati a ogni salvataggio.
I database sono fattibili solo per i server a causa della necessità di lasciar passare la mod nell'ambiente non sicuro.
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`)

97
_it/map/timers.md Normal file
View File

@ -0,0 +1,97 @@
---
title: Timer dei nodi e ABM
layout: default
root: ../..
idx: 3.2
description: Impara come creare ABM e timer per modificare i blocchi.
redirect_from:
- /it/chapters/abms.html
- /it/map/abms.html
---
## Introduzione <!-- omit in toc -->
Eseguire periodicamente una funzione su certi nodi è abbastanza comune.
Minetest fornisce due metodi per fare ciò: gli ABM (*Active Block Modifiers*, Modificatori di blocchi attivi) e i timer associati ai nodi.
Gli ABM scansionano tutti i Blocchi Mappa alla ricerca dei nodi che rientrano nei canoni:
essi sono quindi ottimali per quei nodi che si trovano con frequenza in giro per il mondo, come l'erba.
Possiedono un alto consumo della CPU, senza invece pressoché impattare sulla memoria e lo spazio d'archiviazione.
Per i nodi invece non troppo comuni o che già usano metadati, come le fornaci e i macchinari, dovrebbero venire impiegati i timer.
I timer dei nodi tengon traccia dei timer accodati in ogni Blocco Mappa, eseguendoli quando raggiungono lo zero.
Ciò significa che non hanno bisogno di cercare tra tutti i nodi caricati per trovare un match, bensì, richiedendo un po' più di memoria e spazio d'archiviazione, vanno alla ricerca dei soli nodi con un timer in corso.
- [Timer dei nodi](#timer-dei-nodi)
- [ABM: modificatori di blocchi attivi](#abm-modificatori-di-blocchi-attivi)
- [Il tuo turno](#il-tuo-turno)
## Timer dei nodi
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)
timer:start(10.5) -- in secondi
```
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", {
on_timer = function(pos)
core.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.
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.
## ABM: modificatori di blocchi attivi
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", {
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)
})
core.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
chance = 50, -- possibilità di partire su un nodo ogni 50
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"})
end
})
```
Questo ABM viene eseguito ogni 10 secondi, e per ogni nodo d'erba (`default:default_with_grass`) c'è una possibilità su 50 che l'ABM parta.
Quando ciò accade, un nodo di erba aliena (`alieni:erba`) gli viene piazzato sopra (attenzione, tuttavia, che così facendo, il nodo che c'era prima verrà cancellato, quindi sarebbe meglio controllare prima che sopra ci sia dell'aria)
Specificare dei vicini (*neighbors*) è opzionale.
Se ne vengono specificati più di uno, basterà che uno solo di essi sia presente per soddisfare la condizione.
Anche le possibilità (*chance*) sono opzionali.
Se non vengono specificate, l'ABM verrà sempre eseguito quando le altre condizioni sono soddisfatte.
## Il tuo turno
* Tocco di Mida: tramuta l'acqua in oro con una possibilità di 1 su 100, ogni 5 secondi;
* Decadimento: fai che il legno diventi terra quando questo confina con dell'acqua.
* Al fuoco!: fai prendere fuoco a ogni blocco d'aria (suggerimento: "air" e "fire:basic_flame"). Avvertenza: aspettati un crash del gioco

190
_it/players/chat.md Executable file
View File

@ -0,0 +1,190 @@
---
title: Chat e comandi
layout: default
root: ../..
idx: 4.2
description: Come registrare un comando e gestire i messaggi della chat
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.
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.
cb_cmdsprivs:
level: warning
title: Privilegi e comandi
message: |
Il privilegio shout non è necessario per far sì che un giocatore attivi questo richiamo.
Questo perché i comandi sono implementati in Lua, e sono semplicemente dei messaggi in chat che iniziano con /.
---
## Introduzione <!-- omit in toc -->
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)
- [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)
## 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")
```
Segue un esempio di come apparirerebbe in gioco:
<Tizio> Guarda qui
Questo è un messaggio visualizzabile da tutti
<Caio> Eh, cosa?
Il messaggio appare su una nuova riga, per distinguerlo dai messaggi dei giocatori.
### 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")
```
Questo messaggio viene mostrato esattamente come il precedente, ma solo, in questo caso, a Tizio.
## Comandi
Per registrare un comando, per esempio `/foo`, si usa `register_chatcommand`:
```lua
core.register_chatcommand("foo", {
privs = {
interact = true,
},
func = function(name, param)
return true, "Hai detto " .. param .. "!"
end,
})
```
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 %}
### Accettare più argomenti
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.
#### Usare string.split
Una stringa può essere spezzettata in più parti tramite `string.split(" ")`:
```lua
local parti = param:split(" ")
local cmd = parti[1]
if cmd == "entra" then
local nome_squadra = parti[2]
squadra.entra(name, nome_squadra)
return true, "Sei dentro la squadra!"
elseif cmd == "gioc_max" then
local nome_squadra = parti[2]
local gioc_max = tonumber(parti[3])
if nome_squadra and gioc_max then
return true, "Giocatori massimi della squadra " .. nome_squadra .. " impostati a " .. gioc_max
else
return false, "Uso: /squadra gioc_max <nome_squadra> <numero>"
end
else
return false, "È necessario un comando"
end
```
#### Usare i pattern Lua
[I pattern Lua](https://www.lua.org/pil/20.2.html) sono un modo per estrapolare pezzi di testo seguendo delle regole.
Sono perfetti in caso di argomenti che contengono spazi, o comunque quando è richiesto un controllo più meticoloso dei parametri catturati.
```lua
local a, msg = string.match(param, "^([%a%d_-]+) (.+)$")
```
Il codice sovrastante implementa `/msg <a> <messaggio>`. Vediamo cos'è successo partendo da sinistra:
* `^` dice di iniziare a combaciare dall'inizio della stringa;
* `()` è 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 `-`.
* `+` dice di combaciare ciò che lo precede una o più volte.
* `.` 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).
Vengono poi ritornati nome e messaggio, perché sono inseriti nelle parentesi.
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>.
</p>
## Intercettare i messaggi
Per intercettare un messaggio, si usa `register_on_chat_message`:
```lua
core.register_on_chat_message(function(name, message)
print(name .. " ha detto " .. message)
return false
end)
```
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 %}
Dovresti assicurarti, poi, che il messaggio potrebbe essere un comando che invia messaggi in chat,
o che l'utente potrebbere non avere `shout`.
```lua
core.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
print(name .. " ha detto " .. message)
else
print(name .. " ha provato a dire " .. message ..
" ma non ha lo shout")
end
return false
end)
```

330
_it/players/formspecs.md Normal file
View File

@ -0,0 +1,330 @@
---
title: GUI (Formspec)
layout: default
root: ../..
idx: 4.5
description: Tempo di interagire con le finestre
redirect_from: /it/chapters/formspecs.html
submit_vuln:
level: warning
title: Client malevoli possono inviare qualsiasi cosa quando più gli piace
message: Non dovresti mai fidarti di un modulo di compilazione - anche se non hai mai mostrato loro il formspec.
Questo significa che dovresti controllarne i privilegi e assicurarti che dovrebbero effettivamente essere in grado di eseguire quest'azione.
---
## Introduzione <!-- omit in toc -->
<figure class="right_image">
<img src="{{ page.root }}//static/formspec_example.png" alt="Furnace Inventory">
<figcaption>
Screenshot del formspec di una fornace e della sua struttura.
</figcaption>
</figure>
In questo capitolo impareremo come creare un formspec e mostrarlo all'utente.
Un formspec è il codice di specifica di un modulo (*form*, da qui *form*-*spec*).
In Minetest, i moduli sono delle finestre come l'inventario del giocatore e possono contenere un'ampia gamma di elementi, come le etichette, i pulsanti e i campi.
Tieni presente che se non si ha bisogno di ricevere input dal giocatore, per esempio quando si vogliono far apparire semplicemente delle istruzioni a schermo, si dovrebbe considerare l'utilizzo di una [HUD (Heads Up Display)](hud.html) piuttosto che quello di un formspec, in quanto le finestre inaspettate (con tanto di mouse che appare) tendono a impattare negativamente sulla giocabilità.
- [Coordinate reali o datate](#coordinate-reali-o-datate)
- [Anatomia di un formspec](#anatomia-di-un-formspec)
- [Elementi](#elementi)
- [Intestazione](#intestazione)
- [Esempio: indovina un numero](#esempio-indovina-un-numero)
- [Imbottitura e spaziatura](#imbottitura-e-spaziatura)
- [Ricevere i moduli di compilazione](#ricevere-i-moduli-di-compilazione)
- [Contesti](#contesti)
- [Ricavare un formspec](#ricavare-un-formspec)
- [Formspec nei nodi](#formspec-nei-nodi)
- [Inventario del giocatore](#inventario-del-giocatore)
- [Il tuo turno](#il-tuo-turno)
## Coordinate reali o datate
Nelle vecchie versioni di Minetest, i formspec erano incoerenti.
Il modo in cui elementi diversi venivano posizionati nel formspec variava in maniere inaspettate; era difficile predirne la collocazione e allinearli correttamente.
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
Il formspec è un linguaggio di dominio specifico con un formato insolito.
Consiste in un numero di elementi che seguono il seguente schema:
tipo[param1;param2]
Viene prima dichiarato il tipo dell'elemento, seguito dai parametri nelle parentesi quadre.
Si possono concatenare più elementi, piazzandoli eventualmente su più linee:
foo[param1]bar[param1]
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/).
### Intestazione
L'intestazione di un formspec contiene informazioni che devono apparire prima di tutto il resto.
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.
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]
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.
La posizione e l'ancoraggio degli elementi sono usati per collocare il formspec nello schermo.
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]
size[2,2]
position[0,0.5]
anchor[0,0.5]
Per l'esattezza è stato messo il centro del formspec sul bordo sinistro dello schermo (`position[0, 0.5]`) e poi ne è stato spostato l'ancoraggio in modo da allineare il lato sinistro del formspec con quello dello schermo.
## Esempio: indovina un numero
<figure class="right_image">
<img src="{{ page.root }}/static/formspec_guessing.png" alt="Guessing Formspec">
<figcaption>
Il formspec del gioco dell'indovinare un numero
</figcaption>
</figure>
Il modo migliore per imparare è sporcarsi le mani, quindi creiamo un gioco.
Il principio è semplice: la mod decide un numero, e il giocatore deve tentare di indovinarlo.
La mod, poi, comunica se si è detto un numero più alto o più basso rispetto a quello corretto.
Prima di tutto, costruiamo una funzione per creare il formspec.
È buona pratica fare ciò, in quanto rende il riutilizzo più comodo.
<div style="clear: both;"></div>
```lua
indovina = {}
function indovina.prendi_formspec(nome)
-- TODO: comunicare se il numero del tentativo era più alto o più basso
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), "]",
"field[0.375,1.25;5.25,0.8;numero;Numero;]",
"button[1.5,2.3;3,0.8;indovina;Indovina]"
}
-- table.concat è più veloce della concatenazione di stringhe - `..`
return table.concat(formspec, "")
end
```
Nel codice qui sopra abbiamo inserito un'etichetta (*label*), un campo (*field*) e un pulante (*button*).
Un campo ci permete di inserire del testo, mentre useremo il pulsante per inviare il modulo.
Noterai che gli elementi sono posizionati attentamente per aggiungere imbottitura e spaziatura (*padding* e *spacing*),
ma ci arriveremo tra poco.
Come prossima cosa, vogliamo permettere al giocatore di visualizzare il formspec.
Il metodo principale per farlo è usare `show_formspec`:
```lua
function indovina.mostra_a(nome)
core.show_formspec(nome, "indovina:gioco", indovina.prendi_formspec(nome))
end
core.register_chatcommand("gioco", {
func = function(name)
indovina.mostra_a(name)
end,
})
```
La funzione `show_formspec` prende il nome del giocatore, il nome del formspec e il formspec stesso.
Il nome di quest'ultimo dovrebbe seguire il formato del nome degli oggetti, tipo `nomemod:nomeoggetto`.
### Imbottitura e spaziatura
<figure class="right_image">
<img src="{{ page.root }}/static/formspec_padding_spacing.png" alt="Padding and spacing">
<figcaption>
Il formspec del gioco dell'indovinare un numero
</figcaption>
</figure>
L'imbottitura (*padding*) è lo spazio che intercorre tra il bordo del formspec e i suoi contenuti, o tra elementi non in relazione fra loro - mostrato in rosso.
La spaziatura (*spacing*) è invece lo spazio tra elementi in comune - mostrata in blu.
È abbastanza uno standard avere un'imbottitura di `0.375` e una spaziatura di `0.25`.
<div style="clear: both;"></div>
### Ricevere i moduli di compilazione
Quando `show_formspec` viene chiamato, il formspec viene inviato al client per essere visualizzato.
Per far sì che i formspec siano utili, le informazioni devono essere ritornate dal client al server.
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)
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)
end
end)
```
La funzione data in `core.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.
I nomi degli elementi appariranno nel campo con il loro nome, ma solo se sono rilevanti per l'evento che ha causato l'invio.
Per esempio, un elemento "pulsante" apparirà nei campi solo se quel particolare pulsante è stato premuto.
{% include notice.html notice=page.submit_vuln %}
Quindi, ora il formspec è stato inviato al client e il client ritorna quelle informazioni.
Il prossimo passaggio è generare e ricordare il valore ricevuto, e aggiornare il formspec basandosi sui tentativi.
Il modo per fare ciò è usare un concetto chiamato "contesto".
### Contesti
In molti casi si può desiderare che le informazioni passate da `show_formspec` al callback non raggiungano il client.
Ciò potrebbe includere con cosa è stato chiamato un comando via chat, o di cosa tratta la finestra di dialogo.
In questo caso, il valore che si necessita di ricordare.
Un contesto (*context*) è una tabella assegnata a ogni giocatore per immagazzinare informazioni, e i contesti di tutti i giocatori sono
salvati in una variabile locale di file:
```lua
local _contesti = {}
local function prendi_contesto(nome)
local contesto = _contesto[nome] or {}
_contesti[nome] = contesto
return contesto
end
core.register_on_leaveplayer(function(player)
_contexts[player:get_player_name()] = nil
end)
```
Ora abbiamo bisogno di modificare il codice da mostrare, per aggiornare il contesto prima di mostrare il formspec:
```lua
function indovina.mostra_a(nome)
local contesto = prendi_contesto(nome)
contesto.soluzione = contesto.soluzione or math.random(1, 10)
local formspec = indovina.prendi_formspec(nome, contesto)
core.show_formspec(nome, "indovina:gioco", formspec)
end
```
Abbiamo anche bisogno di modificare la generazione del formspec per usare il contesto:
```lua
function indovina.prendi_formspec(nome, contesto)
local testo
if not contesto.tentativo then
testo = "Sto pensando a un numero... Prova a indovinare!"
elseif contesto.tentativo == contesto.soluzione then
testo = "Yeee, hai indovinato!"
elseif contesto.tentativo > contesto.soluzione then
testo = "Troppo alto!"
else
testo = "Troppo basso!"
end
```
Tieni a mente che quando si ottiene il formspec è buona norma leggerne il contesto, senza però aggiornalo.
Questo può rendere la funzione più semplice, e anche più facile da testare.
E in ultimo, abbiamo bisogno di aggiornare il contesto con il tentativo del giocatore:
```lua
if fields.indovina then
local nome = player:get_player_name()
local contesto = prendi_contesto(nome)
contesto.tentativo = tonumber(fields.numero)
indovina.mostra_a(nome)
end
```
## Ricavare un formspec
Ci sono tre diversi modi per far sì che un formspec sia consegnato al client:
* [show_formspec](#esempio-indovina-un-numero): il metodo usato qui sopra. I campi sono ottenuti tramite `register_on_player_receive_fields`;
* [Metadati di un nodo](#formspec-nei-nodi): si aggiunge il formspec nel nodo tramite metadati, che viene mostrato *immediatamente* al giocatore che preme il nodo col tasto destro.
I campi vengono ricevuti attraverso un metodo nella definizione del nodo chiamato `on_receive_fields`.
* [Inventario del giocatore](#inventario-del-giocatore): il formspec viene inviato al client in un certo momento, e mostrato immediatamente quando il giocatore preme "I".
I campi vengono ricevuti tramite `register_on_player_receive_fields`.
### 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).
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", {
description = "Premimi col tasto destro del mouse!",
tiles = {"miamod_tastodestro.png"},
groups = {cracky = 1},
after_place_node = function(pos, placer)
-- Questa funzione è eseguita quando viene piazzato il nodo.
-- 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)
meta:set_string("formspec",
"formspec_version[4]" ..
"size[5,5]"..
"label[1,1;Questo è mostrato al premere col destro]"..
"field[1,2;2,1;x;x;]")
end,
on_receive_fields = function(pos, formname, fields, player)
if(fields.quit) then return end
print(fields.x)
end
})
```
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
tramite il premere col tasto destro del mouse su un nodo. Non è possibile farlo programmaticamente.
### Inventario del giocatore
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.
### Il tuo turno
* Estendi l'indovina il numero per far in modo che tenga traccia del risultato migliore di ogni giocatore, dove con "risultato migliore" si intende il minor numero di tentativi per indovinare.
* Crea un nodo chiamato "Casella delle lettere" dove gli utenti possono aprire un formspec e lasciare messaggi.
Questo nodo dovrebbe salvare il nome del mittente come `owner` nei metadati, e dovrebbe usare `show_formspec` per mostrare formspec differenti a giocatori differenti.

281
_it/players/hud.md Normal file
View File

@ -0,0 +1,281 @@
---
title: HUD
layout: default
root: ../..
idx: 4.6
description: come creare elementi a schermo
redirect_from: /it/chapters/hud.html
---
## Introduzione <!-- omit in toc -->
Le HUD (Heads Up Display) ti permettono di mostrare testi, immagini e altri elementi grafici senza interrompere il giocatore.
Le HUD, infatti, non accettano input dall'utente, lasciando quel ruolo ai [formspec](formspecs.html).
- [Posizionamento](#posizionamento)
- [Posizione e scostamento](#posizione-e-scostamento)
- [Allineamento](#allineamento)
- [Esempio: tabellone segnapunti](#esempio-tabellone-segnapunti)
- [Elementi di testo](#elementi-di-testo)
- [Parametri](#parametri)
- [Tornando all'esempio](#tornando-allesempio)
- [Elementi immagine](#elementi-immagine)
- [Parametri](#parametri-1)
- [Tornando all'esempio](#tornando-allesempio-1)
- [Cambiare un elemento](#cambiare-un-elemento)
- [Salvare gli ID](#salvare-gli-id)
- [Altri elementi](#altri-elementi)
## Posizionamento
### Posizione e scostamento
<figure class="right_image">
<img
width="300"
src="{{ page.root }}//static/hud_diagram_center.svg"
alt="Diagramma che mostra un elemento di testo centrato">
</figure>
Essendoci schermi di tutte le dimensioni e risoluzioni, per funzionare bene le HUD devono sapersi adattare a ognuno di essi.
Per ovviare al problema, Minetest specifica il collocamento di un elemento usando sia una posizione in percentuale che uno scostamento (*offset*).
La posizione percentuale è relativa alla grandezza dello schermo, dacché per posizionarne un elemento al centro, bisogna fornire un valore di 0.5 (cioè il 50%).
Lo scostamento è poi usato per - appunto - scostare un elemento in relazione alla sua posizione.
<div style="clear:both;"></div>
### Allineamento
L'allineamento (*alignment*) è dove il risultato della posizione e dello scostamento viene applicato sull'elemento - per esempio, `{x = -1.0, y = 0.0}` allineerà i valori degli altri due parametri sulla sinistra dell'elemento.
Questo è particolarmente utile quando si vuole allineare del testo a sinistra, a destra o al centro.
<figure>
<img
width="500"
src="{{ page.root }}//static/hud_diagram_alignment.svg"
alt="Diagramma che mostra i vari tipi di allineamento">
</figure>
Il diagramma qui in alto mostra mostra tre finestre (in blu), ognuna contenente un elemento HUD (in giallo) con ogni volta un allineamento diverso.
La freccia è il risultato del calcolo di posizione e scostamento.
### Esempio: tabellone segnapunti
Per capire meglio il capitolo, vedremo come posizionare e aggiornare un pannello contenente dei punti come questo:
<figure>
<img
src="{{ page.root }}//static/hud_final.png"
alt="Screenshot dell'HUD da realizzare">
</figure>
Nello screenshot sovrastante, tutti gli elementi hanno la stessa posizione percentuale (100%, 50%) - ma scostamenti diversi.
Questo permette all'intero pannello di essere ancorato sulla destra della finestra, senza posizioni/scostamenti strani al ridimensionarla.
## Elementi di testo
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 idx = giocatore:hud_add({
hud_elem_type = "text",
position = {x = 0.5, y = 0.5},
offset = {x = 0, y = 0},
text = "Ciao mondo!",
alignment = {x = 0, y = 0}, -- allineamento centrato
scale = {x = 100, y = 100}, -- lo vedremo tra poco
})
```
La funzione `hud_add` ritorna l'ID di un elemento, che è utile per modificarlo o rimuoverlo.
### Parametri
Il tipo dell'elemento è ottenuto usando la proprietà `hud_elem_type` nella tabella di definizione.
Cambiando il tipo, cambia il significato di alcune delle proprietà che seguono.
`scale` sono i limiti del testo; se esce da questo spazio, viene tagliato - nell'esempio `{x=100, y=100}`.
`number` è il colore del testo, ed è in [formato esadecimale](http://www.colorpicker.com/) - nell'esempio `0xFF0000`.
### Tornando all'esempio
Andiamo avanti e piazziamo tutto il testo nel nostro pannello (si son tenute le stringhe in inglese per non confondere con l'immagine, NdT):
```lua
-- Ottiene il numero di blocchi scavati e piazzati dallo spazio d'archiviazione; se non esiste è 0
local meta = giocatore:get_meta()
local digs_testo = "Digs: " .. meta:get_int("punteggio:digs")
local places_testo = "Places: " .. meta:get_int("punteggio:places")
giocatore:hud_add({
hud_elem_type = "text",
position = {x = 1, y = 0.5},
offset = {x = -120, y = -25},
text = "Stats",
alignment = 0,
scale = { x = 100, y = 30},
number = 0xFFFFFF,
})
giocatore:hud_add({
hud_elem_type = "text",
position = {x = 1, y = 0.5},
offset = {x = -180, y = 0},
text = digs_testo,
alignment = -1,
scale = { x = 50, y = 10},
number = 0xFFFFFF,
})
giocatore:hud_add({
hud_elem_type = "text",
position = {x = 1, y = 0.5},
offset = {x = -70, y = 0},
text = places_testo,
alignment = -1,
scale = { x = 50, y = 10},
number = 0xFFFFFF,
})
```
Il risultato è il seguente:
<figure>
<img
src="{{ page.root }}//static/hud_text.png"
alt="Screenshot della HUD finora">
</figure>
## Elementi immagine
Le immagini sono create in un modo molto simile a quelli del testo:
```lua
player:hud_add({
hud_elem_type = "image",
position = {x = 1, y = 0.5},
offset = {x = -220, y = 0},
text = "punteggio_sfondo.png",
scale = { x = 1, y = 1},
alignment = { x = 1, y = 0 },
})
```
Siamo ora a questo punto:
<figure>
<img
src="{{ page.root }}//static/hud_background_img.png"
alt="Screenshot della HUD finora">
</figure>
### Parametri
Il campo `text` in questo caso viene usato per fornire il nome dell'immagine.
Se una coordinata in `scale` è positiva, allora è un fattore scalare dove 1 è la grandezza originale, 2 è il doppio e così via.
Tuttavia, se una coordinata è negativa, sarà la percentuale della grandezza dello schermo.
Per esempio, `x=-100` equivale al 100% della larghezza di quest'ultimo.
### Tornando all'esempio
Creiamo la barra di progresso per il nostro tabellone usando `scale`:
```lua
local percentuale = tonumber(meta:get("punteggio:score") or 0.2)
giocatore:hud_add({
hud_elem_type = "image",
position = {x = 1, y = 0.5},
offset = {x = -215, y = 23},
text = "barra_punteggio_vuota.png",
scale = { x = 1, y = 1},
alignment = { x = 1, y = 0 },
})
giocatore:hud_add({
hud_elem_type = "image",
position = {x = 1, y = 0.5},
offset = {x = -215, y = 23},
text = "barra_punteggio_piena.png",
scale = { x = percentuale, y = 1},
alignment = { x = 1, y = 0 },
})
```
Abbiamo ora una HUD uguale identica a quella della prima immagine!
C'è un problema, tuttavia: non si aggiornerà al cambiare delle statistiche.
## Cambiare un elemento
Per cambiare un elemento si usa l'ID ritornato dal metodo `hud_add`.
```lua
local idx = giocatore:hud_add({
hud_elem_type = "text",
text = "Ciao mondo!",
-- parametri rimossi per brevità
})
giocatore:hud_change(idx, "text", "Nuovo testo")
giocatore:hud_remove(idx)
```
Il metodo `hud_change` prende l'ID dell'elemento, la proprietà da cambiare e il nuovo valore.
La chiamata qui sopra cambia la proprietà `text` da "Ciao mondo!" a "Nuovo test".
Questo significa che fare `hud_change` subito dopo `hud_add` è funzionalmente equivalente a
fare ciò che segue, in maniera alquanto inefficiente:
```lua
local idx = giocatore:hud_add({
hud_elem_type = "text",
text = "Nuovo testo",
})
```
## Salvare gli ID
```lua
local hud_salvate = {}
function punteggio.aggiorna_hud(giocatore)
local nome_giocatore = giocatore:get_player_name()
-- Ottiene il numero di blocchi scavati e piazzati dallo spazio d'archiviazione; se non esiste è 0
local meta = giocatore:get_meta()
local digs_testo = "Digs: " .. meta:get_int("punteggio:digs")
local places_testo = "Places: " .. meta:get_int("punteggio:places")
local percentuale = tonumber(meta:get("punteggio:score") or 0.2)
local ids = hud_salvate[nome_giocatore]
if ids then
giocatore:hud_change(ids["places"], "text", places_testo)
giocatore:hud_change(ids["digs"], "text", digs_testo)
giocatore:hud_change(ids["bar_foreground"],
"scale", { x = percentuale, y = 1 })
else
ids = {}
hud_salvate[player_name] = ids
-- crea gli elementi HUD e imposta gli ID in `ids`
end
end
core.register_on_joinplayer(punteggio.aggiorna_hud)
core.register_on_leaveplayer(function(player)
hud_salvate[player:get_player_name()] = nil
end)
```
## Altri elementi
Dai un occhio a [lua_api.md](https://minetest.gitlab.io/minetest/hud/) per una lista completa degli elementi HUD.

View File

@ -0,0 +1,64 @@
---
title: Fisica del giocatore
layout: default
root: ../..
idx: 4.4
redirect_from: /it/chapters/player_physics.html
---
## Introduzione <!-- omit in toc -->
La fisica del giocatore può essere modificata usando le sovrascritture apposite (*physics ovverrides*).
Esse sono dei moltiplicatori che servono per impostare la velocità di movimento, del salto, o la gravità del singolo giocatore.
Per esempio, un valore di 2 sulla gravità, renderà la gravità di un utente due volte più forte.
- [Esempio base](#esempio-base)
- [Sovrascritture disponibili](#sovrascritture-disponibili)
- [Vecchio sistema di movimento](#vecchio-sistema-di-movimento)
- [Incompatibilità tra mod](#incompatibilità-tra-mod)
- [Il tuo turno](#il-tuo-turno)
## Esempio base
Segue l'esempio di un comando di antigravità:
```lua
core.register_chatcommand("antigrav", {
func = function(name, param)
local giocatore = core.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)
})
end,
})
```
## 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:
* `speed`: moltiplicatore della velocità di movimento (predefinito: 1)
* `jump`: moltiplicatore del salto (predefinito: 1)
* `gravity`: moltiplicatore della gravità (predefinito: 1)
* `sneak`: se il giocatore può camminare di soppiatto o meno (predefinito: true)
### Vecchio sistema di movimento
Il movimento dei giocatori prima della versione 0.4.16 includeva il cosiddetto *sneak glitch*, il quale permetteva vari glitch di movimento come l'abilità di scalare un "ascensore" fatta di certi blocchi premendo shift (la camminata di soppiatto) e salto. Nonostante non fosse una funzionalità voluta, è stata mantenuta nelle sovrascritture dato il suo uso in molti server.
Per ripristinare del tutto questo comportamento servono due chiavi:
* `new_move`: se usare o meno il nuovo sistema di movimento (predefinito: true)
* `sneak_glitch`: se il giocatore può usare o meno il glitch dell'ascensore (default: false)
## Incompatibilità tra mod
Tieni a mente che le mod che sovrascrivono la stessa proprietà fisica di un giocatore tendono a essere incompatibili tra di loro.
Quando si imposta una sovrascrittura, sovrascriverà qualsiasi altro suo simile impostato in precedenza: ciò significa che se la velocità di movimento di un giocatore viene cambiata più volte, solo l'ultimo valore verrà preso in considerazione.
## Il tuo turno
* **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.

124
_it/players/privileges.md Normal file
View File

@ -0,0 +1,124 @@
---
title: Privilegi
layout: default
root: ../..
idx: 4.1
description: Tu, non puoi, passareee! (Tu invece sì)
redirect_from: /it/chapters/privileges.html
---
## Introduzione <!-- omit in toc -->
I privilegi (*privileges*, solitamente abbreviati in *privs*), danno ai giocatori l'abilità di eseguire certe azioni.
I proprietari dei server possono assegnare e revocare i privilegi per controllare quali cose un giocatore può o non può fare.
- [Privilegi sì e privilegi no](#privilegi-si-e-privilegi-no)
- [Dichiarazione](#dichiarazione)
- [Controlli](#controlli)
- [Ottenere e impostare privilegi](#ottenere-e-impostare-privilegi)
- [Aggiungere privilegi a basic_privs](#aggiungere-privilegi-a-basicprivs)
## Privilegi sì e privilegi no
I privilegi non sono fatti per indicare classi o status.
**Privilegi corretti:**
* interact
* shout
* noclip
* fly
* kick
* ban
* vote
* worldedit
* area_admin - se si tratta di un privilegio per gli amministratori è ok
**Privilegi sbagliati:**
* moderatore
* amministratore
* elfo
* nano
## Dichiarazione
Usa `register_privilege` per dichiarare un nuovo privilegio:
```lua
core.register_privilege("voto", {
description = "Può votare nei sondaggi",
give_to_singleplayer = false
})
```
`give_to_singleplayer` è di base true, quindi non c'è bisogno di specificarlo se non lo si vuole mettere false.
## Controlli
Per controllare velocemente se un giocatore ha tutti i privilegi necessari o meno:
```lua
local celo, manca = core.check_player_privs(player_or_name, {
interact = true,
voto = true })
```
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, {
interact = true,
voto = true })
if celo then
print("Il giocatore ha tutti i privilegi!")
else
print("Al giocatore mancano i seguenti privilegi: " .. dump(manca))
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
return false, "Hai bisogno del privilegio 'interact' per eseguire quest'azione!"
end
```
## Ottenere e impostare privilegi
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)
print(dump(privs))
privs.voto = true
core.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.
```lua
{
fly = true,
interact = true,
shout = true -- per poter scrivere in chat
}
```
## Aggiungere privilegi a basic_privs
I giocatori con il privilegio `basic_privs` sono in grado di assegnare e revocare un set limitato di privilegi.
È cosa comune assegnarlo ai moderatori in modo che possano mettere o togliere `interact` e `shout` agli altri giocatori, ma che al tempo stesso non possano assegnare privilegi (a loro stessi o ad altri giocatori) con maggiori possibilità di abuso - come `give` e `server`.
Per modificare quali sono i privilegi contenuti in `basic_privs`, va cambiata l'omonima opzione.
Se di base si ha infatti:
basic_privs = interact, shout
Per aggiungere `vote`, basta fare:
basic_privs = interact, shout, vote

4
_it/players/sfinv.md Normal file
View File

@ -0,0 +1,4 @@
---
sitemap: false
redirect_to: "https://github.com/rubenwardy/sfinv/blob/master/Tutorial.md"
---

209
_it/quality/clean_arch.md Normal file
View File

@ -0,0 +1,209 @@
---
title: Introduzione alle architetture pulite
layout: default
root: ../..
idx: 8.4
---
## Introduzione <!-- omit in toc -->
Una volta che una mod raggiunge dimensioni considerevoli, sarà sempre più difficile mantenerne il codice pulito e privo di errori.
Questo è un grosso problema soprattutto quando si usa un linguaggio dinamico come Lua, considerando che il compilatore restituisce pochissimi aiuti quando si tratta di cose come l'assicurarsi di non aver fatto errori di battitura.
Questo capitolo si occupa di concetti importanti, necessari per mantenere il codice pulito, e di strutture utili per realizzarli.
Tieni a mente che lo scopo del capitolo non è quello di essere LA bibbia, bensì di dare un'idea su come muoversi.
Non c'è il modo giusto e il modo sbagliato per ideare una mod, in quanto il loro design è alquanto soggettivo.
- [Coesione, dipendenze, e separazione degli ambiti](#coesione-dipendenze-e-separazione-degli-ambiti)
- [Osservatore](#osservatore)
- [Modello-Vista-Controllo](#modello-vista-controllo)
- [API-Vista](#api-vista)
- [Conclusione](#conclusione)
## Coesione, dipendenze, e separazione degli ambiti
Senza alcuna pianificazione, il codice di un progetto diverrà man mano un bel fritto misto (o quello che gli inglesi definiscono *spaghetti code*).
Ovvero, mancherà di struttura perché ne è stata fatta un'accozzaglia senza chiare delimitazioni.
Sul lungo corso il progetto diverrà ingestibile, concludendosi con il suo abbandono.
Per evitare ciò, è buona cosa ideare il proprio progetto come un insieme di piccole aree di codice che interagiscono tra di loro.
> All'interno di ogni grande programma, c'è un programma più piccolo che cerca di scappare.
>
> --C.A.R. Hoare
Questo dovrebbe essere fatto in modo tale da ottenere una "separazione degli ambiti" (*Separation of Concerns*), dove ogni area dovrebe essere distinta e occuparsi di un bisogno specifico.
Questi programmi/aree dovrebbero avere le due seguenti proprietà:
* **Alta coesione** - ciò che succede nell'area dovrebbe essere strettamente legato.
* **Basse dipendenze** - mantenere le dipendenze tra un'area e l'altra più basse possibili, ed evitare di affidarsi a implementazioni interne.
È davvero ottimo assicurarsi di ciò, in quanto cambiare le API di certe aree risulterà più fattibile.
Tieni a mente che ciò si applica sia nella relazione tra una mod e l'altra, che in quella tra le varie aree all'interno della stessa mod.
## Osservatore
Una maniera semplice per separare le aree di codice è usare lo schema dell'Osservatore (*Observer pattern*).
Si prenda l'esempio di sbloccare un trofeo quando un giocatore uccide per la prima volta un mostro raro.
L'approccio ingenuo è quello di avere il codice del trofeo nella funzione di uccisione del mostro, controllando il suo nome e sbloccando il trofeo se coincide.
Questa è però una cattiva idea, in quanto rende il mob dipendente dal codice dei trofei.
Se si contiuasse su questa strada - per esempio aggiungendo l'esperienza ottenuta uccidendo il mob - si finirebbe con l'avere un sacco di dipendenze alla rinfusa.
Lo schema dell'Osservatore dice invece di far esporre alla mod del mostro un modo per far sì che altre aree di codice possano inserire comodamente dei comportamenti extra e ricevere informazioni riguardo all'evento.
```lua
mieimob.registered_on_death = {}
function mieimob.register_on_death(func)
table.insert(mieimob.registered_on_death, func)
end
-- nel codice della morte del mob
for i=1, #mieimob.registered_on_death do
mieimob.registered_on_death[i](entity, reason)
end
```
Quindi l'altro codice registra ciò che gli serve:
```lua
mieimob.register_on_death(function(mob, reason)
if reason.type == "punch" and reason.object and
reason.object:is_player() then
trofei.avvisa_morte_mostro(reason.object, mob.name)
end
end)
```
Potresti star pensando "aspetta un secondo, questo mi sembra terribilmente familiare".
E hai ragione! L'API di Minetest è molto incentrata sull'Osservatore, per far in modo che il motore di gioco non debba preoccuparsi di cosa è in ascolto di cosa.
## 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).
Un modo per fare ciò è pensare a:
* Che **dati** si hanno;
* Che **azioni** si possono eseguire con quei dati;
* Come gli **eventi** (come formspec, pugni ecc.) inneschino queste azioni, e come quest'ultime abbiano conseguenze nel motore di gioco.
Prendiamo come esempio una mod di protezione del terreno.
I dati di cui si dispone sono quelli delle aree e i metadati ad esse associati.
Le azioni richieste sono `crea`, `modifica` o `cancella`.
Gli eventi che richiamano le azioni sono invece comandi via chat e formspec.
Durante i test sarà possibile assicurarsi che un'azione, quando richiamata, faccia quello che deve fare ai dati.
Non c'è bisogno di testare l'evento che chiama l'azione (ciò richiederebbe usare l'API di Minetest, e l'area di codice dovrebbe comunque rimanere quanto più piccola possibile).
Si dovrebbe scrivere la rappresentazione dei dati usando Lua puro.
"Puro" in questo contesto significa che le funzioni potrebbero venir eseguite al di fuori di Minetest - nessuna delle funzioni del motore di gioco vengono chiamate.
```lua
-- Dati
function terreno.crea(nome, nome_area)
terreno.terreni[nome_area] = {
nome = nome_area,
owner = nome,
-- altre cose
}
end
function terreno.ottieni_da_nome(nome_area)
return terreno.terreni[nome_area]
end
```
Anche le azioni dovrebbero essere pure, ma chiamare altre funzioni è più accettato che il comportamento qui sopra.
```lua
-- Controllo
function terreno.gestore_invio_crea(nome, nome_area)
-- processa cose
-- (tipo controllare se ci sono sovrapposizioni, controllo dei permessi ecc)
terreno.crea(nome, nome_area)
end
function terreno.gestore_richiesta_crea(nome)
-- questo è un cattivo esempio, come spiegato poco più avanti
terreno.mostra_formspec_crea(nome)
end
```
I gestori degli eventi dovranno interagire con la API di Minetest.
Il numero di calcoli dovrebbero essere minimizzati il più possibile, in quanto non sarà fattibile testare quest'area così facilmente.
```lua
-- Vista
function terreno.mostra_formspec_crea(nome)
-- nota come qui non ci siano calcoli complessi!
return [[
size[4,3]
label[1,0;Questo è un esempio]
field[0,1;3,1;nome_area;]
button_exit[0,2;1,1;esci;Esci]
]]
end
core.register_chatcommand("/land", {
privs = { terreno = true },
func = function(name)
land.gestore_richiesta_crea(name)
end,
})
core.register_on_player_receive_fields(function(player,
formname, fields)
terreno.gestore_invio_crea(player:get_player_name(),
fields.nome_area)
end)
```
L'approccio adottato qui in alto è la struttura Modello-Vista-Controllo (MVC).
Il modello è un insieme di dati aventi funzioni minime.
La vista è un insieme di funzioni che sono in ascolto di eventi per passarli al controllo, e che riceve inoltre chiamate dal controllo per fare qualcosa con l'API di Minetest.
Il controllo è dove le decisioni vengono prese e la maggior parte delle operazioni eseguite.
Il controllo non dovrebbe avere nessuna conoscenza riguardo l'API di Minetest - nota come non ci siano chiamate a Minetest o funzioni nella vista che le ricordino.
*NON* dovresti, quindi, avere una funzione come `vista.hud_aggiungi(giocatore, def)`.
Al contrario, la vista definisce alcune azioni che il controllo può dirle di fare, come `vista.hud_aggiungi(info)` dove `info` è un valore o una tabella che non è imparentata in alcun modo con l'API di Minetest.
<figure class="right_image">
<img
width="100%"
src="{{ page.root }}/static/mvc_diagram.svg"
alt="Diagramma che mostra la struttura MVC (Modello-Vista-Controllo)">
</figure>
È importante che ogni area comunichi soltanto con i suoi diretti vicini, per ridurre il più possibile la quantità di codice da cambiare al modificare qualcosa.
Per esempio, per cambiare il formspec avrai bisogno di modificare solo la vista.
Per cambiare la API della vista, la vista e il controllo - ma non il modello.
In pratica, questo approccio è raramente utilizzato per via della sua alta complessità e perché non dà molti benefici alla maggior parte delle mod.
Al contrario, un approccio più comune e leggermente meno rigido è quello API-Vista.
### API-Vista
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.
* **Vista** - la vista, esattamente come quella spiegata sopra.
È buona norma strutturare questa parte in file separati per ogni tipo di evento.
La [crafting mod](https://github.com/rubenwardy/crafting) di rubenwardy segue ben o male questo schema: `api.lua` è quasi tutto puro Lua che gestisce lo spazio d'archiviazione e i calcoli che farebbe il controllo, `gui.lua` mostra e invia i formspec, e `async_crafter.lua` è la vista e il controllo dei timer e i formspec nei nodi.
Separare la mod in questa maniera permette di testare molto facilmente la API, in quanto non passa per quella di Minetest - come mostrato nel [prossimo capitolo](unit_testing.html).
## Conclusione
Cosa sia del buon codice è soggettivo, e dipende altamente dal progetto che si vuole realizzare.
Come regola generale, cerca di tenere un'alta coesione (parti di codice tra loro connesse vicine) e poche dipendenze.
Suggerisco caldamente, per chi mastica l'inglese, di leggere il libro [Game Programming Patterns](http://gameprogrammingpatterns.com/).
Lo si può leggere gratuitamente [online](http://gameprogrammingpatterns.com/contents.html) e descrive molto più nel dettaglio gli approcci di programmazione da tenere quando si parla di videogiochi.

View File

@ -0,0 +1,127 @@
---
title: Errori comuni
layout: default
root: ../..
idx: 8.1
redirect_from: /it/chapters/common_mistakes.html
---
## Introduzione <!-- omit in toc -->
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 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à)
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.
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:
```lua
-- questo codice funziona solo da Minetest 5.2 in poi
if obj:get_pos() then
-- è valido!
end
```
## Non fidarti dei campi dei formspec
Client malevoli possono compilare i campi nei formspec quando vogliono, con qualsiasi contenuto vogliono.
Per esempio, il seguente codice presenta una vulnerabilità che permette ai giocatori di assegnarsi da soli il privilegio di moderatore:
```lua
local function show_formspec(name)
if not core.check_player_privs(name, { privs = true }) then
return false
end
core.show_formspec(name, "modman:modman", [[
size[3,2]
field[0,0;3,1;target;Nome;]
button_exit[0,1;3,1;sub;Promuovi]
]])
return true
})
core.register_on_player_receive_fields(function(player,
formname, fields)
-- MALE! Manca il controllo dei privilegi!
local privs = core.get_player_privs(fields.target)
privs.kick = true
privs.ban = true
core.set_player_privs(fields.target, privs)
return true
end)
```
Aggiungi un controllo dei privilegi per ovviare:
```lua
core.register_on_player_receive_fields(function(player,
formname, fields)
if not core.check_player_privs(name, { privs = true }) then
return false
end
-- code
end)
```
## Imposta gli ItemStack dopo averli modificati
Se ci si fa caso, nella documentazione si parla di `ItemStack` e non `ItemStackRef`.
Questo perché gli ItemStack NON sono un riferimento, bensì una copia.
Questo vuol dire che modificando la copia, non si modificherà in automatico anche l'originale.
Sbagliato:
```lua
local inv = player:get_inventory()
local pila = inv:get_stack("main", 1) -- lo copio
pila:get_meta():set_string("description", "Un po' smangiucchiato")
-- MALE! Le modifiche saranno perse
```
Giusto:
```lua
local inv = player:get_inventory()
local pila = inv:get_stack("main", 1) -- lo copio
pila:get_meta():set_string("description", "Un po' smangiucchiato")
inv:set_stack("main", 1, pila)
-- Corretto! L'ItemStack è stato cambiato con la copia
```
Il comportamento dei callback è leggermente più complicato.
```lua
core.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
end)
```
Se nessun callback cancella l'operazione, la pila sarà impostata e la descrizione aggiornata; ma se un callback effettivamente cancella l'operazione, l'aggiornamento potrebbe andar perduto.
È meglio quindi fare così:
```lua
core.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(),
itemstack)
-- Corretto! La descrizione verrà sempre aggiornata
end)
```

101
_it/quality/luacheck.md Normal file
View File

@ -0,0 +1,101 @@
---
title: Controllo automatico degli errori
layout: default
root: ../..
idx: 8.2
description: Usa LuaCheck per trovare eventuali errori
redirect_from: /it/chapters/luacheck.html
---
## Introduzione <!-- omit in toc -->
In questo capitolo, imparerai come usare uno strumento chiamato LuaCheck per scansionare automaticamente le tue mod alla ricerca di eventuali errori.
LuaCheck può essere usato in combinazione con l'editor per fornire avvertimenti vari.
- [Installare LuaCheck](#installare-luacheck)
- [Windows](#windows)
- [Linux](#linux)
- [Eseguire LuaCheck](#eseguire-luacheck)
- [Configurare LuaCheck](#configurare-luacheck)
- [Risoluzione problemi](#risoluzione-problemi)
- [Uso nell'editor](#uso-nelleditor)
## Installare LuaCheck
### Windows
Basta scaricare luacheck.exe dall'apposita [pagina delle versioni su Github](https://github.com/mpeterv/luacheck/releases).
### Linux
Per prima cosa, avrai bisogno di installare LuaRocks:
sudo apt install luarocks
Poi va installato globalmente LuaCheck:
sudo luarocks install luacheck
Per controllare che sia stato installato correttamente, fai:
luacheck -v
## Eseguire LuaCheck
La prima volta che si esegue LuaCheck, segnalerà probabilmente un sacco di falsi errori.
Questo perché ha ancora bisogno di essere configurato.
Su Windows, apri la powershell o la bash nella cartella principale del tuo progetto ed esegui `path\to\luacheck.exe .`
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.
Inserisci il seguente codice all'interno:
```lua
unused_args = false
allow_defined_top = true
globals = {
"minetest",
}
read_globals = {
string = {fields = {"split"}},
table = {fields = {"copy", "getn"}},
-- Builtin
"vector", "ItemStack",
"dump", "DIR_DELIM", "VoxelArea", "Settings",
-- MTG
"default", "sfinv", "creative",
}
```
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.
### Risoluzione problemi
* **accessing undefined variable foobar** - Se `foobar` dovrebbe essere una variabile globale, aggiungila a `read_globals`.
Altrimenti, manca un `local` vicino a `foobar`.
* **setting non-standard global variable foobar** - Se `foobar` dovrebbe essere una variabile globale, aggiungila a `globals`.
Rimuovila da `read_globals` se presente.
Altrimenti, manca un `local` vicino a `foobar`.
* **mutating read-only global variable 'foobar'** - Sposta `foobar` da `read_globals` a `globals`, o smetti di modificare `foobar`.
## Uso nell'editor
È 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:
* **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).

26
_it/quality/readmore.md Normal file
View File

@ -0,0 +1,26 @@
---
title: Per approfondire
layout: default
root: ../..
idx: 8.7
redirect_from: /it/chapters/readmore.html
---
## Lista di risorse
Dopo aver letto questo libro, se mastichi l'inglese dai un occhio a ciò che segue:
### Moddaggio 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).
* Spulcia le [mod esistenti](https://forum.minetest.net/viewforum.php?f=11).
### Programmazione in Lua
* [Programmazione in Lua (PIL)](http://www.lua.org/pil/).
### Modellazione 3D
* [Blender 3D: Noob to pro](https://en.wikibooks.org/wiki/Blender_3D:_Noob_to_Pro).
* [Usare Blender con Minetest](http://wiki.minetest.net/Using_Blender).

144
_it/quality/releasing.md Normal file
View File

@ -0,0 +1,144 @@
---
title: Rilasciare una mod
layout: default
root: ../..
idx: 8.6
redirect_from: /it/chapters/releasing.html
---
## Introduzione <!-- omit in toc -->
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)
- [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)
- [screenshot.png](#screenshotpng)
- [Caricare](#caricare)
- [Sistemi di Controllo Versione](#sistemi-di-controllo-versione)
- [Rilasciare su ContentDB](#rilasciare-su-contentdb)
- [Creare la discussione sul forum](#creare-la-discussione-sul-forum)
## Scegliere una 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.
Se una mod non ha una licenza, la gente non saprà cosa sarà autorizzata a modificare, distribuire o se potrà usarla su un server pubblico.
Le licenze variano anche a seconda di cosa si vuole tutelare: per esempio, le Creative Commons non dovrebbero essere usate per il codice sorgente, ma sono un'ottima opzione per cose come immagini, testi e modelli 3D.
Puoi adottare la licenza che preferisci; tuttavia, sappi che le mod con licenze che ne vietano lavori derivati sono bandite dal forum ufficiale di Minetest (in altre parole, per essere consentita sul forum, gli altri sviluppatori devono poter essere in grado di modificarla e rilasciarne la versione modificata).
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.
Si imposta il codice sotto LGPL 2.1 e i contenuti artistici sotto CC-BY-SA.
Ciò significa che:
* Chiunque può modificare, ridistribuire e vendere versioni modificate e non;
* Se qualcuno modifica la tua mod, deve adottare la stessa licenza;
* Devono citare l'autore originale.
### 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.
### MIT
Questa è una licenza comune per il codice.
La differenza con la LGPL è che le copie derivate in questo caso non devono per forza essere libere (i primi 2 punti della LGPL/GPL), bensì può essere trasformato in codice proprietario.
## Impacchettare
Ci sono alcuni file che è consigliato includere nelle proprie mod e nei propri giochi prima di rilasciarli.
### README.txt
Il README dovrebbe dichiarare:
* Cosa fa la mod/gioco;
* Come si usa;
* 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
Assicurati di aggiungere una descrizione che spieghi cosa fa la mod o il gioco, usando la chiave `description`.
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
Sconsigliato:
description = 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.
## 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:
* **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.
### 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.
La maggior parte dei creatori di mod su Minetest usa Git e un sito per ospitare il loro codice come GitHub o GitLab.
Usare git 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
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.
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*).
## 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.
Il titolo della discussione deve seguire uno dei seguenti formati:
* [Mod] Nome mod [nomemod]
* [Mod] Nome mod [numero versione] [nomemod]
Per esempio:
* [Mod] Blocchi pazzi [0.1] [blocchipazzi]

93
_it/quality/security.md Normal file
View File

@ -0,0 +1,93 @@
---
title: Sicurezza
layout: default
root: ../..
idx: 8.3
---
## Introduzione <!-- omit in toc -->
La sicurezza è molto importante per evitare che una mod permetta di far perdere il controllo del server al suo proprietario.
- [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)
- [Ambienti (non sicuri)](#ambienti-non-sicuri)
## Concetti fondamentali
Il concetto più importante quando si parla di sicurezza è **non fidarsi mai dell'utente**.
Ogni cosa che l'utente può inviare al server deve essere trattata come malevola.
Questo significa che dovresti sempre controllare che le informazioni da loro immesse siano valide, che abbiano i privilegi necessari e che siano autorizzati a fare determinate azioni.
Un'azione malevola non è necessariamente la modifica o la distruzione di dati, ma può essere anche l'accedere a dati sensibili, come gli hash delle password o i messaggi privati.
Questo è grave soprattutto se il server possiede informazioni sugli utenti come le loro e-mail o la loro età, che alcuni potrebbero richiedere per questioni di verifica.
## Formspec
### Non fidarsi mai dei campi dei formspec
Qualsiasi utente può inviare qualsiasi formspec con i valori che preferisce quando preferisce.
Segue del codice trovato realmente in una mod:
```lua
core.register_on_player_receive_fields(function(player,
formname, fields)
for key, field in pairs(fields) do
local x,y,z = string.match(key,
"goto_([%d-]+)_([%d-]+)_([%d-]+)")
if x and y and z then
player:set_pos({ x=tonumber(x), y=tonumber(y),
z=tonumber(z) })
return true
end
end
end
```
Riesci a vedere il problema? Un utente malintenzionato potrebbe inviare un formspec contenente la propria posizione, permettendogli di venire teletrasportato dovunque vuole.
Addirittura il tutto potrebbe essere automatizzato usando modifiche del client per replicare il comportamento di `/teleport` senza aver bisogno di alcun privilegio.
La soluzione per questo tipo di problematica è usare un [Contesto](../players/formspecs.html#contexts), come mostrato precedentemente nel capitolo dei Formspec.
### Il momento per controllare non è il momento dell'uso (Time of Check is not Time of Use)
Qualsiasi utente può inviare qualsiasi formspec con i valori che preferisce quando preferisce, sì: a meno che il motore di gioco non glielo impedisca:
* L'invio dei formspec di un nodo vengono bloccati se l'utente è troppo distante;
* Dalla 5.0 in poi, i formspec con un nome sono bloccati se non sono stati ancora mostrati.
Questo significa che dovresti controllare che l'utente soddisfi i requisiti per visualizzare il formspec in primis, esattamente come per ogni azione corrispondente.
La vulnerabilità causata dal controllare i privilegi nel `show_formspec` ma non nella gestione del formspec in primis è chiamata *Time Of Check is not Time Of Use* (Il momento per controllare non è il momento dell'uso), o più brevemente TOCTOU.
## Ambienti (non sicuri)
Minetest permette alle mod di richiedere ambienti senza limiti, dando loro accesso all'intera API Lua.
Riesci a individuare la vulnerabilità in questo pezzo di codice??
```lua
local ie = core.request_insecure_environment()
ie.os.execute(("path/to/prog %d"):format(3))
```
`string.format` è una funzione nella tabella globale condivisa `string`.
Una mod malevola potrebbe sovrascrivere questa funzione e passare "cose" a `os.execute`:
```lua
string.format = function()
return "xdg-open 'http://esempio.com'"
end
```
La mod potrebbe passare qualcosa di molto più malevolo dell'apertura di un sito, come dare il controllo remoto della macchina al malintenzionato in questione.
Alcune regole per usare un ambiente non sicuro:
* Tenerlo sempre in una variabile locale e non passarlo mai a una funzione;
* Assicurarsi di potersi fidare di qualsiasi input eseguita in una funzione insicura, per evitare il problema sopracitato.
Questo significa evitare funzioni globali ridefinibili.

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

@ -0,0 +1,153 @@
---
title: Traduzione
layout: default
root: ../..
idx: 8.05
marked_text_encoding:
level: info
title: Marked Text Encoding
message: |
Non hai davvero bisogno di capire come funziona il testo formattato, ma potrebbe aiutarti a capire meglio.
```
"\27(T@miamod)Hello everyone!\27E"
```
* `\27` è il carattere di escape - è usato per dire a Minetest di far attenzione, in quanto sta per seguire qualcosa di speciale. È usato sia per le traduzioni che per la colorazione del testo.
* `(T@miamod)` dice che il testo a seguire è traducibile usando il dominio testuale di `miamod`.
* `Hello everyone!` è il testo in inglese da tradurre, passato alla funzione di traduzione.
* `\27E` è di nuovo il carattere di escape, dove `E` è usato per segnalare che si è arrivati alla fine.
---
## Introduzione <!-- omit in toc -->
Aggiungere il supporto per le traduzioni nelle tue mod e giochi dà la possibilità a più persone di gustarsele.
Stando a Google Play, il 64% dei giocatori di Minetest Android non usano l'inglese come prima lingua.
Per quanto Minetest non tenga traccia di questo parametro nelle altre piattaforme, vien comunque da sé pensare che una buona parte di giocatrici e giocatori non siano madrelingua inglesi.
Minetest ti permette di localizzare i tuoi contenuti in tante lingue diverse, chiedendoti il testo base in inglese, seguito da dei file di traduzione che mappano le parole/frasi equivalenti nelle altre lingue. Questo processo di traduzione è fatto a lato client, cosicché ogni giocatore possa vedere il contenuto nella propria lingua (se disponibile).
- [Come funziona la traduzione lato client?](#come-funziona-la-traduzione-lato-client)
- [Testo formattato](#testo-formattato)
- [File di traduzione](#file-di-traduzione)
- [Formattare una stringa](#formattare-una-stringa)
- [Buona prassi per una buona traduzione](#buona-prassi-per-una-buona-traduzione)
- [Traduzioni lato server](#traduzioni-lato-server)
- [Per concludere](#per-concludere)
## Come funziona la traduzione lato client?
### Testo formattato
Il server ha bisogno di dire ai client come tradurre il testo.
Questo accade grazie alla funzione `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.

160
_it/quality/unit_testing.md Normal file
View File

@ -0,0 +1,160 @@
---
title: Testing d'unità automatici
layout: default
root: ../..
idx: 8.5
---
## Introduzione <!-- omit in toc -->
I testing d'unità sono uno strumento essenziale nell'assicurarsi che il codice sia corretto.
Questo capitolo ti mostrerà come scrivere questi per le mod e i giochi di Minetest usando Busted.
Scrivere i testing d'unità per le funzioni dove vengono chiamate quelle di Minetest è alquanto difficile, ma per fortuna abbiamo già discusso [nel capitolo precedente](clean_arch.html) come strutturare il codice in modo da non complicarci la vita.
- [Installare Busted](#installare-busted)
- [Il tuo primo test](#il-tuo-primo-test)
- [init.lua](#initlua)
- [api.lua](#apilua)
- [tests/api_spec.lua](#testsapi_speclua)
- [Simulare: usare funzioni esterne](#simulare-usare-funzioni-esterne)
- [Conclusione](#conclusione)
## Installare Busted
Prima di tutto, c'è bisogno di installare LuaRocks.
* Windows: segui le istruzioni sulla [wiki di LuaRocks](https://github.com/luarocks/luarocks/wiki/Installation-instructions-for-Windows).
* Debian/Ubuntu Linux: `sudo apt install luarocks`
Poi, dovresti installare Busted a livello globale:
sudo luarocks install busted
Infine, controlla che sia installato:
busted --version
## Il tuo primo test
Busted è il quadro strutturale (o *framework*) per eccellenza di Lua.
Quello che fa è cercare i file Lua con il nome che termina in `_spec`, eseguendoli poi in un ambiente Lua a sé stante.
miamod/
├── init.lua
├── api.lua
└── test
└── api_spec.lua
### init.lua
```lua
miamod = {}
dofile(core.get_modpath("miamod") .. "/api.lua")
```
### api.lua
```lua
function miamod.somma(x, y)
return x + y
end
```
### tests/api_spec.lua
```lua
-- Cerca le cose necessarie in package.path = "../?.lua;" .. package.path
-- Imposta la globale miamod per far sì che l'API possa scriverci sopra
_G.mymod = {} --_
-- Esegue il file api.lua
require("api")
-- Test vari
describe("somma", function()
it("aggiunge", function()
assert.equals(2, miamod.somma(1, 1))
end)
it("supporta valori negativi", function()
assert.equals(0, miamod.somma(-1, 1))
assert.equals(-2, miamod.somma(-1, -1))
end)
end)
```
Puoi ora eseguire i vari test aprendo un terminale nella cartella della mod ed eseguendo `busted .`.
È importante che il file dell'API non crei da sé la tabella, in quanto le variabili globali su Busted funzionano diversamente.
Ogni variabile che dovrebbe essere globale su Minetest è invece un file locale su Busted.
Sarebbe stato un modo migliore per Minetest di gestire le cose, ma è ormai troppo tardi per renderlo realtà.
Un'altra cosa da notare è che qualsiasi file si stia testando, bisognerebbe evitare che chiami funzioni al di fuori di esso.
Si tende infatti a scrivere i test che controllino un solo file alla volta.
## Simulare: usare funzioni esterne
Il simulare (*mocking*) è la pratica di sostituire le funzioni dalle quali la parte di codice da testare è dipendente.
Questo può avere due obiettivi: il primo, la funzione potrebbe non essere disponibile nell'area di testing; il secondo, si potrebbero voler catturare le chiamate alla funzione e gli argomenti da essa passati.
Se si sono seguiti i consigli nel capitolo delle [Architetture pulite](clean_arch.html), si avrà già un file bello pronto da testare, anche se si dovrà comunque simulare le cose non contenute nell'area di testing (per esempio, la vista quando si testa il controllo/API).
Se invece si è deciso di lasciar perdere quella parte, allora le cose sono un po' più complicate in quanto ci sarà da simulare anche la API di Minetest.
```lua
-- come prima, crea una tabella
_G.minetest = {}
-- Definisce la funzione simulata
local chiamate_chat_send_all = {}
function core.chat_send_all(name, message)
table.insert(chiamate_chat_send_all, { nome = name, messaggio = message })
end
-- Test
describe("elenca_aree", function()
it("ritorna una riga per ogni area", function()
chiamate_chat_send_all = {} -- resetta la tabella
miamod.elenca_aree_chat("singleplayer", "singleplayer")
assert.equals(2, #chiamate_chat_send_all)
end)
it("invia al giocatore giusto", function()
chiamate_chat_send_all = {} -- resetta la tabella
miamod.elenca_aree_chat("singleplayer", "singleplayer")
for _, chiamata in pairs(chiamate_chat_send_all) do --_
assert.equals("singleplayer", chiamata.nome)
end
end)
-- I due test qui in alto in verità sono inutili in quanto
-- questo li esegue entrambi
it("ritorna correttamente", function()
chiamate_chat_send_all = {} -- resetta la tabella
miamod.elenca_aree_chat("singleplayer", "singleplayer")
local previsto = {
{ nome = "singleplayer", messaggio = "Town Hall (2,43,63)" },
{ nome = "singleplayer", messaggio = "Airport (43,45,63)" },
}
assert.same(previsto, chiamate_chat_send_all)
end)
end)
```
## 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).

56
_layouts/base.html Normal file
View File

@ -0,0 +1,56 @@
---
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>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width">
<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">
</head>
<body>
<div id="container">
{{ content }}
</div>
</body>
</html>

View File

@ -1,86 +1,74 @@
---
layout: compress
layout: base
---
<!doctype html>
<html>
<head>
<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">
<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">
</head>
<body>
{% assign pathsplit = page.dir | split: '/' %}
{% assign language = pathsplit[1] %}
{% assign pathsplit = page.path | split: '/' %}
{% if language == "en" %}
{% assign links = site.en | sort: "idx" %}
{% else %}
{% assign language = "en" %}
{% assign links = site.en | sort: "idx" %}
{% endif %}
{% assign language = pathsplit[0] %}
{% assign num = 0 %}
{% if language == "_it" %}
{% assign language = "it" %}
{% assign links = site.it %}
{% else %}
{% assign language = "en" %}
{% assign links = site.en %}
{% endif %}
{% assign links = links | where_exp: "item", "item.sitemap != false" | sort: "idx" %}
<div id="container">
<nav>
{% for link in links %}
{% assign idsplit = link.id | split: '/' %}
{% assign section = idsplit[2] %}
<li>
<a href="{{ page.root }}{{ link.url }}"
class="{% if page.title == link.title %}selected{% endif %}{% if section != last_section and section != 'index' %} hr {% endif %}">
{% if section != "index" %}{{ num }} - {% endif %}
{{ link.title }}
</a>
</li>
{% assign num = 0 %}
{% assign last_section = section %}
{% assign num = num | plus:1 %}
{% endfor %}
<nav>
{% for link in links %}
{% assign idsplit = link.id | split: '/' %}
{% assign section = idsplit[2] %}
<li>
<a href="{{ page.root }}{{ link.url }}"
class="{% if page.title == link.title %}selected{% endif %}{% if section != last_section and section != 'index' %} hr {% endif %}">
{% if section != "index" %}{{ num }} - {% endif %}
{{ link.title }}
</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>
{% 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>
</nav>
<main>
<article {% if page.homepage %}class="homepage"{% endif %}>
{% if page.no_header %}{% else %}<h1>{{ page.title }}</h1>{% endif %}
{{ content }}
</article>
<main>
<article {% if page.homepage %}class="homepage"{% endif %}>
<a href="{{ page.root }}/languages.html" class="language-switcher">
<img src="{{ page.root }}/static/languages.svg" alt="Choose a language">
<span>{{ language }}</span>
</a>
{% if page.no_header %}{% else %}<h1>{{ page.title }}</h1>{% endif %}
{{ content }}
</article>
{% for link in links %}
{% if link.title == page.title %}
{% unless forloop.first %}
{% assign prev = tmpprev %}
{% endunless %}
{% unless forloop.last %}
{% assign next = links[forloop.index] %}
{% endunless %}
{% endif %}
{% assign tmpprev = link %}
{% endfor %}
{% for link in links %}
{% if link.title == page.title %}
{% unless forloop.first %}
{% assign prev = tmpprev %}
{% endunless %}
{% unless forloop.last %}
{% assign next = links[forloop.index] %}
{% endunless %}
{% endif %}
{% assign tmpprev = link %}
{% endfor %}
<ul class="prevnext">
<li>{% if prev %}<a href="{{ page.root }}{{ prev.url}}">&lt; {{ prev.title }}</a>{% endif %}</li>
<li>{% if next %}<a href="{{ page.root }}{{ next.url}}">{{ next.title }} &gt;</a>{% endif %}</li>
</ul>
<ul class="prevnext">
<li>{% if prev %}<a href="{{ page.root }}{{ prev.url}}">&lt; {{ prev.title }}</a>{% endif %}</li>
<li>{% if next %}<a href="{{ page.root }}{{ next.url}}">{{ next.title }} &gt;</a>{% endif %}</li>
</ul>
<footer>
&copy; 2014-8 |
Helpful? Consider
<a href="https://rubenwardy.com/donate/">donating</a>
to support my work.
</footer>
</article>
</main>
<script async src="https://code.jquery.com/jquery-1.11.2.min.js"></script>
<script async src="{{ page.root }}/static/script.js"></script>
</body>
</html>
<footer>
&copy; 2014-{{ site.time | date: '%Y' }}
{% if language == "en" %}
| Helpful? Consider
<a href="https://rubenwardy.com/donate/">donating</a>
to support my work.
{% endif %}
</footer>
</main>

View File

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

121
_sass/_feedback.scss Normal file
View File

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

View File

@ -83,6 +83,24 @@ article {
// text-align: justify;
}
.language-switcher {
display: flex;
align-items: center;
float: right;
color: black;
text-decoration: none;
padding-top: 0.3rem;
img, span {
display: inline-block;
}
img {
height: 1.2em;
padding-right: 0.3em;
}
}
footer {
margin: 0 0 20px 0;
padding: 1em 1.5em;
@ -101,16 +119,16 @@ footer a:hover {
text-decoration: underline;
}
#header {
header {
text-align: center;
padding: 100px 0;
}
#header h1 {
header h1 {
padding-bottom: 20px;
}
#header span {
header span {
display: block;
padding: 6px;
}
@ -119,6 +137,10 @@ footer a:hover {
font-size: 200%;
}
.book-only {
display: none;
}
@media all and (max-height: 568px) {
nav {
position: absolute;
@ -176,3 +198,10 @@ footer a:hover {
position: relative;
}
}
@import "content";
@import "code";
@import "notice";
@import "table";
@import "feedback";

61
_sass/_notice.scss Normal file
View File

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

17
_sass/_table.scss Normal file
View File

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

View File

@ -1,14 +0,0 @@
---
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 %}

29
comment_received.html Normal file
View File

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

View File

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

24
languages.html Normal file
View File

@ -0,0 +1,24 @@
---
title: Languages
layout: base
root: .
---
<main>
<article>
<h1>Luanti Modding Book (formerly Minetest)</h1>
<h2>Choose a Language</h2>
<ul>
{% for lang in site.data.languages %}
<li><a href="{{ lang.code }}/index.html">{{ lang.name }}</a></li>
{% endfor %}
</ul>
</article>
<footer>
&copy; 2014-{{ site.time | date: '%Y' }}
</footer>
</main>

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,7 @@
---
---
{% assign pages = site.en | sort: "idx" %}
{% assign pages = site.en | where_exp: "item", "item.sitemap != false" | sort: "idx" %}
{% assign num = 0 %}
[
@ -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,14 +22,6 @@
{% 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/biomes_voronoi.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

1
static/languages.svg Normal file
View File

@ -0,0 +1 @@
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="language" class="svg-inline--fa fa-language fa-w-20" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><path fill="currentColor" d="M152.1 236.2c-3.5-12.1-7.8-33.2-7.8-33.2h-.5s-4.3 21.1-7.8 33.2l-11.1 37.5H163zM616 96H336v320h280c13.3 0 24-10.7 24-24V120c0-13.3-10.7-24-24-24zm-24 120c0 6.6-5.4 12-12 12h-11.4c-6.9 23.6-21.7 47.4-42.7 69.9 8.4 6.4 17.1 12.5 26.1 18 5.5 3.4 7.3 10.5 4.1 16.2l-7.9 13.9c-3.4 5.9-10.9 7.8-16.7 4.3-12.6-7.8-24.5-16.1-35.4-24.9-10.9 8.7-22.7 17.1-35.4 24.9-5.8 3.5-13.3 1.6-16.7-4.3l-7.9-13.9c-3.2-5.6-1.4-12.8 4.2-16.2 9.3-5.7 18-11.7 26.1-18-7.9-8.4-14.9-17-21-25.7-4-5.7-2.2-13.6 3.7-17.1l6.5-3.9 7.3-4.3c5.4-3.2 12.4-1.7 16 3.4 5 7 10.8 14 17.4 20.9 13.5-14.2 23.8-28.9 30-43.2H412c-6.6 0-12-5.4-12-12v-16c0-6.6 5.4-12 12-12h64v-16c0-6.6 5.4-12 12-12h16c6.6 0 12 5.4 12 12v16h64c6.6 0 12 5.4 12 12zM0 120v272c0 13.3 10.7 24 24 24h280V96H24c-13.3 0-24 10.7-24 24zm58.9 216.1L116.4 167c1.7-4.9 6.2-8.1 11.4-8.1h32.5c5.1 0 9.7 3.3 11.4 8.1l57.5 169.1c2.6 7.8-3.1 15.9-11.4 15.9h-22.9a12 12 0 0 1-11.5-8.6l-9.4-31.9h-60.2l-9.1 31.8c-1.5 5.1-6.2 8.7-11.5 8.7H70.3c-8.2 0-14-8.1-11.4-15.9z"></path></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

View File

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

View File

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

View File

@ -1,112 +0,0 @@
import markdown, urllib2, datetime, re, string
from bs4 import BeautifulSoup
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 = urllib2.urlopen(url).read()
text = unicode(text, "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")