Compare commits

...

No commits in common. "master" and "examples" have entirely different histories.

142 changed files with 463 additions and 13725 deletions

88
.gitignore vendored
View File

@ -1,88 +0,0 @@
# 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 ###
*~
# temporary files which can be created if a process still has a handle open of a deleted file
.fuse_hidden*
# KDE directory preferences
.directory
# Linux trash folder which might appear on any partition or disk
.Trash-*
# .nfs files are created when an open file is removed but is still being accessed
.nfs*
### Ruby ###
*.gem
*.rbc
/.config
/coverage/
/InstalledFiles
/pkg/
/spec/reports/
/spec/examples.txt
/test/tmp/
/test/version_tmp/
/tmp/
# Used by dotenv library to load environment variables.
# .env
# Ignore Byebug command history file.
.byebug_history
## Specific to RubyMotion:
.dat*
.repl_history
build/
*.bridgesupport
build-iPhoneOS/
build-iPhoneSimulator/
## Specific to RubyMotion (use of CocoaPods):
#
# 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:
/.yardoc/
/_yardoc/
/doc/
/rdoc/
## Environment normalization:
/.bundle/
/vendor/bundle
/lib/bundler/man/
# for a library or gem, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# Gemfile.lock
# .ruby-version
# .ruby-gemset
# unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
.rvmrc
# End of https://www.gitignore.io/api/ruby,code,linux,jekyll

View File

@ -1,31 +0,0 @@
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
View File

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

View File

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

View File

@ -0,0 +1 @@
default

View File

@ -0,0 +1,14 @@
print("This file will be run at load time!")
minetest.register_node("example:node", {
description = "This is a node",
tiles = {
"mymod_node.png",
"mymod_node.png",
"mymod_node.png",
"mymod_node.png",
"mymod_node.png",
"mymod_node.png"
},
groups = {cracky = 1}
})

0
1_folders/modpack.txt Normal file
View File

View File

@ -0,0 +1,6 @@
minetest.register_node("mymod:diamond", {
description = "Alien Diamond",
tiles = {"mymod_diamond.png"},
is_ground_content = true,
groups = {cracky=3, stone=1}
})

View File

@ -0,0 +1,13 @@
minetest.register_craftitem("mymod:diamond_chair", {
description = "Diamond Chair",
inventory_image = "mymod_diamond_chair.png"
})
minetest.register_craft({
output = "mymod:diamond_chair 99",
recipe = {
{"mymod:diamond_fragments", "", ""},
{"mymod:diamond_fragments", "mymod:diamond_fragments", ""},
{"mymod:diamond_fragments", "mymod:diamond_fragments", ""}
}
})

View File

@ -0,0 +1,4 @@
minetest.register_craftitem("mymod:diamond_fragments", {
description = "Alien Diamond Fragments",
inventory_image = "mymod_diamond_fragments.png"
})

View File

@ -0,0 +1,30 @@
minetest.register_craftitem("mymod:mudpie", {
description = "Alien Mud Pie",
inventory_image = "myfood_mudpie.png",
on_use = minetest.item_eat(20)
})
minetest.register_craftitem("mymod:mudpie_ex", {
description = "Alien Mud Pie Extended",
inventory_image = "myfood_mudpie.png",
on_use = function(itemstack, user, pointed_thing)
hp_change = 20
replace_with_item = nil
minetest.chat_send_player(user:get_player_name(), "You ate an alien mud pie!")
-- Support for hunger mods using minetest.register_on_item_eat
for _, callback in pairs(minetest.registered_on_item_eats) do
local result = callback(hp_change, replace_with_item, itemstack, user, pointed_thing)
if result then
return result
end
end
if itemstack:take_item() ~= nil then
user:set_hp(user:get_hp() + hp_change)
end
return itemstack
end
})

View File

View File

View File

@ -0,0 +1,184 @@
minetest.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,
groups = {cracky = 3},
drop = "mymod:diamond_fragments"
})
minetest.register_node("mymod: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 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.
-- (you can place a node and remove the air node
-- that used to be there)
air_equivalent = true,
drop = "",
groups = {not_in_creative_inventory=1}
})
-- Some properties have been removed as they are beyond the scope of this chapter.
minetest.register_node(":default:water_source", {
drawtype = "liquid",
paramtype = "light",
inventory_image = minetest.inventorycube("default_water.png"),
-- ^ this is required to stop the inventory image from being animated
tiles = {
{
name = "default_water_source_animated.png",
animation = {
type = "vertical_frames",
aspect_w = 16,
aspect_h = 16,
length = 2.0
}
}
},
special_tiles = {
-- New-style water source material (mostly unused)
{
name = "default_water_source_animated.png",
animation = {type = "vertical_frames", aspect_w = 16, aspect_h = 16, length = 2.0},
backface_culling = false,
}
},
--
-- Behavior
--
walkable = false, -- The player falls through
pointable = false, -- The player can't highlight it
diggable = false, -- The player can't dig it
buildable_to = true, -- Nodes can be replace this node
alpha = 160,
--
-- Liquid Properties
--
drowning = 1,
liquidtype = "source",
liquid_alternative_flowing = "default:water_flowing",
-- ^ when the liquid is flowing
liquid_alternative_source = "default:water_source",
-- ^ when the liquid is a source
liquid_viscosity = WATER_VISC,
-- ^ how far
post_effect_color = {a=64, r=100, g=100, b=200},
-- ^ color of screen when the player is submerged
})
minetest.register_node("mymod: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},
})
minetest.register_node("mymod: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()
})
minetest.register_node("mymod:leaves", {
description = "Leaves",
drawtype = "allfaces_optional",
tiles = {"default_leaves.png"}
})
minetest.register_node("mymod: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},
}
})
minetest.register_node("mymod: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},
},
}
})
minetest.register_node("mymod:sign", {
drawtype = "nodebox",
node_box = {
type = "wallmounted",
-- Ceiling
wall_top = {
{-0.4375, 0.4375, -0.3125, 0.4375, 0.5, 0.3125}
},
-- Floor
wall_bottom = {
{-0.4375, -0.5, -0.3125, 0.4375, -0.4375, 0.3125}
},
-- Wall
wall_side = {
{-0.5, -0.3125, -0.4375, -0.4375, 0.3125, 0.4375}
}
},
})

View File

@ -0,0 +1,17 @@
minetest.register_node("aliens:grass", {
description = "Alien Grass",
light_source = 3, -- The node radiates light. Values can be from 1 to 15
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)
minetest.set_node({x = pos.x, y = pos.y + 1, z = pos.z}, {name = "aliens:grass"})
end
})

View File

View File

@ -0,0 +1,9 @@
minetest.register_chatcommand("antigravity",
func = function(name, param)
local player = minetest.get_player_by_name(name)
player:set_physics_override({
gravity = 0.1 -- set gravity to 10% of its original value
-- (0.1 * 9.81)
})
end
})

View File

View File

@ -0,0 +1,26 @@
-- Show form when the /formspec command is used.
minetest.register_chatcommand("formspec", {
func = function(name, param)
minetest.show_formspec(name, "first_formspec: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 ~= "first_formspec:form" then
-- Formname is not mymod:form,
-- exit callback.
return false
end
-- Send message to player.
minetest.chat_send_player(player:get_player_name(), "You said: " .. fields.name .. "!")
-- Return true to stop other minetest.register_on_player_receive_fields
-- from receiving this submission.
return true
end)

View File

@ -0,0 +1,63 @@
guessing = {}
local _contexts = {}
minetest.register_on_leaveplayer(function(player)
_contexts[player:get_player_name()] = nil
end)
local function get_context(name)
local context = _contexts[name] or {}
_contexts[name] = context
return context
end
function guessing.get_formspec(name)
local context = get_context(name)
context.target = context.target or math.random(1, 10)
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 = "To high!"
else
text = "To low!"
end
local formspec = {
"size[6,3.476]",
"real_coordinates[true]",
"label[0.375,0.5;", minetest.formspec_escape(text), "]",
"field[0.375,1.25;5.25,0.8;number;Number;]",
"button[1.5,2.3;3,0.8;guess;Guess]"
}
-- table.concat is faster than ..
return table.concat(formspec, "")
end
function guessing.show_to(name)
minetest.show_formspec(name, "guessing:game", guessing.get_formspec(name))
end
minetest.register_chatcommand("game", {
func = function(name)
guessing.show_to(name)
end,
})
minetest.register_on_player_receive_fields(function(player, formname, fields)
if formname ~= "guessing:game" then
return
end
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
end)

0
7_formspecs/modpack.txt Normal file
View File

View File

@ -0,0 +1,51 @@
--
-- Step 1) set context when player requests the formspec
--
-- 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
end)

9
8_hud/basic_hud/init.lua Normal file
View File

@ -0,0 +1,9 @@
minetest.register_on_joinplayer(function(player)
local idx = player:hud_add({
hud_elem_type = "text",
position = {x = 1, y = 0},
offset = {x=-100, y = 20},
scale = {x = 100, y = 100},
text = "My Text"
})
end)

0
8_hud/modpack.txt Normal file
View File

View File

@ -0,0 +1,26 @@
My Super Special Mod
====================
Adds magic, rainbows and other special things.
Version: 1.1
Licence: LGPL 2.1 or later
Dependencies: default mod (found in minetest_game)
Report bugs or request help on the forum topic.
Installation
------------
Unzip the archive, rename the folder to to modfoldername and
place it in minetest/mods/minetest/
( Linux: If you have a linux system-wide installation place
it in ~/.minetest/mods/minetest/. )
( If you only want this to be used in a single world, place
the folder in worldmods/ in your worlddirectory. )
For further information or help see:
http://wiki.minetest.com/wiki/Installing_Mods

View File

@ -0,0 +1 @@
Adds magic, rainbows and other special things.

View File

@ -0,0 +1,2 @@
-- Nothing here!
print("9_releasing_a_mod: this mod does nothing!")

10
Gemfile
View File

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

View File

@ -1,75 +0,0 @@
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
View File

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

136
README.md
View File

@ -1,136 +0,0 @@
# 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.
License: CC-BY-SA 3.0
## Finding your way around
* `_data/` - Contains list of languages
* `_layouts/` - Layouts to wrap around each page.
* `static/` - CSS, images, scripts.
* `_<lang>/`
* `<section>/` - Markdown files for each chapter.
## Contributing chapters
* Create a pull request with a new chapter in markdown.
* Write a new chapter in the text editor of your choice and
[send them to me](https://rubenwardy.com/contact/).
I'm happy to fix the formatting of any chapters. It is
the writing which is the hard bit, not the formatting.
### Chapter and Writing Guide
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.
### Making a Chapter
To create a new chapter, make a new file in _en/section/.
Name it something that explains what the chapter is about.
Replace spaces with underscores ( _ )
```markdown
---
title: Chapter Name
layout: default
root: ..
idx: 4.5
long_notice:
level: tip
title: This is a long tip!
message: This is a very long tip, so it would be unreadable if
placed in the main body of the chapter. Therefore,
it is a good idea to put it in the frontmatter instead.
---
## Chapter Name
Write a paragraph or so explaining what will be covered in this chapter.
Explain why/how these concepts are useful in modding
* [List the](#list-the)
* [Parts in](#parts-in)
* [This Chapter](#this-chapter)
## List the
{% include notice.html notice=page.long_notice %}
Paragraphs
\```lua
code
\```
## Parts in
## This Chapter
```
## Commits
If you are editing or creating a particular chapter, then use commit messages like this:
```
Getting Started - corrected typos
Entities - created chapter
```
Just use a normal style commit message otherwise.
## Adding a new language
1. Copy `_en/` to your language code
2. Add entry to `_data/languages.yml`
3. Add entry to `collections` in `_config.yml`
4. Add your language to the if else in `layouts/default.html`
5. Translate your language code folder (that you made in step 1)
You can translate the file paths, just make sure you keep any ids the same.
## Using Jeykll
I use [Jekyll](http://jekyllrb.com/) 3.8.0
# For Debian/Ubuntu based:
sudo apt install ruby-dev
gem install jekyll github-pages
### Building as a website
You can build it as a website using [Jekyll](http://jekyllrb.com/)
$ jekyll build
Goes to _site/
### Webserver for Development
You can start a webserver on localhost which will automatically
rebuild pages when you modify their markdown source.
$ jekyll serve
This serves at <http://localhost:4000> on my computer, but the port
may be different. Check the console for the "server address"

7
README.txt Normal file
View File

@ -0,0 +1,7 @@
Examples for Minetest Modding Book
==================================
Some of the chapter folders are modpacks. As the mod configurator (eg: configure
on world select) doesn't support modpacks in modpacks, you need to take out all
of these folders and install them into the mod location.
For example, minetest/mods/1_folders and minetest/mods/3_nodes_items_crafting

View File

@ -1 +0,0 @@
calc_word_count: true

View File

@ -1,16 +0,0 @@
url: "https://rubenwardy.com"
baseurl: "/minetest_modding_book"
sass:
# nested (default), compact, compressed, expanded
style: compressed
plugins:
- jekyll-sitemap
- jekyll-redirect-from
collections:
en:
output: true
it:
output: true

View File

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

View File

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

@ -1,211 +0,0 @@
---
title: Lua Voxel Manipulators
layout: default
root: ../..
idx: 6.2
description: Learn how to use LVMs to speed up map operations.
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 <!-- omit in toc -->
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
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
An LVM allows you to load large areas of the map into your mod's memory.
You can then read and write this data without further interaction with the
engine and without running any callbacks, which means that these
operations are very fast. Once done, you can then write the area back into
the engine and run any lighting calculations.
## Reading into the LVM
You can only load a cubic area into an LVM, so you need to work out the minimum
and maximum positions that you need to modify. Then you can create and read into
an LVM. For example:
```lua
local vm = core.get_voxel_manip()
local emin, emax = vm:read_from_map(pos1, pos2)
```
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()`.
This returns a flat array where each entry represents the type of a
particular node.
```lua
local data = vm:get_data()
```
You can get param2 and lighting data using the methods `get_light_data()` and `get_param2_data()`.
You'll need to use `emin` and `emax` to work out where a node is in the flat arrays
given by the above methods. There's a helper class called `VoxelArea` which handles
the calculation for you.
```lua
local a = VoxelArea:new{
MinEdge = emin,
MaxEdge = emax
}
-- Get node's index
local idx = a:index(x, y, z)
-- Read node
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 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 = core.get_content_id("default:stone")
```
You can then check whether the node is stone:
```lua
local idx = a:index(x, y, z)
if data[idx] == c_stone then
print("is stone!")
end
```
Content IDs of a node type may change during load time, so it is recommended that
you don't try getting them during this time.
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:
```lua
for z = min.z, max.z do
for y = min.y, max.y do
for x = min.x, max.x do
-- vi, voxel index, is a common variable name here
local vi = a:index(x, y, z)
if data[vi] == c_stone then
print("is stone!")
end
end
end
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 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 *cache thrashing*.
## Writing Nodes
First, you need to set the new content ID in the data array:
```lua
for z = min.z, max.z do
for y = min.y, max.y do
for x = min.x, max.x do
local vi = a:index(x, y, z)
if data[vi] == c_stone then
data[vi] = c_air
end
end
end
end
```
When you finish setting nodes in the LVM, you then need to upload the data
array to the engine:
```lua
vm:set_data(data)
vm:write_to_map(true)
```
For setting lighting and param2 data, use the appropriately named
`set_light_data()` and `set_param2_data()` methods.
`write_to_map()` takes a Boolean which is true if you want lighting to be
calculated. If you pass false, you need to recalculate lighting at a future
time using `core.fix_light`.
## Example
```lua
local function grass_to_dirt(pos1, pos2)
local c_dirt = core.get_content_id("default:dirt")
local c_grass = core.get_content_id("default:dirt_with_grass")
-- Read data into 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()
-- Modify data
for z = pos1.z, pos2.z do
for y = pos1.y, pos2.y do
for x = pos1.x, pos2.x do
local vi = a:index(x, y, z)
if data[vi] == c_grass then
data[vi] = c_dirt
end
end
end
end
-- Write data
vm:set_data(data)
vm:write_to_map(true)
end
```
## Your Turn
* 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 node each
time? If so, how could you stop this?

View File

@ -1,187 +0,0 @@
---
title: Getting Started
layout: default
root: ../..
idx: 1.1
description: Learn how to make a mod folder, including init.lua, mod.conf and more.
redirect_from:
- /en/chapters/folders.html
- /en/basics/folders.html
---
## Introduction <!-- omit in toc -->
Understanding the basic structure of a mod's folder is an essential skill when
creating mods. In this chapter, you'll learn about how modding in Minetest works
and create your first mod.
- [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.
In Minetest, a game is a collection of modules which work together to provide the
content and behaviour of a game.
A module, commonly known as a mod, is a collection of scripts and resources.
It's possible to make a game using only one mod, but this is rarely done because it
reduces the ease by which parts of the game can be adjusted and replaced
independently of others.
It's also possible to distribute mods outside of a game, in which case they
are also *mods* in the more traditional sense - modifications. These mods adjust
or extend the features of a game.
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.
## 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. Minetest checks in several different locations for
mods. These locations are commonly called *mod load paths*.
For a given world/save game, three mod locations are checked.
They are, in order:
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
├── textures
│   └── mymod_node.png files
├── init.lua
└── mod.conf
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.
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.
Dependencies are specified in a comma-separated list in mod.conf.
depends = modone, modtwo
optional_depends = modthree
## Mod Packs
Mods can be grouped into mod packs, which allow multiple mods to be packaged
and moved together. They are useful if you want to supply multiple mods to
a player, but don't want to make them download each one individually.
modpack1
├── modpack.conf (required) - signals that this is a mod pack
├── mod1
│   └── ... mod files
└── mymod (optional)
   └── ... mod files
Please note that a modpack is not a *game*.
Games have their own organisational structure which will be explained in the
Games chapter.

View File

@ -1,197 +0,0 @@
---
title: Lua Scripting
layout: default
root: ../..
idx: 1.2
description: A basic introduction to Lua, including a guide on global/local scope.
redirect_from: /en/chapters/lua.html
---
## Introduction <!-- omit in toc -->
In this chapter, you'll learn about scripting in Lua, the tools required
to help with this, and some techniques that you may find useful.
- [Programming](#programming)
- [Coding in Lua](#coding-in-lua)
- [Code Editors](#code-editors)
- [Local and Global Scope](#local-and-global-scope)
- [Locals should be used as much as possible](#locals-should-be-used-as-much-as-possible)
- [Including other Lua Scripts](#including-other-lua-scripts)
## Programming
Programming is the action of taking a problem, such as sorting a list
of items, and turning it into steps that a computer can understand.
Teaching you the logical process of programming is beyond the scope of this book;
however, the following websites are quite useful in developing this:
* [Codecademy](http://www.codecademy.com/) is one of the best resources for
learning to write code. It provides an interactive tutorial experience.
* [Scratch](https://scratch.mit.edu) is a good resource for starting from
absolute basics, and learning the problem-solving techniques required to program.
It's great for children and teenagers.
* [Programming with Mosh](https://www.youtube.com/user/programmingwithmosh) is
a good YouTube series to learn programming.
### Coding in Lua
It's also beyond the scope of this book to teach Lua coding.
The [Programming in Lua (PiL)](https://www.lua.org/pil/contents.html) book is an
excellent introduction to Lua programming.
## Code Editors
A code editor with code highlighting is sufficient for writing scripts in Lua.
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)
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
```
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.
Commonly used editors which are well-suited for Lua include:
* [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
Other suitable editors are also available.
## Local and Global Scope
Whether a variable is local or global determines where it can be written to or
read from. Global variables can be accessed from anywhere in the script file,
and from any other mod:
```lua
function one()
foo = "bar"
end
function two()
print(dump(foo)) -- Output: "bar"
end
one()
two()
```
In constrast, a local variable is only accessible from where it is defined.
Lua defaults to variables being global, so you need to explicitly use the
`local` keyword:
```lua
-- Accessible from within this script file
local one = 1
function myfunc()
-- Accessible from within this function
local two = one + one
if two == one then
-- Accessible from within this if statement
local three = one + two
end
end
```
### Locals should be used as much as possible
Local variables should be used whenever possible. Mods should only create one
global at most, with the same name as the mod. Creating other globals is sloppy
coding, and Minetest will warn about this:
Assignment to undeclared global 'foo' inside function at init.lua:2
To correct this, use "local":
```lua
function one()
local foo = "bar"
end
function two()
print(dump(foo)) -- Output: nil
end
one()
two()
```
Remember that nil means **not initialised**. The variable hasn't been assigned a
value yet, doesn't exist, or has been uninitialised (meaning set to nil).
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)
return bar * 2
end
```
To allow mods to call your functions, you should create a table with the same
name as the mod and add your function to it. This table is often called an API
table or namespace.
```lua
mymod = {}
function mymod.foo(bar)
return "foo" .. bar
end
-- In another mod, or script:
mymod.foo("foobar")
```
`function mymod.foo()` is equivalent to `mymod.foo = function()`, it's just a
nicer way to write it.
## Including other Lua Scripts
The recommended way to include other Lua scripts in a mod is to use *dofile*.
```lua
dofile(core.get_modpath("modname") .. "/script.lua")
```
A script can return a value, which is useful for sharing private locals:
```lua
-- script.lua
local module = {}
module.message = "Hello World!"
return module
-- init.lua
local ret = dofile(core.get_modpath("modname") .. "/script.lua")
print(ret.message) -- Hello world!
```
[Later chapters](../quality/clean_arch.html) will discuss how best to split up
code for a mod.

View File

@ -1,93 +0,0 @@
---
title: Creating Games
layout: default
root: ../..
idx: 7.1
---
## Introduction <!-- omit in toc -->
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?
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,
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.
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`.
foo_game
├── game.conf
├── menu
│   ├── header.png
│   ├── background.png
│   └── icon.png
├── minetest.conf
├── mods
│   └── ... mods
├── README.txt
└── settingtypes.txt
The only thing that is required is a mods folder, but `game.conf` and `menu/icon.png`
are recommended.
## Inter-game Compatibility
### API Compatibility
It's a good idea to try to keep as much API compatibility with Minetest Game as
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,
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:
* Lua API table - All documented/advertised functions in the global table which shares the same name.
For example, `mobs.register_mob`.
* Registered Nodes/Items - The presence of items.
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 mega God-mod like
*default* in Minetest Game, in which case the game shouldn't include a mod named
default.
API compatibility also applies to other third-party mods and games,
so try to make sure that any new mods have a unique mod name.
To check whether a mod name has been taken, search for it on
[content.minetest.net](https://content.minetest.net/).
### Groups and Aliases
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
* Create a simple game where the player gains points from digging special blocks.

View File

@ -1,36 +0,0 @@
---
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
---
<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>
</header>
## Introduction
Minetest uses Lua scripts to provide modding support.
This book aims to teach you how to create your own mods, starting from the basics.
Each chapter focuses on a particular part of the API, and will soon get you making
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://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 [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://gitlab.com/rubenwardy/minetest_modding_book/-/blob/master/README.md).

View File

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

View File

@ -1,98 +0,0 @@
---
title: Creating Textures
layout: default
root: ../..
idx: 2.2
description: An introduction to making textures in your editor of choice, and a guide on GIMP.
redirect_from: /en/chapters/creating_textures.html
---
## Introduction <!-- omit in toc -->
Being able to create and optimise textures is a very useful skill when
developing for Minetest.
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.
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.
- [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
### Using the Pencil
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 unintended blurring. It also gives you a high
level of precision and control.
### Tiling
Textures used for nodes should generally be designed to tile. This means
when you place multiple nodes with the same texture together, the edges line
up correctly.
<!-- IMAGE NEEDED - cobblestone that tiles correctly -->
If you fail to match the edges correctly, the result is far less pleasing
to look at.
<!-- IMAGE NEEDED - node that doesn't tile correctly -->
### Transparency
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
### MS Paint
MS Paint is a simple editor which can be useful for basic texture
design; however, it does not support transparency.
This usually won't matter when making textures for the sides of nodes,
but if you need transparency in your textures you should choose a
different editor.
### Aseprite / LibreSprite
[Aseprite](https://www.aseprite.org/) is a proprietary pixel art editor.
It contains a lot of useful features by default such as color palettes and
animation tools.
[LibreSprite](https://libresprite.github.io/) is an open-source fork of Aseprite
from before it went proprietary.
### GIMP
GIMP is commonly used in the Minetest community. It has quite a high
learning curve because many of its features are not immediately
obvious.
When using GIMP, make sure to use the Pencil tool with the Pixel brush and a
size of 1. It's also advisable to select the "Hard edge" checkbox for the Eraser
tool.

View File

@ -1,356 +0,0 @@
---
title: ItemStacks and Inventories
layout: default
root: ../..
idx: 2.4
description: Manipulate InvRefs and ItemStacks
redirect_from:
- /en/chapters/inventories.html
- /en/chapters/itemstacks.html
- /en/inventories/inventories.html
- /en/inventories/itemstacks.html
---
## Introduction <!-- omit in toc -->
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 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 referred to as *lists* in the context of
inventories.
Players and nodes only have a single inventory; lists enable you to have
multiple grids within that inventory. By default, the player has the "main" list
for the bulk of its inventory and a few lists for the crafting system.
## ItemStacks
ItemStacks have four components to them: `name`, `count`, `wear`, and metadata.
The item name may be the item name of a registered item, an alias, or an unknown
item name. Unknown items are common when users uninstall mods, or when mods
remove items without precautions, such as registering aliases.
```lua
print(stack:get_name())
stack:set_name("default:dirt")
if not stack:is_known() then
print("Is an unknown item!")
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.
```lua
print(stack:get_stack_max())
```
An ItemStack can be empty, in which case the count will be 0.
```lua
print(stack:get_count())
stack:set_count(10)
```
ItemStacks can be constructed in multiple ways using the ItemStack function:
```lua
ItemStack() -- name="", count=0
ItemStack("default:pick_stone") -- count=1
ItemStack("default:stone 30")
ItemStack({ name = "default:wood", count = 10 })
```
Item metadata is an unlimited key-value store for data about the item. Key-value
means that you use a name (called the key) to access the data (called the
value). Some keys have special meaning, such as `description` which is used to
have a per-stack item description. This will be covered in more detail in the
[Storage and Metadata](../map/storage.html) chapter.
## 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.
### 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
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*.
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.
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 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 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("width: " .. inv:get_width("main"))
else
print("Error! Invalid itemname or size to set_size()")
end
```
`set_size` will fail and return false if the listname or size is invalid.
For example, the new size may be too small to fit all the current items
in the inventory.
### Checking Contents
`is_empty` can be used to see if a list contains any items:
```lua
if inv:is_empty("main") then
print("The list is empty!")
end
```
`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
`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")
local leftover = inv:add_item("main", stack)
if leftover:get_count() > 0 then
print("Inventory is full! " ..
leftover:get_count() .. " items weren't added")
end
```
### Taking Items
To remove items from a list:
```lua
local taken = inv:remove_item("main", stack)
print("Took " .. taken:get_count())
```
### Manipulating Stacks
You can modify individual stacks by first getting them:
```lua
local stack = inv:get_stack(listname, 0)
```
Then modifying them by setting properties or by using the methods which
respect `stack_size`:
```lua
local stack = ItemStack("default:stone 50")
local to_add = ItemStack("default:stone 100")
local leftover = stack:add_item(to_add)
local taken = stack:take_item(19)
print("Could not add" .. leftover:get_count() .. " of the items.")
-- ^ will be 51
print("Have " .. stack:get_count() .. " items")
-- ^ will be 80
-- min(50+100, stack_max) - 19 = 80
-- where stack_max = 99
```
`add_item` will add items to an ItemStack and return any that could not be added.
`take_item` will take up to the number of items but may take less, and returns the stack taken.
Finally, set the item stack:
```lua
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 can be manipulated using `add_wear()`, `get_wear()`, and `set_wear(wear)`.
```lua
local stack = ItemStack("default:pick_mese")
local max_uses = 10
-- This is done automatically when you use a tool that digs things
-- It increases the wear of an item by one use.
stack:add_wear(65535 / (max_uses - 1))
```
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
ItemStacks and Inventories can be converted to and from tables.
This is useful for copying and bulk operations.
```lua
-- Entire inventory
local data = inv1:get_lists()
inv2:set_lists(data)
-- One list
local listdata = inv1:get_list("main")
inv2:set_list("main", listdata)
```
The table of lists returned by `get_lists()` will be in this form:
```lua
{
list_one = {
ItemStack,
ItemStack,
ItemStack,
ItemStack,
-- inv:get_size("list_one") elements
},
list_two = {
ItemStack,
ItemStack,
ItemStack,
ItemStack,
-- inv:get_size("list_two") elements
}
}
```
`get_list()` will return a single list as just a list of ItemStacks.
One important thing to note is that the set methods above don't change the size
of the lists.
This means that you can clear a list by setting it to an empty table and it won't
decrease in size:
```lua
inv:set_list("main", {})
```

View File

@ -1,446 +0,0 @@
---
title: Node Drawtypes
layout: default
root: ../..
idx: 2.3
description: Guide to all drawtypes, including node boxes/nodeboxes and mesh nodes.
redirect_from: /en/chapters/node_drawtypes.html
---
## Introduction <!-- omit in toc -->
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`.
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 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.
- [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)
## Cubic Nodes: Normal and Allfaces
<figure class="right_image">
<img src="{{ page.root }}//static/drawtype_normal.png" alt="Normal Drawtype">
<figcaption>
Normal Drawtype
</figcaption>
</figure>
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
core.register_node("mymod:diamond", {
description = "Alien Diamond",
tiles = {"mymod_diamond.png"},
groups = {cracky = 3},
})
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 Nodes
These nodes are not rendered and thus have no textures.
```lua
core.register_node("myair:air", {
description = "MyAir (you hacker you!)",
drawtype = "airlike",
paramtype = "light",
sunlight_propagates = true,
walkable = false, -- Would make the player collide with the air node
pointable = false, -- You can't select the node
diggable = false, -- You can't dig the node
buildable_to = true, -- Nodes can replace this node.
-- (you can place a node and remove the air node
-- that used to be there)
air_equivalent = true,
drop = "",
groups = {not_in_creative_inventory=1}
})
```
## 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">
<figcaption>
Liquid Drawtype
</figcaption>
</figure>
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.
core.register_node("default:water_source", {
drawtype = "liquid",
paramtype = "light",
inventory_image = core.inventorycube("default_water.png"),
-- ^ this is required to stop the inventory image from being animated
tiles = {
{
name = "default_water_source_animated.png",
animation = {
type = "vertical_frames",
aspect_w = 16,
aspect_h = 16,
length = 2.0
}
}
},
special_tiles = {
-- New-style water source material (mostly unused)
{
name = "default_water_source_animated.png",
animation = {type = "vertical_frames", aspect_w = 16,
aspect_h = 16, length = 2.0},
backface_culling = false,
}
},
--
-- Behavior
--
walkable = false, -- The player falls through
pointable = false, -- The player can't highlight it
diggable = false, -- The player can't dig it
buildable_to = true, -- Nodes can be replace this node
alpha = 160,
--
-- Liquid Properties
--
drowning = 1,
liquidtype = "source",
liquid_alternative_flowing = "default:water_flowing",
-- ^ when the liquid is flowing
liquid_alternative_source = "default:water_source",
-- ^ when the liquid is a source
liquid_viscosity = WATER_VISC,
-- ^ how fast
liquid_range = 8,
-- ^ how far
post_effect_color = {a=64, r=100, g=100, b=200},
-- ^ colour of screen when the player is submerged
})
```
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.
## Node Boxes
<figure class="right_image">
<img src="{{ page.root }}//static/drawtype_nodebox.gif" alt="Nodebox drawtype">
<figcaption>
Nodebox drawtype
</figcaption>
</figure>
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
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},
},
}
})
```
The most important part is the node box table:
```lua
{-0.5, -0.5, -0.5, 0.5, 0, 0.5},
{-0.5, 0, 0, 0.5, 0.5, 0.5}
```
Each row is a cuboid which are joined to make a single node.
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.
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 Node Boxes
Sometimes you want different nodeboxes for when it is placed on the floor, wall, or ceiling like with torches.
```lua
core.register_node("default:sign_wall", {
drawtype = "nodebox",
node_box = {
type = "wallmounted",
-- Ceiling
wall_top = {
{-0.4375, 0.4375, -0.3125, 0.4375, 0.5, 0.3125}
},
-- Floor
wall_bottom = {
{-0.4375, -0.5, -0.3125, 0.4375, -0.4375, 0.3125}
},
-- Wall
wall_side = {
{-0.5, -0.3125, -0.4375, -0.4375, 0.3125, 0.4375}
}
},
})
```
## 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;
Inner faces will still be rendered even when they're completely hidden.
A face is a flat surface on a mesh. An inner face occurs when the faces of two
different node boxes overlap, causing parts of the node box model to be
invisible but still rendered.
You can register a mesh node as so:
```lua
core.register_node("mymod:meshy", {
drawtype = "mesh",
-- Holds the texture for each "material"
tiles = {
"mymod_meshy.png"
},
-- Path to the mesh
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
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 Nodes
Signlike nodes are flat nodes with can be mounted on the sides of other nodes.
Despite the name of this drawtype, signs don't actually tend to use signlike but
instead use the `nodebox` drawtype to provide a 3D effect. The `signlike` drawtype
is, however, commonly used by ladders.
```lua
core.register_node("default:ladder_wood", {
drawtype = "signlike",
tiles = {"default_ladder_wood.png"},
-- Required: store the rotation in param2
paramtype2 = "wallmounted",
selection_box = {
type = "wallmounted",
},
})
```
## Plantlike Nodes
<figure class="right_image">
<img src="{{ page.root }}//static/drawtype_plantlike.png" alt="Plantlike Drawtype">
<figcaption>
Plantlike Drawtype
</figcaption>
</figure>
Plantlike nodes draw their tiles in an X like pattern.
```lua
core.register_node("default:papyrus", {
drawtype = "plantlike",
-- Only one texture used
tiles = {"default_papyrus.png"},
selection_box = {
type = "fixed",
fixed = {-6 / 16, -0.5, -6 / 16, 6 / 16, 0.5, 6 / 16},
},
})
```
## Firelike Nodes
Firelike is similar to plantlike, except that it is designed to "cling" to walls
and ceilings.
<figure>
<img src="{{ page.root }}//static/drawtype_firelike.png" alt="Firelike nodes">
<figcaption>
Firelike nodes
</figcaption>
</figure>
```lua
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

@ -1,345 +0,0 @@
---
title: Nodes, Items, and Crafting
layout: default
root: ../..
idx: 2.1
description: Learn how to register node, items, and craft recipes using register_node, register_item, and register_craft.
redirect_from: /en/chapters/nodes_items_crafting.html
---
## 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](#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 if it isn't possible through normal gameplay.
A node is an item that can be placed or be found in the world. Every position
in the world must be occupied with one and only one node - seemingly blank
positions are usually air nodes.
A craftitem can't be placed and is only found in inventories or as a dropped item
in the world.
A tool is like a craftitem but has the ability to wear. As you use the tool, the
wear bar goes down until the tool breaks. Tools can also never be stacked. In
the future, it's likely that craftitems and tools will merge into one type of
item, as the distinction between them is rather artificial.
## Registering Items
Item definitions consist of an *item name* and a *definition table*.
The definition table contains attributes that affect the behaviour of the item.
```lua
core.register_craftitem("modname:itemname", {
description = "My Special Item",
inventory_image = "modname_itemname.png"
})
```
### 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.
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.
* 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.
```lua
core.register_alias("dirt", "default:dirt")
```
Mods need to make sure to resolve aliases before dealing directly with item names,
as the engine won't do this.
This is pretty simple though:
```lua
itemname = core.registered_aliases[itemname] or itemname
```
### Textures
Textures should be placed in the textures/ folder with names in the format
`modname_itemname.png`.\\
JPEG textures are supported, but they do not support transparency and are generally
bad quality at low resolutions.
It is often better to use the PNG format.
Textures in Minetest are usually 16 by 16 pixels. They can be any resolution,
but it is recommended that they are in the order of 2, for example, 16, 32, 64,
or 128. This is because other resolutions may not be supported correctly on
older devices, especially phones, resulting in degraded performance.
## Registering a basic node
Registering nodes is similar to registering items, just with a different
function:
```lua
core.register_node("mymod:diamond", {
description = "Alien Diamond",
tiles = {"mymod_diamond.png"},
is_ground_content = true,
groups = {cracky=3, stone=1}
})
```
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:
up (+Y), down (-Y), right (+X), left (-X), back (+Z), front (-Z).
(+Y, -Y, +X, -X, +Z, -Z)
Remember that +Y is upwards in Minetest, as is the convention with
most 3D computer games.
```lua
core.register_node("mymod:diamond", {
description = "Alien Diamond",
tiles = {
"mymod_diamond_up.png", -- y+
"mymod_diamond_down.png", -- y-
"mymod_diamond_right.png", -- x+
"mymod_diamond_left.png", -- x-
"mymod_diamond_back.png", -- z+
"mymod_diamond_front.png", -- z-
},
is_ground_content = true,
groups = {cracky = 3},
drop = "mymod:diamond_fragments"
-- ^ Rather than dropping diamond, drop mymod:diamond_fragments
})
```
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.
## Crafting
There are several types of crafting recipe available, indicated by the `type`
property.
* shaped - Ingredients must be in the correct position.
* shapeless - It doesn't matter where the ingredients are,
just that there is the right amount.
* cooking - Recipes for the furnace to use.
* fuel - Defines items which can be burned in furnaces.
* tool_repair - Defines items which can be tool repaired.
Craft recipes are not items, so they do not use Item Names to uniquely
identify themselves.
### Shaped
Shaped recipes are when the ingredients need to be in the right shape or
pattern to work. In the example below, the fragments need to be in a
chair-like pattern for the craft to work.
```lua
core.register_craft({
type = "shaped",
output = "mymod:diamond_chair 99",
recipe = {
{"mymod:diamond_fragments", "", ""},
{"mymod:diamond_fragments", "mymod:diamond_fragments", ""},
{"mymod:diamond_fragments", "mymod:diamond_fragments", ""}
}
})
```
One thing to note is the blank column on the right-hand side.
This means that there *must* be an empty column to the right of the shape, otherwise
this won't work.
If this empty column shouldn't be required, then the empty strings can be left
out like so:
```lua
core.register_craft({
output = "mymod:diamond_chair 99",
recipe = {
{"mymod:diamond_fragments", "" },
{"mymod:diamond_fragments", "mymod:diamond_fragments"},
{"mymod:diamond_fragments", "mymod:diamond_fragments"}
}
})
```
The type field isn't actually needed for shaped crafts, as shaped is the
default craft type.
### Shapeless
Shapeless recipes are a type of recipe which is used when it doesn't matter
where the ingredients are placed, just that they're there.
```lua
core.register_craft({
type = "shapeless",
output = "mymod:diamond 3",
recipe = {
"mymod:diamond_fragments",
"mymod:diamond_fragments",
"mymod:diamond_fragments",
},
})
```
### Cooking and Fuel
Recipes with the type "cooking" are not made in the crafting grid,
but are cooked in furnaces, or other cooking tools that might be found in mods.
```lua
core.register_craft({
type = "cooking",
output = "mymod:diamond_fragments",
recipe = "default:coalblock",
cooktime = 10,
})
```
The only real difference in the code is that the recipe is just a single item,
compared to being in a table (between braces).
They also have an optional "cooktime" parameter which
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 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
core.register_craft({
type = "fuel",
recipe = "mymod:diamond",
burntime = 300,
})
```
They don't have an output like other recipes, but they have a burn time
which defines how long they will last as fuel in seconds.
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
and have an associated value.
```lua
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 the group to be used.
```lua
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
with different tools.
A dig type group with a higher associated value means the node is easier
and quicker to cut.
It's possible to combine multiple dig types to allow the more efficient use
of multiple types of tools.
A node with no dig types cannot be dug by any tools.
| Group | Best Tool | Description |
|--------|-----------|-------------|
| crumbly | spade | Dirt, sand |
| cracky | pickaxe | Tough (but brittle) stuff like stone |
| snappy | *any* | Can be cut using fine tools;<br>e.g. leaves, smallplants, wire, sheets of metal |
| choppy | axe | Can be cut using a sharp force; e.g. trees, wooden planks |
| fleshy | sword | Living things like animals and the player.<br>This could imply some blood effects when hitting. |
| explody | ? | Especially prone to explosions |
| oddly_breakable_by_hand | *any* | Torches and such - very quick to dig |
Every tool has a tool capability.
A capability includes a list of supported dig types, and associated properties
for each type such as dig times and the amount of wear.
Tools can also have a maximum supported hardness for each type, which makes
it possible to prevent weaker tools from digging harder nodes.
It's very common for tools to include all dig types in their capabilities,
with the less suitable ones having very inefficient properties.
If the item a player is currently wielding doesn't have an explicit tool
capability, then the capability of the current hand is used instead.
```lua
core.register_tool("mymod:tool", {
description = "My Tool",
inventory_image = "mymod_tool.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},
},
})
```
Groupcaps is the list of supported dig types for digging nodes.
Damage groups are for controlling how tools damage objects, which will be
discussed later in the Objects, Players, and Entities chapter.

View File

@ -1,234 +0,0 @@
---
title: Basic Map Operations
layout: default
root: ../..
idx: 3.1
description: Basic operations like set_node and get_node
redirect_from: /en/chapters/environment.html
---
## Introduction <!-- omit in toc -->
In this chapter, you will learn how to perform basic actions on the map, such as
adding, removing, and finding nodes.
- [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,
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, 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
to its maximum value, 31000, by default. Existing MapBlocks can, however, be
loaded from the world database outside of the generation limit.
## Reading
### Reading Nodes
You can read from the map once you have a position:
```lua
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, 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 `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
limit (`mapgen_limit`).
### Finding Nodes
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,
and adapt the growth rate accordingly.
`core.find_node_near` will return the first found node in a certain radius
which matches the node names or groups given. In the following example,
we look for a mese node within 5 nodes of the position:
```lua
local grow_speed = 1
local node_pos = core.find_node_near(pos, 5, { "default:mese" })
if node_pos then
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 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 =
core.find_nodes_in_area(pos1, pos2, { "default:mese" })
local grow_speed = 1 + #pos_list
```
The above code finds the number of nodes in a *cuboid volume*. This is different
to `find_node_near`, which uses the distance to the position (ie: a *sphere*). In
order to fix this, we will need to manually check the range ourselves:
```lua
local pos1 = vector.subtract(pos, { x = 5, y = 5, z = 5 })
local pos2 = vector.add(pos, { x = 5, y = 5, z = 5 })
local pos_list =
core.find_nodes_in_area(pos1, pos2, { "default:mese" })
local grow_speed = 1
for i=1, #pos_list do
local delta = vector.subtract(pos_list[i], pos)
if delta.x*delta.x + delta.y*delta.y + delta.z*delta.z <= 5*5 then
grow_speed = grow_speed + 1
end
end
```
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 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
and are useful in other circumstances.
## Writing
### Writing Nodes
You can use `set_node` to write to the map. Each call to set_node will cause
lighting to be recalculated and node callbacks to run, which means that set_node
is fairly slow for large numbers of nodes.
```lua
core.set_node({ x = 1, y = 3, z = 4 }, { name = "default:mese" })
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 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
core.swap_node({ x = 1, y = 3, z = 4 }, { name = "default:mese" })
```
### Removing Nodes
A node must always be present. To remove a node, you set the position to `air`.
The following two lines will both remove a node, and are both identical:
```lua
core.remove_node(pos)
core.set_node(pos, { name = "air" })
```
In fact, remove_node is just a helper function that calls set_node with `"air"`.
## Loading Blocks
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.
```lua
-- Load a 20x20x20 area
local halfsize = { x = 10, y = 10, z = 10 }
local pos1 = vector.subtract(pos, halfsize)
local pos2 = vector.add (pos, halfsize)
local context = {} -- persist data between callback calls
core.emerge_area(pos1, pos2, emerge_callback, context)
```
Minetest will call `emerge_callback` whenever it loads a block, with some
progress information.
```lua
local function emerge_callback(pos, action,
num_calls_remaining, context)
-- On first call, record number of blocks
if not context.total_blocks then
context.total_blocks = num_calls_remaining + 1
context.loaded_blocks = 0
end
-- Increment number of blocks loaded
context.loaded_blocks = context.loaded_blocks + 1
-- Send progress message
if context.total_blocks == context.loaded_blocks then
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)
core.chat_send_all(msg)
end
end
```
This is not the only way of loading blocks; using an
[Lua Voxel Manipulator (LVM)](../advmap/lvm.html) will also cause the
encompassed blocks to be loaded synchronously.
## Deleting Blocks
You can use delete_blocks to delete a range of map blocks:
```lua
-- Delete a 20x20x20 area
local halfsize = { x = 10, y = 10, z = 10 }
local pos1 = vector.subtract(pos, halfsize)
local pos2 = vector.add (pos, halfsize)
core.delete_area(pos1, pos2)
```
This will delete all map blocks in that area, *inclusive*. This means that some
nodes will be deleted outside the area as they will be on a mapblock which overlaps
the area bounds.

View File

@ -1,361 +0,0 @@
---
title: Objects, Players, and Entities
layout: default
root: ../..
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 <!-- 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?](#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
independently of the node grid and has properties such as velocity and scale.
Objects aren't items, and they have their own separate registration system.
There are a few differences between Players and Entities.
The biggest one is that Players are player-controlled, whereas Entities are mod-controlled.
This means that the velocity of a player cannot be set by mods - players are client-side,
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.
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 = core.get_player_by_name("bob")
local pos = object:get_pos()
object:set_pos({ x = pos.x, y = pos.y + 1, z = pos.z })
```
`set_pos` immediately sets the position, with no animation. If you'd like to
smoothly animate an object to the new position, you should use `move_to`.
This, unfortunately, only works for entities.
```lua
object:move_to({ x = pos.x, y = pos.y + 1, z = pos.z })
```
An important thing to think about when dealing with entities is network latency.
In an ideal world, messages about entity movements would arrive immediately,
in the correct order, and with a similar interval as to how you sent them.
However, unless you're in singleplayer, this isn't an ideal world.
Messages will take a while to arrive. Position messages may arrive out of order,
resulting in some `set_pos` calls being skipped as there's no point going to
a position older than the current known position.
Moves may not be similarly spaced, which makes it difficult to use them for animation.
All this results in the client seeing different things to the server, which is something
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.
```lua
object:set_properties({
visual = "mesh",
mesh = "character.b3d",
textures = {"character_texture.png"},
visual_size = {x=1, y=1},
})
```
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 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 definition table that resembles an item definition table.
This table can contain callback methods, initial object properties, and custom
members.
```lua
local MyEntity = {
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},
},
message = "Default message",
}
function MyEntity:set_message(msg)
self.message = msg
end
```
Entity definitions differ in one very important way from Item definitions.
When an entity is emerged (ie: loaded or created), a new table is created for
that entity that *inherits* from the definition table.
<!--
This inheritance is done using a metatables.
Metatables are an important Lua feature that you will need to be aware of, as it
is an essential part of the Lua language. In layman's terms, a metatable allows
you to control how the table behaves when using certain Lua syntax. The most
common use of metatables is the ability to use another table as a prototype,
defaulting to the other table's properties and methods when they do not exist in
the current table.
Say you want to access `a.x`. If the table `a` has that member, then it will be
returned as normal. However, if the table doesn't have that member and the
metatable lists a table `b` as a prototype, then table `b` will be checked to
see if it has that member.
-->
Both an ObjectRef and an entity table provide ways to get the counterpart:
```lua
local entity = object:get_luaentity()
local object = entity.object
print("entity is at " .. core.pos_to_string(object:get_pos()))
```
There are a number of available callbacks for use with entities.
A complete list can be found in [lua_api.md](https://minetest.gitlab.io/minetest/minetest-namespace-reference/#registered-definition-tables).
```lua
function MyEntity:on_step(dtime)
local pos = self.object:get_pos()
local pos_down = vector.subtract(pos, vector.new(0, 1, 0))
local delta
if core.get_node(pos_down).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.object:move_to(vector.add(pos, delta))
end
function MyEntity:on_punch(hitter)
core.chat_send_player(hitter:get_player_name(), self.message)
end
```
Now, if you were to spawn and use this entity, you'd notice that the message
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 the custom information that
needs to stored.
```lua
function MyEntity:get_staticdata()
return core.write_json({
message = self.message,
})
end
function MyEntity:on_activate(staticdata, dtime_s)
if staticdata ~= "" and staticdata ~= nil then
local data = core.parse_json(staticdata) or {}
self:set_message(data.message)
end
end
```
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.
`on_activate()`, on the other hand, will only be called when an entity becomes
active either from the MapBlock becoming active or from the entity spawning.
This means that staticdata could be empty.
Finally, you need to register the type table using the aptly named `register_entity`.
```lua
core.register_entity("mymod:entity", MyEntity)
```
The entity can be spawned by a mod like so:
```lua
local pos = { x = 1, y = 2, z = 3 }
local obj = core.add_entity(pos, "mymod:entity", nil)
```
The third parameter is the initial staticdata.
To set the message, you can use the entity table method:
```lua
obj:get_luaentity():set_message("hello!")
```
Players with the *give* [privilege](../players/privileges.html) can
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 -
is moved. An attached object is said to be a child of the parent.
An object can have an unlimited number of children, but at most one parent.
```lua
child:set_attach(parent, bone, position, rotation)
```
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.
{% 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,
to move the arm.
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 (using just the entity API, and without using any other mods).

View File

@ -1,247 +0,0 @@
---
title: Storage and Metadata
layout: default
root: ../..
idx: 3.3
description: Mod Storage, NodeMetaRef (get_meta).
redirect_from:
- /en/chapters/node_metadata.html
- /en/map/node_metadata.html
---
## Introduction <!-- omit in toc -->
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)
- [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
### What is Metadata?
In Minetest, Metadata is a key-value store used to attach custom data to something.
You can use metadata to store information against a Node, Player, or ItemStack.
Each type of metadata uses the exact same API.
Metadata stores values as strings, but there are a number of methods to
convert and store other primitive types.
Some keys in metadata may have special meaning.
For example, `infotext` in node metadata is used to store the tooltip which shows
when hovering over the node using the crosshair.
To avoid conflicts with other mods, you should use the standard namespace
convention for keys: `modname:keyname`.
The exception is for conventional data such as the owner name which is stored as
`owner`.
Metadata is data about data.
The data itself, such as a node's type or an stack's count, is not metadata.
### Obtaining a Metadata Object
If you know the position of a node, you can retrieve its metadata:
```lua
local meta = core.get_meta({ x = 1, y = 2, z = 3 })
```
Player and ItemStack metadata are obtained using `get_meta()`:
```lua
local pmeta = player:get_meta()
local imeta = stack:get_meta()
```
### Reading and Writing
In most cases, `get_<type>()` and `set_<type>()` methods will be used to read
and write to meta.
Metadata stores strings, so the string methods will directly set and get the value.
```lua
print(meta:get_string("foo")) --> ""
meta:set_string("foo", "bar")
print(meta:get_string("foo")) --> "bar"
```
All of the typed getters will return a neutral default value if the key doesn't
exist, such as `""` or `0`.
You can use `get()` to return a string or nil.
As Metadata is a reference, any changes will be updated to the source automatically.
ItemStacks aren't references however, so you'll need to update the itemstack in the
inventory.
The non-typed getters and setters will convert to and from strings:
```lua
print(meta:get_int("count")) --> 0
meta:set_int("count", 3)
print(meta:get_int("count")) --> 3
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 when showing the ownership or status of a node.
`description` is used in ItemStack Metadata to override the description when
hovering over the stack in an inventory.
You can use colours by encoding them with `core.colorize()`.
`owner` is a common key used to store the username of the player that owns the
item or node.
### Storing Tables
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 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", core.serialize(data))
data = core.deserialize(meta:get_string("foo"))
```
### Private Metadata
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")
meta:mark_as_private("secret")
```
### Lua Tables
You can convert to and from Lua tables using `to_table` and `from_table`:
```lua
local tmp = meta:to_table()
tmp.foo = "bar"
meta:from_table(tmp)
```
## Mod Storage
Mod storage uses the exact same API as Metadata, although it's not technically
Metadata.
Mod storage is per-mod, and can only be obtained during load time in order to
know which mod is requesting it.
```lua
local storage = core.get_mod_storage()
```
You can now manipulate the storage just like metadata:
```lua
storage:set_string("foo", "bar")
```
## Databases
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.
```lua
local backend
if use_database then
backend =
dofile(core.get_modpath("mymod") .. "/backend_sqlite.lua")
else
backend =
dofile(core.get_modpath("mymod") .. "/backend_storage.lua")
end
backend.get_foo("a")
backend.set_foo("a", { score = 3 })
```
The backend_storage.lua file should include a mod storage implementation:
```lua
local storage = core.get_mod_storage()
local backend = {}
function backend.set_foo(key, value)
storage:set_string(key, core.serialize(value))
end
function backend.get_foo(key)
return core.deserialize(storage:get_string(key))
end
return backend
```
The backend_sqlite would do a similar thing, but use the Lua *lsqlite3* library
instead of mod storage.
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 = 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
if sqlite3 then
sqlite3 = nil
end
```
Teaching about SQL or how to use the lsqlite3 library is out of scope for this book.
## Deciding Which to Use
The type of method you use depends on what the data is about,
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 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 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
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 `core.set_node`.)

View File

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

@ -1,197 +0,0 @@
---
title: Chat and Commands
layout: default
root: ../..
idx: 4.2
description: Registering a chatcommand and handling chat messages with register_on_chat_message
redirect_from: /en/chapters/chat.html
cmd_online:
level: warning
title: Offline players can run commands
message: |
A player name is passed instead of a player object because mods
can run commands on behalf of offline players. For example, the IRC
bridge allows players to run commands without joining the game.
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 /.
---
## Introduction <!-- omit in toc -->
Mods can interact with player chat, including
sending messages, intercepting messages, and registering chat commands.
- [Sending Messages](#sending-messages)
- [To All Players](#to-all-players)
- [To Specific Players](#to-specific-players)
- [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
To send a message to every player in the game, call the `chat_send_all` function.
```lua
core.chat_send_all("This is a chat message to all players")
```
Here is an example of how this appears in-game:
<player1> Look at this entrance
This is a chat message to all players
<player2> What about it?
The message appears on a separate line to distinguish it from in-game player chat.
### To Specific Players
To send a message to a specific player, call the `chat_send_player` function:
```lua
core.chat_send_player("player1", "This is a chat message for player1")
```
This message displays in the same manner as messages to all players, but is
only visible to the named player, in this case, player1.
## Chat Commands
To register a chat command, for example `/foo`, use `register_chatcommand`:
```lua
core.register_chatcommand("foo", {
privs = {
interact = true,
},
func = function(name, param)
return true, "You said " .. param .. "!"
end,
})
```
In the above snippet, `interact` is listed as a required
[privilege](privileges.html) meaning that only players with the `interact` privilege can run the command.
`param` is a string containing everything a player writes after the chatcommand
name. For example, if a user types `/grantme one,two,three` then `param` will be
`one,two,three`.
Chat commands can return up to two values,
the first being a Boolean indicating success, and the second being a
message to send to the user.
{% include notice.html notice=page.cmd_online %}
### Accepting Multiple Arguments
<a name="complex-subcommands"></a>
`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.
#### Using string.split
A string can be split up into words using `string.split(" ")`:
```lua
local parts = param:split(" ")
local cmd = parts[1]
if cmd == "join" then
local team_name = parts[2]
team.join(name, team_name)
return true, "Joined team!"
elseif cmd == "max_users" then
local team_name = parts[2]
local max_users = tonumber(parts[3])
if team_name and max_users then
return true, "Set max users of team " .. team_name .. " to " .. max_users
else
return false, "Usage: /team max_users <team_name> <number>"
end
else
return false, "Command needed"
end
```
#### Using Lua patterns
[Lua patterns](https://www.lua.org/pil/20.2.html) are a way of extracting stuff
from text using rules. They're best suited for when there are arguments that can
contain spaces or more control is needed on how parameters are captured.
```lua
local to, msg = param:match("^([%a%d_-]+) (.+)$")
```
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 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, 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).
## Intercepting Messages
To intercept a message, use register_on_chat_message:
```lua
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, because `nil` is returned implicitly and is treated like false.
{% include notice.html notice=page.cb_cmdsprivs %}
You should make sure you take into account that it may be a chat command,
or the user may not have `shout`.
```lua
core.register_on_chat_message(function(name, message)
if message:sub(1, 1) == "/" then
print(name .. " ran chat command")
elseif core.check_player_privs(name, { shout = true }) then
print(name .. " said " .. message)
else
print(name .. " tried to say " .. message ..
" but doesn't have shout")
end
return false
end)
```

View File

@ -1,379 +0,0 @@
---
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
title: Malicious clients can submit anything at anytime
message: You should never trust a formspec submission. A malicious client
can submit anything they like at any time - even if you never showed
them the formspec. This means that you should check privileges
and make sure that they should be allowed to perform the action.
---
## Introduction <!-- omit in toc -->
<figure class="right_image">
<img src="{{ page.root }}//static/formspec_example.png" alt="Furnace Inventory">
<figcaption>
Screenshot of furnace formspec, labelled.
</figcaption>
</figure>
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 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)](hud.html) elements instead of forms, because
unexpected windows tend to disrupt gameplay.
- [Real or Legacy Coordinates](#real-or-legacy-coordinates)
- [Anatomy of a Formspec](#anatomy-of-a-formspec)
- [Elements](#elements)
- [Header](#header)
- [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)
## Real or Legacy Coordinates
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.
Using a formspec_version of 2 or above will enable real coordinates.
## 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]
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.
### Header
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.
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.
The size is in formspec slots - a unit of measurement which is roughly
around 64 pixels, but varies based on the screen density and scaling
settings of the client. Here's a formspec which is `2,2` in size:
formspec_version[4]
size[2,2]
Notice how we explicitly defined the formspec language version.
Without this, the legacy system will instead be used instead - which will
prevent the use of consistent element positioning and other new features.
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:
formspec_version[4]
size[2,2]
position[0,0.5]
anchor[0,0.5]
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.
## Guessing Game
<figure class="right_image">
<img src="{{ page.root }}/static/formspec_guessing.png" alt="Guessing Formspec">
<figcaption>
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
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,
})
```
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
core.register_on_player_receive_fields(function(player, formname, fields)
if formname ~= "guessing:game" then
return
end
if fields.guess then
local pname = player:get_player_name()
core.chat_send_all(pname .. " guessed " .. fields.number)
end
end)
```
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 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 %}
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".
### Contexts
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. In this case,
the target value that needs to be remembered.
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
local _contexts = {}
local function get_context(name)
local context = _contexts[name] or {}
_contexts[name] = context
return context
end
core.register_on_leaveplayer(function(player)
_contexts[player:get_player_name()] = nil
end)
```
Next, we need to modify the show code to update the context
before showing the formspec:
```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
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.
-- The following code sets the formspec for chest.
-- Meta is a way of storing data onto a node.
local meta = core.get_meta(pos)
meta:set_string("formspec",
"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
print(fields.x)
end
})
```
Formspecs set this way do not trigger the same callback. In order to
receive form input for meta formspecs, you must include an
`on_receive_fields` entry when registering the node.
This style of callback triggers when you press enter
in a field, which is impossible with `core.show_formspec`;
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

@ -1,294 +0,0 @@
---
title: HUD
layout: default
root: ../..
idx: 4.6
description: Learn how to display HUD elements
redirect_from: /en/chapters/hud.html
---
## 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).
- [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
### Position and Offset
<figure class="right_image">
<img
width="300"
src="{{ page.root }}//static/hud_diagram_center.svg"
alt="Diagram showing a centered text element">
</figure>
Screens come in a variety of different physical sizes and resolutions, and
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 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.
<div style="clear:both;"></div>
### Alignment
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 aligned to the left, centre, or right.
<figure>
<img
width="500"
src="{{ page.root }}//static/hud_diagram_alignment.svg"
alt="Diagram showing alignment">
</figure>
The above diagram shows 3 windows (blue), each with a single HUD element (yellow)
and a different alignment each time. The arrow is the result of the position
and offset calculation.
### Scoreboard
For the purposes of this chapter, you will learn how to position and update a
score panel like so:
<figure>
<img
src="{{ page.root }}//static/hud_final.png"
alt="screenshot of the HUD we're aiming for">
</figure>
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.
## Text Elements
You can create a HUD element once you have a copy of the player object:
```lua
local player = core.get_player_by_name("username")
local idx = player:hud_add({
hud_elem_type = "text",
position = {x = 0.5, y = 0.5},
offset = {x = 0, y = 0},
text = "Hello world!",
alignment = {x = 0, y = 0}, -- center aligned
scale = {x = 100, y = 100}, -- covered later
})
```
The `hud_add` function returns an element ID - this can be used later to modify
or remove a HUD element.
### Parameters
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, e.g.: `{x=100, y=100}`.
`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:
```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},
offset = {x = -120, y = -25},
text = "Stats",
alignment = 0,
scale = { x = 100, y = 30},
number = 0xFFFFFF,
})
player:hud_add({
hud_elem_type = "text",
position = {x = 1, y = 0.5},
offset = {x = -180, y = 0},
text = digs_text,
alignment = -1,
scale = { x = 50, y = 10},
number = 0xFFFFFF,
})
player:hud_add({
hud_elem_type = "text",
position = {x = 1, y = 0.5},
offset = {x = -70, y = 0},
text = places_text,
alignment = -1,
scale = { x = 50, y = 10},
number = 0xFFFFFF,
})
```
This results in the following:
<figure>
<img
src="{{ page.root }}//static/hud_text.png"
alt="screenshot of the HUD we're aiming for">
</figure>
## Image Elements
Image elements are created in a very similar way to text elements:
```lua
player:hud_add({
hud_elem_type = "image",
position = {x = 1, y = 0.5},
offset = {x = -220, y = 0},
text = "score_background.png",
scale = { x = 1, y = 1},
alignment = { x = 1, y = 0 },
})
```
You will now have this:
<figure>
<img
src="{{ page.root }}//static/hud_background_img.png"
alt="screenshot of the HUD so far">
</figure>
### Parameters
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, 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
Let's make the progress bar for our score panel as an example of scale:
```lua
local percent = tonumber(meta:get("score:score") or 0.2)
player:hud_add({
hud_elem_type = "image",
position = {x = 1, y = 0.5},
offset = {x = -215, y = 23},
text = "score_bar_empty.png",
scale = { x = 1, y = 1},
alignment = { x = 1, y = 0 },
})
player:hud_add({
hud_elem_type = "image",
position = {x = 1, y = 0.5},
offset = {x = -215, y = 23},
text = "score_bar_full.png",
scale = { x = percent, y = 1},
alignment = { x = 1, y = 0 },
})
```
We now have a HUD that looks like the one in the first post!
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 it or remove it later.
```lua
local idx = player:hud_add({
hud_elem_type = "text",
text = "Hello world!",
-- parameters removed for brevity
})
player:hud_change(idx, "text", "New Text")
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 "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:
```lua
local idx = player:hud_add({
hud_elem_type = "text",
text = "New Text",
})
```
## Storing IDs
```lua
score = {}
local saved_huds = {}
function score.update_hud(player)
local player_name = player:get_player_name()
-- 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
player:hud_change(ids["places"], "text", places_text)
player:hud_change(ids["digs"], "text", digs_text)
player:hud_change(ids["bar_foreground"],
"scale", { x = percent, y = 1 })
else
ids = {}
saved_huds[player_name] = ids
-- create HUD elements and set ids into `ids`
end
end
core.register_on_joinplayer(score.update_hud)
core.register_on_leaveplayer(function(player)
saved_huds[player:get_player_name()] = nil
end)
```
## Other Elements
Read [lua_api.md](https://minetest.gitlab.io/minetest/hud/) for a complete list of HUD elements.

View File

@ -1,77 +0,0 @@
---
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 <!-- 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.
- [Basic Example](#basic-example)
- [Available Overrides](#available-overrides)
- [Old Movement Behaviour](#old-movement-behaviour)
- [Mod Incompatibility](#mod-incompatibility)
- [Your Turn](#your-turn)
## Basic Example
Here is an example of how to add an antigravity command, which
puts the caller in low G:
```lua
core.register_chatcommand("antigravity", {
func = function(name, param)
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)
})
end,
})
```
## Available Overrides
`player:set_physics_override()` is given a table of overrides.\\
According to [lua_api.md](https://minetest.gitlab.io/minetest/class-reference/#player-only-no-op-for-other-objects),
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 the player can sneak (default: true)
### Old Movement Behaviour
Player movement prior to the 0.4.16 release included the sneak glitch, which
allows various movement glitches, including the ability
to climb an 'elevator' made from a certain placement of nodes by sneaking
(pressing shift) and pressing space to ascend. Though the behaviour was
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)
## Mod Incompatibility
Please be warned that mods which override the same physics value of a player tend
to be incompatible with each other. When setting an override, it overwrites
any overrides that have been set before. This means that if multiple overrides set a
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 metres (1 metre is 1 node).
* **Space**: Make gravity decrease as the player gets higher.

View File

@ -1,138 +0,0 @@
---
title: Privileges
layout: default
root: ../..
idx: 4.1
description: Registering privs.
redirect_from: /en/chapters/privileges.html
---
## 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-basicprivs)
## When to use Privileges
A privilege should give a player the ability to do something.
Privileges are **not** for indicating class or status.
**Good Privileges:**
* interact
* shout
* noclip
* fly
* kick
* ban
* vote
* worldedit
* area_admin - admin functions of one mod is ok
**Bad Privileges:**
* moderator
* admin
* elf
* dwarf
## Declaring Privileges
Use `register_privilege` to declare a new privilege:
```lua
core.register_privilege("vote", {
description = "Can vote on issues",
give_to_singleplayer = true
})
```
`give_to_singleplayer` defaults to true when not specified, so it isn't
actually needed in the above definition.
## Checking for Privileges
To quickly check whether a player has all the required privileges:
```lua
local has, missing = core.check_player_privs(player_or_name, {
interact = true,
vote = true })
```
In this example, `has` is true if the player has all the privileges needed.
If `has` is false, then `missing` will contain a key-value table
of the missing privileges.
```lua
local has, missing = core.check_player_privs(name, {
interact = true,
vote = true })
if has then
print("Player has all privs!")
else
print("Player is missing privs: " .. dump(missing))
end
```
If you don't need to check the missing privileges, you can put
`check_player_privs` directly into the if statement.
```lua
if not core.check_player_privs(name, { interact=true }) then
return false, "You need interact for this!"
end
```
## Getting and Setting Privileges
Player privileges can be accessed or modified regardless of the player
being online.
```lua
local privs = core.get_player_privs(name)
print(dump(privs))
privs.vote = true
core.set_player_privs(name, privs)
```
Privileges are always specified as a key-value table with the key being
the privilege name and the value being a boolean.
```lua
{
fly = true,
interact = true,
shout = true
}
```
## 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
they can grant and revoke `interact` and `shout`, but can't grant themselves or other
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
grant and revoke from other players, you must change the `basic_privs` setting.
By default, `basic_privs` has the following value:
basic_privs = interact, shout
To add `vote`, update this to:
basic_privs = interact, shout, vote
This will allow players with `basic_privs` to grant and revoke the `vote` privilege.

View File

@ -1,5 +0,0 @@
---
sitemap: false
redirect_from: /en/chapters/sfinv.html
redirect_to: "https://github.com/rubenwardy/sfinv/blob/master/Tutorial.md"
---

View File

@ -1,253 +0,0 @@
---
title: Intro to Clean Architectures
layout: default
root: ../..
idx: 8.4
---
## 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
a dynamically typed language like Lua, given that the compiler gives you very little
compiler-time help when it comes to things like making sure that types are used correctly.
This chapter covers important concepts needed to keep your code clean,
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)
- [Observer](#observer)
- [Model-View-Controller](#model-view-controller)
- [API-View](#api-view)
- [Conclusion](#conclusion)
## Cohesion, Coupling, and Separation of Concerns
Without any planning, a programming project will tend to gradually descend into
spaghetti code. Spaghetti code is characterised by a lack of structure - all the
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. <!-- Weird wording? -->
> Inside every large program, there is a small program trying to get out.
>
> --C.A.R. Hoare
This should be done in such a way that you achieve Separation of Concerns -
each area should be distinct and address a separate need or concern.
These programs/areas should have the following two properties:
* **High Cohesion** - the area should be closely/tightly related.
* **Low Coupling** - keep dependencies between areas as low as possible, and avoid
relying on internal implementations. It's a very good idea to make sure you have
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.
## 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 (`core.*`, other mods)
as much as possible.
One way to do this is to think about:
* What **data** you have.
* What **actions** you can take with this data.
* How **events** (ie: formspec, punches, etc) trigger these actions, and how
these actions cause things to happen in the engine.
Let's take an example of a land protection mod. The data you have is the areas
and any associated metadata. Actions you can take are `create`, `edit`, or
`delete`. The events that trigger these actions are chat commands and formspec
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. 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.)
You should write your data representation using Pure Lua. "Pure" in this context
means that the functions could run outside of Minetest - none of the engine's
functions are called.
```lua
-- Data
function land.create(name, area_name)
land.lands[area_name] = {
name = area_name,
owner = name,
-- more stuff
}
end
function land.get_by_name(area_name)
return land.lands[area_name]
end
```
Your actions should also be pure, but calling other functions is more
acceptable than in the above.
```lua
-- Controller
function land.handle_create_submit(name, area_name)
-- process stuff
-- (ie: check for overlaps, check quotas, check permissions)
land.create(name, area_name)
end
function land.handle_creation_request(name)
-- This is a bad example, as explained later
land.show_create_formspec(name)
end
```
Your event handlers will have to interact with the Minetest API. You should keep
the number of calculations to a minimum, as you won't be able to test this area
very easily.
```lua
-- View
function land.show_create_formspec(name)
-- Note how there's no complex calculations here!
return [[
size[4,3]
label[1,0;This is an example]
field[0,1;3,1;area_name;]
button_exit[0,2;1,1;exit;Exit]
]]
end
core.register_chatcommand("/land", {
privs = { land = true },
func = function(name)
land.handle_creation_request(name)
end,
})
core.register_on_player_receive_fields(function(player,
formname, fields)
land.handle_create_submit(player:get_player_name(),
fields.area_name)
end)
```
The above is the Model-View-Controller pattern. The model is a collection of data
with minimal functions. The view is a collection of functions which listen to
events and pass it to the controller, and also receives calls from the controller to
do something with the Minetest API. The controller is where the decisions and
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 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.
<figure class="right_image">
<img
width="100%"
src="{{ page.root }}/static/mvc_diagram.svg"
alt="Diagram showing a centered text element">
</figure>
It is important that each area only communicates with its direct neighbours,
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 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 compromise is to reduce the mod into two
parts:
* **API** - This was the model and controller above. There should be no uses of
`core.` here.
* **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
follows this design. `api.lua` is almost all pure Lua functions handling the data
storage and controller-style calculations. `gui.lua` is the view for formspecs
and formspec submission, and `async_crafter.lua` is the view and controller for
a node formspec and node timers.
Separating the mod like this means that you can very easily test the API part,
as it doesn't use any Minetest APIs - as shown in the
[next chapter](unit_testing.html) and seen in the crafting mod.
## Conclusion
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.
I highly recommend reading the [Game Programming Patterns](http://gameprogrammingpatterns.com/)
book. It's freely available to [read online](http://gameprogrammingpatterns.com/contents.html)
and goes into much more detail on common programming patterns relevant to games.

View File

@ -1,140 +0,0 @@
---
title: Common Mistakes
layout: default
root: ../..
idx: 8.1
redirect_from: /en/chapters/common_mistakes.html
---
## Introduction <!-- omit in toc -->
This chapter details common mistakes, and how to avoid them.
- [Be Careful When Storing ObjectRefs (ie: players or entities)](#be-careful-when-storing-objectrefs-ie-players-or-entities)
- [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)
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.
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
-- 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.
For example, the following code has a vulnerability which allows players to
give themselves moderator privileges:
```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;Name;]
button_exit[0,1;3,1;sub;Promote]
]])
return true
})
core.register_on_player_receive_fields(function(player,
formname, fields)
-- BAD! Missing privilege check here!
local privs = core.get_player_privs(fields.target)
privs.kick = true
privs.ban = true
core.set_player_privs(fields.target, privs)
return true
end)
```
Add a privilege check to solve this:
```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)
```
## Set ItemStacks After Changing Them
Have you noticed that it's simply called an `ItemStack` in the API, not an `ItemStackRef`,
similar to `InvRef`? This is because an `ItemStack` isn't a reference - it's a
copy. Stacks work on a copy of the data rather than the stack in the inventory.
This means that modifying a stack won't actually modify that stack in the inventory.
For example, don't do this:
```lua
local inv = player:get_inventory()
local stack = inv:get_stack("main", 1)
stack:get_meta():set_string("description", "Partially eaten")
-- BAD! Modification will be lost
```
Do this instead:
```lua
local inv = player:get_inventory()
local stack = inv:get_stack("main", 1)
stack:get_meta():set_string("description", "Partially eaten")
inv:set_stack("main", 1, stack)
-- Correct! Item stack is set
```
The behaviour of callbacks is slightly more complicated. Modifying an `ItemStack` you
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.
```lua
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
-- callback cancels the behaviour
end)
```
If no callbacks cancel this, the stack will be set and the description will be updated,
but if a callback does cancel this, then the update may be lost.
It's better to do this instead:
```lua
core.register_on_item_eat(function(hp_change, replace_with_item,
itemstack, user, pointed_thing)
itemstack:get_meta():set_string("description", "Partially eaten")
user:get_inventory():set_stack("main", user:get_wield_index(),
itemstack)
-- Correct, description will always be set!
end)
```
If the callbacks cancel or the callback runner doesn't set the stack,
then the update will still be set.
If the callbacks or the callback runner set the stack, then the use of
set_stack doesn't matter.

View File

@ -1,107 +0,0 @@
---
title: Automatic Error Checking
layout: default
root: ../..
idx: 8.2
description: Use LuaCheck to find errors
redirect_from: /en/chapters/luacheck.html
---
## Introduction <!-- omit in toc -->
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)
- [Using with editor](#using-with-editor)
## Installing LuaCheck
### Windows
Simply download luacheck.exe from
[the Github Releases page](https://github.com/mpeterv/luacheck/releases).
### Linux
First, you'll need to install LuaRocks:
sudo apt install luarocks
You can then install LuaCheck globally:
sudo luarocks install luacheck
Check that it's installed with the following command:
luacheck -v
## Running LuaCheck
The first time you run LuaCheck, it will probably pick up a lot of false
errors. This is because it still needs to be configured.
On Windows, open powershell or bash in the root folder of your project
and run `path\to\luacheck.exe .`
On Linux, run `luacheck .` whilst in the root folder of your project.
## Configuring LuaCheck
Create a file called .luacheckrc in the root of your project. This could be the
root of your game, modpack, or mod.
Put the following contents in it:
```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",
}
```
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,
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,
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`, or stop writing to foobar.
## Using with editor
It is highly recommended that you find and install a plugin for your editor of choice
to show you errors without running a command. Most editors will likely have a plugin
available.
* **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).

View File

@ -1,26 +0,0 @@
---
title: Read More
layout: default
root: ../..
idx: 8.7
redirect_from: /en/chapters/readmore.html
---
## List of Resources
After you've read this book, take a look at the following.
### Minetest Modding
* Minetest's Lua API Reference - [multiple page version](https://minetest.gitlab.io/minetest/class-reference/#player-only-no-op-for-other-objects) |
[single page version](https://github.com/minetest/minetest/blob/master/doc/lua_api.md).
* Look at [existing mods](https://forum.minetest.net/viewforum.php?f=11).
### Lua Programming
* [Programming in Lua (PIL)](http://www.lua.org/pil/).
### 3D Modelling
* [Blender 3D: Noob to pro](https://en.wikibooks.org/wiki/Blender_3D:_Noob_to_Pro).
* [Using Blender with Minetest](http://wiki.minetest.net/Using_Blender).

View File

@ -1,166 +0,0 @@
---
title: Releasing a Mod
layout: default
root: ../..
idx: 8.6
redirect_from: /en/chapters/releasing.html
---
## 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.
- [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)
## 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
a license, people won't know whether they are allowed to modify, distribute or use your
mod on a public server.
Your code and your art need different things from the licenses they use. For example,
Creative Commons licenses shouldn't be used with source code,
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 to modify it and release the modified version.)
Please note that **public domain is not a valid licence**, because the definition varies
in different countries.
It is important to note that WTFPL is
[strongly discouraged](https://content.minetest.net/help/wtfpl/) and people may
choose not to use your mod if it has this license.
### LGPL and CC-BY-SA
This is a common license combination in the Minetest community, and is what
Minetest and Minetest Game use.
You license your code under LGPL 2.1 and your art under CC-BY-SA.
This means that:
* Anyone can modify, redistribute and sell modified or unmodified versions.
* If someone modifies your mod, they must give their version the same license.
* Your copyright notice must be kept.
### CC0
This license can be used for both code and art, and allows anyone to do what
they want with your work. This means they can modify, redistribute, sell, or
leave-out attribution.
### MIT
This is a common license for code. The only restriction it places on users
of your code is that they must include the same copyright notice and license
in any copies of the code or of substantial parts of the code.
## Packaging
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:
* What the mod/game does, how to use it.
* What the license is.
* Optionally:
* where to report problems or get help.
* credits
### mod.conf / game.conf
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:
description = Adds soup, cakes, bakes and juices.
Avoid this:
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 inside of Minetest as a thumbnail for the content.
## Uploading
So that a potential user can download your mod, you need to upload it somewhere
publicly accessible. There are several ways to do this, but you should use the
approach that works best for you, as long as it meets these requirements, and any
others which may be added by forum moderators:
* **Stable** - The hosting website should be unlikely to shut down without warning.
* **Direct link** - You should be able to click a link and download the file
without having to view another page.
* **Virus Free** - Scammy upload hosts may contain insecure adverts.
ContentDB allows you to upload zip files, and meets these criteria.
### Version Control Systems
A Version Control System (VCS) is software that manages changes to software,
often making it easier to distribute and receive contributed changes.
The majority of Minetest modders use Git and a website like GitHub to distribute
their code.
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.
## Releasing on ContentDB
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.
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 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."
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:
* [Mod] Mod Title [modname]
* [Mod] Mod Title [version number] [modname]
For example:
* [Mod] More Blox [0.1] [moreblox]

View File

@ -1,110 +0,0 @@
---
title: Security
layout: default
root: ../..
idx: 8.3
---
## 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
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 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 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.
## Formspecs
### Never Trust Submissions
Any users can submit almost any formspec with any values at any time.
Here's some real code found in a mod:
```lua
core.register_on_player_receive_fields(function(player,
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
```
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 previously in
the Formspecs chapter.
### Time of Check isn't Time of Use
Any users can submit any formspec with any values at any time, except where the
engine forbids it:
* A node formspec submission will be blocked if the user is too far away.
* 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, 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).
## (Insecure) Environments
Minetest allows mods to request an unsandboxed environment, giving them access
to the full Lua API.
Can you spot the vulnerability in the following?
```lua
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`.
A malicious mod could override this function and pass stuff to os.execute:
```lua
string.format = function()
return "xdg-open 'http://example.com'"
end
```
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:
* Always store it in a local and never pass it into a function.
* Make sure you can trust any input given to an insecure function, to avoid the
issue above. This means avoiding globally redefinable functions.

View File

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

View File

@ -1,172 +0,0 @@
---
title: Automatic Unit Testing
layout: default
root: ../..
idx: 8.5
---
## 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 structure your code avoid this.
- [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.
* 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 install Busted globally:
sudo luarocks install busted
Finally, check that it is installed:
busted --version
## Your First Test
Busted is Lua's leading unit test framework. Busted looks for Lua files with
names ending in `_spec`, and then executes them in a standalone Lua environment.
mymod/
├── init.lua
├── api.lua
└── tests
└── api_spec.lua
### init.lua
```lua
mymod = {}
dofile(core.get_modpath("mymod") .. "/api.lua")
```
### api.lua
```lua
function mymod.add(x, y)
return x + y
end
```
### tests/api_spec.lua
```lua
-- Look for required things in
package.path = "../?.lua;" .. package.path
-- Set mymod global for API to write into
_G.mymod = {} --_
-- Run api.lua file
require("api")
-- Tests
describe("add", function()
it("adds", function()
assert.equals(2, mymod.add(1, 1))
end)
it("supports negatives", function()
assert.equals(0, mymod.add(-1, 1))
assert.equals(-2, mymod.add(-1, -1))
end)
end)
```
You can now run the tests by opening a terminal in the mod's directory and
running `busted .`
It's important that the API file doesn't create the table itself, as globals in
Busted work differently. Any variable which would be global in Minetest is instead
a file local in busted. This would have been a better way for Minetest to do things,
but it's too late for that now.
Another thing to note is that any files you're testing should avoid calls to any
functions not inside of it. You tend to only write tests for a single file at once.
## Mocking: Using External Functions
Mocking is the practice of replacing functions that the thing you're testing depends
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
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.
```lua
-- As above, make a table
_G.minetest = {}
-- Define the mock function
local chat_send_all_calls = {}
function core.chat_send_all(name, message)
table.insert(chat_send_all_calls, { name = name, message = message })
end
-- Tests
describe("list_areas", function()
it("returns a line for each area", function()
chat_send_all_calls = {} -- reset table
mymod.list_areas_to_chat("singleplayer", "singleplayer")
assert.equals(2, #chat_send_all_calls)
end)
it("sends to right player", function()
chat_send_all_calls = {} -- reset table
mymod.list_areas_to_chat("singleplayer", "singleplayer")
for _, call in pairs(chat_send_all_calls) do --_
assert.equals("singleplayer", call.name)
end
end)
-- The above two tests are actually pointless,
-- as this one tests both things
it("returns correct thing", function()
chat_send_all_calls = {} -- reset table
mymod.list_areas_to_chat("singleplayer", "singleplayer")
local expected = {
{ name = "singleplayer", message = "Town Hall (2,43,63)" },
{ name = "singleplayer", message = "Airport (43,45,63)" },
}
assert.same(expected, chat_send_all_calls)
end)
end)
```
## Conclusion
Unit tests will greatly increase the quality and reliability of your project if used
well, but they require you to structure your code in a different way than usual.
For an example of a mod with lots of unit tests, see
[crafting by rubenwardy](https://github.com/rubenwardy/crafting).

View File

@ -1,21 +0,0 @@
{% if include.notice %}
{% assign notice=include.notice %}
{% else %}
{% assign notice=include %}
{% endif %}
<div class="notice notice-{{ notice.level }} {{ notice.classes }}">
{% if notice.level == "warning" %}
<span></span>
{% else if notice.level == "tip" %}
<span>i</span>
{% else %}
<span>?</span>
{% endif %}
<div>
{% if notice.title %}
<h2>{{ notice.title }}</h2>
{% endif %}
{{ notice.message | markdownify }}
</div>
</div>

View File

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

View File

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

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

View File

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

View File

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

View File

@ -1,35 +0,0 @@
---
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).

View File

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

View File

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

View File

@ -1,319 +0,0 @@
---
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", {})
```

View File

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

@ -1,296 +0,0 @@
---
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à.

View File

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

View File

@ -1,315 +0,0 @@
---
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).

View File

@ -1,217 +0,0 @@
---
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`)

View File

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

View File

@ -1,190 +0,0 @@
---
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)
```

View File

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

View File

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

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

View File

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

View File

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

View File

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

@ -1,127 +0,0 @@
---
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)
```

View File

@ -1,101 +0,0 @@
---
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).

View File

@ -1,26 +0,0 @@
---
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).

View File

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

View File

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

View File

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

View File

@ -1,160 +0,0 @@
---
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).

View File

@ -1,56 +0,0 @@
---
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,9 +0,0 @@
---
# Jekyll layout that compresses HTML
# v2.0.0
# http://jch.penibelst.de/
# © 20142015 Anatol Broder
# MIT License
---
{% if site.compress_html.ignore.envs contains jekyll.environment %}{{ content }}{% else %}{% capture _content %}{{ content }}{% endcapture %}{% assign _profile = site.compress_html.profile %}{% if site.compress_html.endings == "all" %}{% assign _endings = "html head body li dt dd p rt rp optgroup option colgroup caption thead tbody tfoot tr td th" | split: " " %}{% else %}{% assign _endings = site.compress_html.endings %}{% endif %}{% for _element in _endings %}{% capture _end %}</{{ _element }}>{% endcapture %}{% assign _content = _content | remove: _end %}{% endfor %}{% if _profile and _endings %}{% assign _profile_endings = _content | size | plus: 1 %}{% endif %}{% assign _pre_befores = _content | split: "<pre" %}{% assign _content = "" %}{% for _pre_before in _pre_befores %}{% assign _pres = _pre_before | split: "</pre>" %}{% case _pres.size %}{% when 2 %}{% capture _content %}{{ _content }}<pre{{ _pres.first }}</pre>{{ _pres.last | split: " " | join: " " }}{% endcapture %}{% when 1 %}{% capture _content %}{{ _content }}{{ _pres.last | split: " " | join: " " }}{% endcapture %}{% endcase %}{% endfor %}{% if _profile %}{% assign _profile_collapse = _content | size | plus: 1 %}{% endif %}{% if site.compress_html.comments == "all" %}{% assign _comments = "<!-- -->" | split: " " %}{% else %}{% assign _comments = site.compress_html.comments %}{% endif %}{% if _comments.size == 2 %}{% assign _comment_befores = _content | split: _comments.first %}{% for _comment_before in _comment_befores %}{% assign _comment_content = _comment_before | split: _comments.last | first %}{% if _comment_content %}{% capture _comment %}{{ _comments.first }}{{ _comment_content }}{{ _comments.last }}{% endcapture %}{% assign _content = _content | remove: _comment %}{% endif %}{% endfor %}{% if _profile %}{% assign _profile_comments = _content | size | plus: 1 %}{% endif %}{% endif %}{% if site.compress_html.clippings == "all" %}{% assign _clippings = "html head title base link meta style body article section nav aside h1 h2 h3 h4 h5 h6 hgroup header footer address p hr blockquote ol ul li dl dt dd figure figcaption main div table caption colgroup col tbody thead tfoot tr td th" | split: " " %}{% else %}{% assign _clippings = site.compress_html.clippings %}{% endif %}{% for _element in _clippings %}{% assign _edges = " <e;<e; </e>;</e>;</e> ;</e>" | replace: "e", _element | split: ";" %}{% assign _content = _content | replace: _edges[0], _edges[1] | replace: _edges[2], _edges[3] | replace: _edges[4], _edges[5] %}{% endfor %}{% if _profile and _clippings %}{% assign _profile_clippings = _content | size | plus: 1 %}{% endif %}{{ _content }}{% if _profile %} <table id="compress_html_profile_{{ site.time | date: "%Y%m%d" }}" class="compress_html_profile"> <thead> <tr> <td>Step <td>Bytes <tbody> <tr> <td>raw <td>{{ content | size }}{% if _profile_endings %} <tr> <td>endings <td>{{ _profile_endings }}{% endif %}{% if _profile_collapse %} <tr> <td>collapse <td>{{ _profile_collapse }}{% endif %}{% if _profile_comments %} <tr> <td>comments <td>{{ _profile_comments }}{% endif %}{% if _profile_clippings %} <tr> <td>clippings <td>{{ _profile_clippings }}{% endif %} </table>{% endif %}{% endif %}

View File

@ -1,74 +0,0 @@
---
layout: base
---
{% assign pathsplit = page.path | split: '/' %}
{% assign language = pathsplit[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" %}
{% assign num = 0 %}
<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 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 %}>
<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 %}
<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-{{ 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

@ -1,83 +0,0 @@
code {
display: inline-block;
padding: 0 0.25em;
margin: 2px;
background: #f9f9f9;
border: 1px solid #e0e0e0;
border-radius: 2px;
white-space: pre;
max-width: 95%;
overflow-x: auto;
font-size: 14px;
vertical-align: middle;
}
pre code {
background: #f0f0f0;
padding: 5px 10px;
display: block;
}
/* https://raw.githubusercontent.com/richleland/pygments-css/master/friendly.css */
.highlight .hll { background-color: #ffffcc }
.highlight .c { color: #60a0b0; font-style: italic } /* Comment */
.highlight .err { border: 1px solid #FF0000 } /* Error */
.highlight .k { color: #007020; font-weight: bold } /* Keyword */
.highlight .o { color: #666666 } /* Operator */
.highlight .cm { color: #60a0b0; font-style: italic } /* Comment.Multiline */
.highlight .cp { color: #007020 } /* Comment.Preproc */
.highlight .c1 { color: #60a0b0; font-style: italic } /* Comment.Single */
.highlight .cs { color: #60a0b0; background-color: #fff0f0 } /* Comment.Special */
.highlight .gd { color: #A00000 } /* Generic.Deleted */
.highlight .ge { font-style: italic } /* Generic.Emph */
.highlight .gr { color: #FF0000 } /* Generic.Error */
.highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */
.highlight .gi { color: #00A000 } /* Generic.Inserted */
.highlight .go { color: #808080 } /* Generic.Output */
.highlight .gp { color: #c65d09; font-weight: bold } /* Generic.Prompt */
.highlight .gs { font-weight: bold } /* Generic.Strong */
.highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */
.highlight .gt { color: #0040D0 } /* Generic.Traceback */
.highlight .kc { color: #007020; font-weight: bold } /* Keyword.Constant */
.highlight .kd { color: #007020; font-weight: bold } /* Keyword.Declaration */
.highlight .kn { color: #007020; font-weight: bold } /* Keyword.Namespace */
.highlight .kp { color: #007020 } /* Keyword.Pseudo */
.highlight .kr { color: #007020; font-weight: bold } /* Keyword.Reserved */
.highlight .kt { color: #902000 } /* Keyword.Type */
.highlight .m { color: #40a070 } /* Literal.Number */
.highlight .s { color: #4070a0 } /* Literal.String */
.highlight .na { color: #4070a0 } /* Name.Attribute */
.highlight .nb { color: #007020 } /* Name.Builtin */
.highlight .nc { color: #0e84b5; font-weight: bold } /* Name.Class */
.highlight .no { color: #60add5 } /* Name.Constant */
.highlight .nd { color: #555555; font-weight: bold } /* Name.Decorator */
.highlight .ni { color: #d55537; font-weight: bold } /* Name.Entity */
.highlight .ne { color: #007020 } /* Name.Exception */
.highlight .nf { color: #06287e } /* Name.Function */
.highlight .nl { color: #002070; font-weight: bold } /* Name.Label */
.highlight .nn { color: #0e84b5; font-weight: bold } /* Name.Namespace */
.highlight .nt { color: #062873; font-weight: bold } /* Name.Tag */
.highlight .nv { color: #bb60d5 } /* Name.Variable */
.highlight .ow { color: #007020; font-weight: bold } /* Operator.Word */
.highlight .w { color: #bbbbbb } /* Text.Whitespace */
.highlight .mf { color: #40a070 } /* Literal.Number.Float */
.highlight .mh { color: #40a070 } /* Literal.Number.Hex */
.highlight .mi { color: #40a070 } /* Literal.Number.Integer */
.highlight .mo { color: #40a070 } /* Literal.Number.Oct */
.highlight .sb { color: #4070a0 } /* Literal.String.Backtick */
.highlight .sc { color: #4070a0 } /* Literal.String.Char */
.highlight .sd { color: #4070a0; font-style: italic } /* Literal.String.Doc */
.highlight .s2 { color: #4070a0 } /* Literal.String.Double */
.highlight .se { color: #4070a0; font-weight: bold } /* Literal.String.Escape */
.highlight .sh { color: #4070a0 } /* Literal.String.Heredoc */
.highlight .si { color: #70a0d0; font-style: italic } /* Literal.String.Interpol */
.highlight .sx { color: #c65d09 } /* Literal.String.Other */
.highlight .sr { color: #235388 } /* Literal.String.Regex */
.highlight .s1 { color: #4070a0 } /* Literal.String.Single */
.highlight .ss { color: #517918 } /* Literal.String.Symbol */
.highlight .bp { color: #007020 } /* Name.Builtin.Pseudo */
.highlight .vc { color: #bb60d5 } /* Name.Variable.Class */
.highlight .vg { color: #bb60d5 } /* Name.Variable.Global */
.highlight .vi { color: #bb60d5 } /* Name.Variable.Instance */
.highlight .il { color: #40a070 } /* Literal.Number.Integer.Long */

View File

@ -1,115 +0,0 @@
a {
color: blue;
text-decoration: underline;
}
img {
max-width: 100%;
}
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;
}
.right_image img {
margin: 0;
padding: 0;
}
.right_image figcaption {
padding: 0 0 0 6px;
}
.header-link, .anchor {
text-decoration: none;
color: #bbb;
padding: 0 10px 0 0;
}
.header-link {
position: absolute;
right: 10px;
}
.header-link:hover {
color: #999;
}
h1 {
text-align: center;
margin: 0;
padding: 10px 0 0 0;
}
h2 {
border-bottom: 1px solid #bbb;
margin: 30px 0 10px 0;
display: block;
padding: 0 0 5px 0;
clear: both;
}
h3 {
font-size: 105%;
font-weight: bold;
margin: 30px 0 10px 0;
}
.toc > ul > li > ul > li > ul {
display: none;
}
.prevnext {
display: flex;
list-style: none;
margin: 0 auto;
padding: 0;
background: #eee;
li {
display: inline-block;
list-style: none;
margin: 0;
padding: 0;
flex: 1;
a {
display: block;
margin: 0;
padding: 1em 0;
text-align: center;
font-size: 100%;
text-decoration: none;
color: black;
}
a:hover {
text-decoration: none;
background: #fff;
}
}
li:first-child {
border-right: 2px solid white;
}
li:last-child {
border-left: 2px solid white;
}
}

View File

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

Some files were not shown because too many files have changed in this diff Show More