Nodes, Items, and Crafting: Rewrite chapter

This commit is contained in:
rubenwardy 2018-09-19 12:02:40 +01:00
parent c1a2528c72
commit c0045d484a
No known key found for this signature in database
GPG Key ID: A1E29D52FF81513C
2 changed files with 226 additions and 194 deletions

View File

@ -119,7 +119,7 @@ It's good practice to make sure a variable is only ever nil or a single non-nil
| Number | A whole or decimal number. | `local A = 4` | | Number | A whole or decimal number. | `local A = 4` |
| String | A piece of text | `local D = "one two three" | | String | A piece of text | `local D = "one two three" |
| Boolean | True or False | `local is_true = false`, `local E = (1 == 1)`` | | Boolean | True or False | `local is_true = false`, `local E = (1 == 1)` |
| Table | Lists | Explained below | | Table | Lists | Explained below |
| Function | Can run. May require inputs and may return a value | `local result = func(1, 2, 3)` | | Function | Can run. May require inputs and may return a value | `local result = func(1, 2, 3)` |

View File

@ -12,144 +12,102 @@ redirect_from: /en/chapters/nodes_items_crafting.html
Registering new nodes and craftitems, and creating craft recipes, are Registering new nodes and craftitems, and creating craft recipes, are
basic requirements for many mods. basic requirements for many mods.
* [Item Strings](#item-strings) * [What are Nodes and Items?](#what-are-nodes-and-items)
* [Overriding](#overriding) * [Registering Items](#registering-items)
* [Textures](#textures) * [Item Names and Aliases](#item-names-and-aliases)
* [Registering a Craftitem](#registering-a-craftitem) * [Textures](#textures)
* [Foods](#foods)
* [Foods, extended](#foods-extended)
* [Registering a Basic Node](#registering-a-basic-node) * [Registering a Basic Node](#registering-a-basic-node)
* [Actions and Callbacks](#actions-and-callbacks)
* [on_use](#on_use)
* [Crafting](#crafting) * [Crafting](#crafting)
* [Shaped](#shaped)
* [Shapeless](#shapeless)
* [Cooking](#cooking)
* [Fuel](#fuel) * [Fuel](#fuel)
* [Groups](#groups) * [Groups](#groups)
* [Dig Types](#dig-types) * [Tools, Capabilities and Dig Types](#tools-capabilities-and-dig-types)
## Item Strings ## What are Nodes and Items?
A string in programming terms is a piece of text. Nodes, Craftitems, and Tools are all Items.
Each in-game item, whether a node, craftitem, tool, or entity, has an item string. An item is something that could be found in an inventory -
This is sometimes referred to as its registered name or just its name. It takes the format: even though it may not be possible through normal game play.
A node is an item which is placable or can 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 isn't placable but is only found in inventories or as a dropped item
in the world.
A tool has the ability to wear and typically has non-default digging capabilities.
In the future, it's likely that craftitems and tools will merge into one type of
item, as the distinction between them is rather artificial.
## Registering Items
Item definitions consist of an *item name* and a *definition table*.
The definition table contains attributes which affect the behaviour of the item.
{% highlight lua %}
minetest.register_craftitem("modname:itemname", {
description = "My Special Item",
inventory_image = "modname_itemname.png"
})
{% endhighlight %}
### Item Names and Aliases
Every item has an item name used to refer to it, which should be in the
following format:
modname:itemname modname:itemname
The modname is the name of the mod in which the item is registered, and the The modname is the name of the mod in which the item is registered, and the
itemname is the name of the item itself. item name is the name of the item itself.
The itemname should be relevant to what the item is and can't already be registered. The item name should be relevant to what the item is and can't already be registered.
### Overriding Items can also have *aliases* pointing to their name.
An *alias* is a pseudo-item name which results in the engine treating any
occurrences of the alias as if it were the item name.
There are two main common uses of this:
Overriding allows you to: * Renaming removed items to something else.
There may be unknown nodes in the world and in inventories if an item is
removed from a mod without any corrective code.
It's important to avoid aliasing to an unobtainable node if the remove node
could be obtained.
* Adding a shortcut. `/giveme dirt` is easier than `/giveme default:dirt`.
* Redefine an existing item. Registering an alias is pretty simple.
* Use a different modname. A good way to remember is `from → to` where *from* is the alias and *to*
is the target.
To override an item, prefix the item string with a colon, ``:``. {% highlight lua %}
For example, declaring an item as ``:default:dirt`` will override minetest.register_alias("dirt", "default:dirt")
default:dirt in the default mod. {% endhighlight %}
## Textures 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:
{% highlight lua %}
itemname = minetest.registered_aliases[itemname] or itemname
{% endhighlight %}
### 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. 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, They can be any resolution, but it is recommended that they are in the order of 2,
for example 16, 32, 64, or 128, for example 16, 32, 64, or 128.
because other resolutions may not be supported correctly on older devices. This is because other resolutions may not be supported correctly on older devices,
resulting in lowered performance.
Textures should be placed in the textures/ folder with names in the format
``modname_itemname.png``.\\
JPEG textures are supported, but do not support transparency and are generally
bad quality at low resolutions. It is often better to use the PNG format.
## Registering a Craftitem
Craftitems are the simplest items in Minetest. They cannot be placed in the world.
They are used in recipes to create other items, or can be used by the player.
Examples include food items which can be eaten and metal ingots which can be
crafted into tools or placeable nodes.
Item definitions usually include a unique
[item string](#item-strings) and a definition table. The definition table
contains attributes which affect the behaviour of the item. For example:
{% highlight lua %}
minetest.register_craftitem("mymod:diamond_fragments", {
description = "Alien Diamond Fragments",
inventory_image = "mymod_diamond_fragments.png"
})
{% endhighlight %}
### Foods
Foods are items which restore health. To create a food item you need to define
the on_use property of the item:
{% highlight lua %}
minetest.register_craftitem("mymod:mudpie", {
description = "Alien Mud Pie",
inventory_image = "myfood_mudpie.png",
on_use = minetest.item_eat(20)
})
{% endhighlight %}
The number supplied to the minetest.item_eat function is the number of hit points
healed when this food is consumed.
Each heart icon the player has is worth two hitpoints. A player can usually have up to
10 hearts, which is equal to 20 hitpoints.
Hitpoints don't have to be integers (whole numbers); they can be decimals.
Sometimes you may want a food item to be replaced with another item after it is eaten,
for example smaller pieces of cake or bones after eating meat. To do this, use:
minetest.item_eat(hp, replace_with_item)
In this example replace_with_item must be an item string.
### Foods, extended
How about if you want to do more than just eat the item,
such as send a message to the player?
{% highlight lua %}
minetest.register_craftitem("mymod:mudpie", {
description = "Alien Mud Pie",
inventory_image = "myfood_mudpie.png",
on_use = function(itemstack, user, pointed_thing)
local hp_change = 20
local 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
})
{% endhighlight %}
If you are creating a hunger mod, or if you are affecting foods outside of your
mod, you should consider using minetest.register_on_item_eat
## Registering a basic node ## Registering a basic node
In Minetest, a node is an item that you can place.
Most nodes are 1m x 1m x 1m cubes; however, the shape doesn't
have to be a cube - as we will explore later.
Let's get onto it. A node's definition table is very similar to a craftitem's
definition table; however, you need to set the textures for the faces of the cube.
{% highlight lua %} {% highlight lua %}
minetest.register_node("mymod:diamond", { minetest.register_node("mymod:diamond", {
description = "Alien Diamond", description = "Alien Diamond",
@ -159,29 +117,25 @@ minetest.register_node("mymod:diamond", {
}) })
{% endhighlight %} {% endhighlight %}
Let's ignore ``groups`` for now, and take a look at the tiles. The `tiles` property is a table of texture names the node will use.
The ``tiles`` property is a table of texture names the node will use.
When there is only one texture, this texture is used on every side. When there is only one texture, this texture is used on every side.
To give a different texture per-side, supply the names of 6 textures in this order:
What if you would like a different texture for each side? up (+Y), down (-Y), right (+X), left (-X), back (+Z), front (-Z).
Well, you give a table of 6 texture names, in this order:\\ (+Y, -Y, +X, -X, +Z, -Z)
up (+Y), down (-Y), right (+X), left (-X), back (+Z), front (-Z).
(+Y, -Y, +X, -X, +Z, -Z)
Remember: +Y is upwards in Minetest, along with most video games. Remember: Just like with most 3D graphics, +Y is upwards in Minetest.
A plus direction means that it is facing positive co-ordinates,
a negative direction means that it is facing negative co-ordinates.
{% highlight lua %} {% highlight lua %}
minetest.register_node("mymod:diamond", { minetest.register_node("mymod:diamond", {
description = "Alien Diamond", description = "Alien Diamond",
tiles = { tiles = {
"mymod_diamond_up.png", "mymod_diamond_up.png", -- y+
"mymod_diamond_down.png", "mymod_diamond_down.png", -- y-
"mymod_diamond_right.png", "mymod_diamond_right.png", -- x+
"mymod_diamond_left.png", "mymod_diamond_left.png", -- x-
"mymod_diamond_back.png", "mymod_diamond_back.png", -- z+
"mymod_diamond_front.png" "mymod_diamond_front.png", -- z-
}, },
is_ground_content = true, is_ground_content = true,
groups = {cracky = 3}, groups = {cracky = 3},
@ -191,86 +145,122 @@ minetest.register_node("mymod:diamond", {
{% endhighlight %} {% endhighlight %}
The is_ground_content attribute allows caves to be generated over the stone. The is_ground_content attribute allows caves to be generated over the stone.
This is essential for any node which may be placed during map generation underground.
Caves are cut out of the world after all the other nodes in an area have generated.
## Actions and Callbacks
Minetest heavily uses a callback-based modding design.
Callbacks can be placed in the item definition table to allow response to various
different user events.
### on_use
By default, the use callback is triggered when a player left-clicks with an item.
Having a use callback prevents the item being used to dig nodes.
One common use of the use callback is for food:
{% highlight lua %}
minetest.register_craftitem("mymod:mudpie", {
description = "Alien Mud Pie",
inventory_image = "myfood_mudpie.png",
on_use = minetest.item_eat(20),
})
{% endhighlight %}
The number supplied to the minetest.item_eat function is the number of hit points
healed when this food is consumed.
Each heart icon the player has is worth two hitpoints.
A player can usually have up to 10 hearts, which is equal to 20 hitpoints.
Hitpoints don't have to be integers (whole numbers); they can be decimals.
minetest.item_eat() is a function which returns a function, setting it
as the on_use callback.
This means the code above is roughly similar to this:
{% highlight lua %}
minetest.register_craftitem("mymod:mudpie", {
description = "Alien Mud Pie",
inventory_image = "myfood_mudpie.png",
on_use = function(...)
return minetest.do_item_eat(20, nil, ...)
end,
})
{% endhighlight %}
By, understanding how item_eat works by simply returning a function, it's
possible to modify it to do more complex behaviour such as play a custom sound.
## Crafting ## Crafting
There are several types of crafting, identified by the ``type`` property. There are several types of crafting recipe available, indicated by the `type`
property.
* shaped - Ingredients must be in the correct position. * shaped - Ingredients must be in the correct position.
* shapeless - It doesn't matter where the ingredients are, * shapeless - It doesn't matter where the ingredients are,
just that there is the right amount. just that there is the right amount.
* cooking - Recipes for the furnace to use. * cooking - Recipes for the furnace to use.
* fuel - Defines items which can be burned in furnaces. * fuel - Defines items which can be burned in furnaces.
* tool_repair - Used to allow the repairing of tools. * tool_repair - Defines items which can be tool repaired.
Craft recipes do not use Item Strings to uniquely identify themselves. Craft recipes are not items, so they do not use Item Names to uniquely
identify themselves.
### Shaped ### Shaped
Shaped recipes are the normal recipes - the ingredients have to be in the Shaped recipes are when the ingredients need to be in the right shape or
right place. pattern to work. In the below example, the fragments need to be in a
For example, when you are making a pickaxe the ingredients have to be in the chair-like pattern for the craft to work.
right place for it to work.
{% highlight lua %} {% highlight lua %}
minetest.register_craft({ minetest.register_craft({
type = "shaped",
output = "mymod:diamond_chair 99", output = "mymod:diamond_chair 99",
recipe = { recipe = {
{"mymod:diamond_fragments", "", ""}, {"mymod:diamond_fragments", "", ""},
{"mymod:diamond_fragments", "mymod:diamond_fragments", ""}, {"mymod:diamond_fragments", "mymod:diamond_fragments", ""},
{"mymod:diamond_fragments", "mymod:diamond_fragments", ""} {"mymod:diamond_fragments", "mymod:diamond_fragments", ""}
} }
}) })
{% endhighlight %} {% endhighlight %}
This is pretty self-explanatory. You don't need to define the type, as One thing to note is the blank column on the right-hand side.
shaped crafts are default. The 99 after the itemname in output makes the This means that there *must* be an empty column to the right of the shape, otherwise
craft create 99 chairs rather than one. this won't work.
If this empty column shouldn't be required, then the empty strings can be left
If you notice, there is a blank column at the far end. out like so:
This means that the craft must always be exactly that.
In most cases, such as the door recipe, you don't care if the ingredients
are always in an exact place, you just want them correct relative to each
other. In order to do this, delete any empty rows and columns.
In the above case, there is an empty last column, which, when removed,
allows the recipe to be crafted if it was all moved one place to the right.
{% highlight lua %} {% highlight lua %}
minetest.register_craft({ minetest.register_craft({
output = "mymod:diamond_chair", output = "mymod:diamond_chair 99",
recipe = { recipe = {
{"mymod:diamond_fragments", ""}, {"mymod:diamond_fragments", "" },
{"mymod:diamond_fragments", "mymod:diamond_fragments"}, {"mymod:diamond_fragments", "mymod:diamond_fragments"},
{"mymod:diamond_fragments", "mymod:diamond_fragments"} {"mymod:diamond_fragments", "mymod:diamond_fragments"}
} }
}) })
{% endhighlight %} {% endhighlight %}
The type field isn't actually needed for shapeless crafts, as shapeless is the
default craft type.
### Shapeless ### Shapeless
Shapeless recipes are a type of recipe which is used when it doesn't matter 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. where the ingredients are placed, just that they're there.
For example, when you craft a bronze ingot, the steel and the copper do not
need to be in any specific place for it to work.
{% highlight lua %} {% highlight lua %}
minetest.register_craft({ minetest.register_craft({
type = "shapeless", type = "shapeless",
output = "mymod:diamond", output = "mymod:diamond 3",
recipe = {"mymod:diamond_fragments", "mymod:diamond_fragments", "mymod:diamond_fragments"} recipe = {"mymod:diamond_fragments", "mymod:diamond_fragments", "mymod:diamond_fragments"}
}) })
{% endhighlight %} {% endhighlight %}
When you are crafting the diamond, the three diamond fragments can be anywhere ### Cooking and Fuel
in the grid.\\
Note: You can still use options like the number after the result, as mentioned
earlier.
### Cooking
Recipes with the type "cooking" are not made in the crafting grid, 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. but are cooked in furnaces, or other cooking tools that might be found in mods.
For example, you use a cooking recipe to turn ores into bars.
{% highlight lua %} {% highlight lua %}
minetest.register_craft({ minetest.register_craft({
@ -281,17 +271,16 @@ minetest.register_craft({
}) })
{% endhighlight %} {% endhighlight %}
As you can see from this example, the only real difference in the code The only real difference in the code is that the recipe is just a single item,
is that the recipe is just a single item, compared to being in a table compared to being in a table (between braces).
(between braces). They also have an optional "cooktime" parameter which 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. 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, The recipe above works when the coal block is in the input slot,
with some form of a fuel below it. with some form of a fuel below it.
It creates diamond fragments after 10 seconds! It creates diamond fragments after 10 seconds!
#### Fuel
This type is an accompaniment to the cooking type, as it defines This type is an accompaniment to the cooking type, as it defines
what can be burned in furnaces and other cooking tools from mods. what can be burned in furnaces and other cooking tools from mods.
@ -310,30 +299,73 @@ So, the diamond is good as fuel for 300 seconds!
## Groups ## Groups
Items can be members of many groups and groups can have many members. Items can be members of many groups and groups can have many members.
Groups are usually identified using `group:group_name` Groups are defined using the `groups` property in the definition table,
There are several reasons you use groups. and have an associated value.
Groups can be used in crafting recipes to allow interchangeability
of ingredients. For example, you may use group:wood to allow any wood
item to be used in the recipe.
### Dig types
Let's look at our above ``mymod:diamond`` definition. You'll notice this line:
{% highlight lua %} {% highlight lua %}
groups = {cracky = 3} groups = {cracky = 3, wood = 1}
{% endhighlight %} {% endhighlight %}
Cracky is a digtype. Dig types specify what type of the material the node is There are several reasons you use groups.
physically, and what tools are best to destroy it. Firstly, groups are used to describe properties such as dig types and flammability.
Secondly, groups can be used in a craft recipe instead of an item name to allow
any item in group to be used.
| Group | Description | {% highlight lua %}
|-------------------------|----------------------------------------------------------------------------------------------| minetest.register_craft({
| crumbly | dirt, sand | type = "shapeless",
| cracky | tough but crackable stuff like stone. | output = "mymod:diamond_thing 3",
| snappy | something that can be cut using fine tools; e.g. leaves, smallplants, wire, sheets of metal | recipe = {"group:wood", "mymod:diamond"}
| choppy | something that can be cut using force; e.g. trees, wooden planks | })
| fleshy | Living things like animals and the player. This could imply some blood effects when hitting. | {% endhighlight %}
| explody | Especially prone to explosions |
| oddly_breakable_by_hand | Torches, etc, quick to dig | ## 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 having 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.
{% highlight lua %}
minetest.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},
},
})
{% endhighlight %}
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 chapter.