Use GH-markdown code highlighting instead of Liquid block

This commit is contained in:
rubenwardy 2018-09-19 12:04:51 +01:00
parent c0045d484a
commit 386febe45d
No known key found for this signature in database
GPG Key ID: A1E29D52FF81513C
24 changed files with 344 additions and 344 deletions

View File

@ -67,9 +67,9 @@ Explain why/how these concepts are useful in modding
Paragraphs
{% highlight lua %}
```lua
code
{% endhighlight %}
```
## Parts in

View File

@ -113,7 +113,7 @@ Are you confused? Don't worry, here is an example which puts all of this togethe
default
### init.lua
{% highlight lua %}
```lua
print("This file will be run at load time!")
minetest.register_node("mymod:node", {
@ -128,7 +128,7 @@ minetest.register_node("mymod:node", {
},
groups = {cracky = 1}
})
{% endhighlight %}
```
### mod.conf
name = mymod

View File

@ -30,7 +30,7 @@ A code editor with code highlighting is sufficient for writing scripts in Lua.
Code highlighting gives different colours to different words and characters
depending on what they mean. This allows you to spot mistakes.
{% highlight lua %}
```lua
function ctf.post(team,msg)
if not ctf.team(team) then
return false
@ -44,7 +44,7 @@ function ctf.post(team,msg)
return true
end
{% endhighlight %}
```
For example, keywords in the above snippet are highlighted such as if, then, end, return.
table.insert is a function which comes with Lua by default.
@ -88,13 +88,13 @@ There are three main types of flow:
So, what do statements in Lua look like?
{% highlight lua %}
```lua
local a = 2 -- Set 'a' to 2
local b = 2 -- Set 'b' to 2
local result = a + b -- Set 'result' to a + b, which is 4
a = a + 10
print("Sum is "..result)
{% endhighlight %}
```
Whoa, what happened there?
@ -140,14 +140,14 @@ Not an exhaustive list. Doesn't contain every possible operator.
The most basic selection is the if statement. It looks like this:
{% highlight lua %}
```lua
local random_number = math.random(1, 100) -- Between 1 and 100.
if random_number > 50 then
print("Woohoo!")
else
print("No!")
end
{% endhighlight %}
```
That example generates a random number between 1 and 100. It then prints
"Woohoo!" if that number is bigger than 50, otherwise it prints "No!".
@ -169,24 +169,24 @@ What else can you get apart from '>'?
That doesn't contain every possible operator, and you can combine operators like this:
{% highlight lua %}
```lua
if not A and B then
print("Yay!")
end
{% endhighlight %}
```
Which prints "Yay!" if A is false and B is true.
Logical and arithmetic operators work exactly the same, they both accept inputs
and return a value which can be stored.
{% highlight lua %}
```lua
local A = 5
local is_equal = (A == 5)
if is_equal then
print("Is equal!")
end
{% endhighlight %}
```
## Programming
@ -208,7 +208,7 @@ however, the following websites are quite useful in developing this:
Whether a variable is local or global determines where it can be written to or read to.
A local variable is only accessible from where it is defined. Here are some examples:
{% highlight lua %}
```lua
-- Accessible from within this script file
local one = 1
@ -221,11 +221,11 @@ function myfunc()
local three = one + two
end
end
{% endhighlight %}
```
Whereas global variables can be accessed from anywhere in the script file, and from any other mod.
{% highlight lua %}
```lua
my_global_variable = "blah"
function one()
@ -235,7 +235,7 @@ end
print(my_global_variable) -- Output: "blah"
one()
print(my_global_variable) -- Output: "three"
{% endhighlight %}
```
### Locals should be used as much as possible
@ -243,7 +243,7 @@ print(my_global_variable) -- Output: "three"
Lua is global by default (unlike most other programming languages).
Local variables must be identified as such.
{% highlight lua %}
```lua
function one()
foo = "bar"
end
@ -254,7 +254,7 @@ end
one()
two()
{% endhighlight %}
```
dump() is a function that can turn any variable into a string so the programmer can
see what it is. The foo variable will be printed as "bar", including the quotes
@ -266,7 +266,7 @@ This is sloppy coding, and Minetest will in fact warn about this:
To correct this, use "local":
{% highlight lua %}
```lua
function one()
local foo = "bar"
end
@ -277,7 +277,7 @@ end
one()
two()
{% endhighlight %}
```
Remember that nil means **not initialised**.
The variable hasn't been assigned a value yet,
@ -286,15 +286,15 @@ doesn't exist, or has been uninitialised (ie: set to nil).
The same goes for functions. Functions are variables of a special type, and
should be made local, as other mods could have functions of the same name.
{% highlight lua %}
```lua
local function foo(bar)
return bar * 2
end
{% endhighlight %}
```
API tables should be used to allow other mods to call the functions, like so:
{% highlight lua %}
```lua
mymod = {}
function mymod.foo(bar)
@ -303,27 +303,27 @@ end
-- In another mod, or script:
mymod.foo("foobar")
{% endhighlight %}
```
## Including other Lua Scripts
The recommended way to include other Lua scripts in a mod is to use *dofile*.
{% highlight lua %}
```lua
dofile(minetest.get_modpath("modname") .. "/script.lua")
{% endhighlight %}
```
"local" variables declared outside of any functions in a script file will be local to that script.
A script can return a value, which is useful for sharing private locals:
{% highlight lua %}
```lua
-- script.lua
return "Hello world!"
-- init.lua
local ret = dofile(minetest.get_modpath("modname") .. "/script.lua")
print(ret) -- Hello world!
{% endhighlight %}
```
Later chapters will discuss how to split up the code of a mod in a lot of detail.
However, the simplistic approach for now is to have different files for different

View File

@ -69,9 +69,9 @@ Press i in game to see your player inventory.
Use a player's name to get their inventory:
{% highlight lua %}
```lua
local inv = minetest.get_inventory({type="player", name="celeron55"})
{% endhighlight %}
```
### Node Inventories
@ -80,9 +80,9 @@ The node must be loaded, because it is stored in [node metadata](node_metadata.h
Use its position to get a node inventory:
{% highlight lua %}
```lua
local inv = minetest.get_inventory({type="node", pos={x=, y=, z=}})
{% endhighlight %}
```
### Detached Inventories
@ -93,15 +93,15 @@ A detached inventory would also allow multiple chests to share the same inventor
Use the inventory name to get a detached inventory:
{% highlight lua %}
```lua
local inv = minetest.get_inventory({type="detached", name="inventory_name"})
{% endhighlight %}
```
You can create your own detached inventories:
{% highlight lua %}
```lua
minetest.create_detached_inventory("inventory_name", callbacks)
{% endhighlight %}
```
This creates a detached inventory or, if the inventory already exists, it is cleared.
You can also supply a [table of callbacks]({{ page.root }}/lua_api.html#detached-inventory-callbacks).
@ -112,9 +112,9 @@ You can also supply a [table of callbacks]({{ page.root }}/lua_api.html#detached
You can check where an inventory is located:
{% highlight lua %}
```lua
local location = inv:get_location()
{% endhighlight %}
```
This will return a table like the one passed to `minetest.get_inventory()`.
@ -125,7 +125,7 @@ If the location is unknown, `{type="undefined"}` is returned.
Inventory lists have a size, for example `main` has size of 32 slots by default.
They also have a width, which is used to divide them into a grid.
{% highlight lua %}
```lua
if inv:set_size("main", 32) then
inv:set_width("main", 8)
print("size: " .. inv.get_size("main"))
@ -133,7 +133,7 @@ if inv:set_size("main", 32) then
else
print("Error!")
end
{% endhighlight %}
```
<!--The width and height of an inventory in a [formspec](formspecs.html) is
determined by the formspec element, not by the inventory. By that I mean
@ -143,23 +143,23 @@ a list doesn't have a width or height, only the maximum number of stacks/slots.-
You can use `list_is_empty` to check if a list is empty:
{% highlight lua %}
```lua
if inv:is_empty("main") then
print("The list is empty!")
end
{% endhighlight %}
```
### Lua Tables
You can convert an inventory to a Lua table:
{% highlight lua %}
```lua
local lists = inv:get_lists()
{% endhighlight %}
```
The table will be in this form:
{% highlight lua %}
```lua
{
list_one = {
ItemStack,
@ -176,13 +176,13 @@ The table will be in this form:
-- inv:get_size("list_two") elements
}
}
{% endhighlight %}
```
You can then set the inventory:
{% highlight lua %}
```lua
inv:set_lists(lists)
{% endhighlight %}
```
Please note that the sizes of lists will not change.
@ -190,13 +190,13 @@ Please note that the sizes of lists will not change.
You can do the above for individual lists:
{% highlight lua %}
```lua
local list = inv:get_list("list_one")
{% endhighlight %}
```
It will be in this form:
{% highlight lua %}
```lua
{
ItemStack,
ItemStack,
@ -204,13 +204,13 @@ It will be in this form:
ItemStack,
-- inv:get_size("list_one") elements
}
{% endhighlight %}
```
You can then set the list:
{% highlight lua %}
```lua
inv:set_list("list_one", list)
{% endhighlight %}
```
Please note that the sizes of lists will not change.
@ -220,42 +220,42 @@ Please note that the sizes of lists will not change.
To add items to a list named `"main"`:
{% highlight lua %}
```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
{% endhighlight %}
```
### Checking for Room
To check whether a list has room for items:
{% highlight lua %}
```lua
if not inv:room_for_item("main", stack) then
print("Not enough room!")
end
{% endhighlight %}
```
### Taking Items
To remove items from a list:
{% highlight lua %}
```lua
local taken = inv:remove_item("main", stack)
print("Took " .. taken:get_count())
{% endhighlight %}
```
### Checking Inventory Contents
To check whether an inventory contains a specific quantity of an item:
{% highlight lua %}
```lua
if not inv:contains_item(listname, stack) then
print("Item not in inventory!")
end
{% endhighlight %}
```
This works if the item count is split up over multiple stacks.
For example checking for "default:stone 200" will work if there
@ -265,7 +265,7 @@ are stacks of 99 + 95 + 6.
You can manipulate individual stacks:
{% highlight lua %}
```lua
local stack = inv:get_stack(listname, 0)
inv:set_stack(listname, 0, stack)
{% endhighlight %}
```

View File

@ -25,31 +25,31 @@ It's basically just an item type with a count of items in the stack.
You can create a stack like so:
{% highlight lua %}
```lua
local items = ItemStack("default:dirt")
local items = ItemStack("default:stone 99")
{% endhighlight %}
```
You could alternatively create a blank ItemStack and fill it using methods:
{% highlight lua %}
```lua
local items = ItemStack()
if items:set_name("default:dirt") then
items:set_count(99)
else
print("An error occured!")
end
{% endhighlight %}
```
And you can copy stacks like this:
{% highlight lua %}
```lua
local items2 = ItemStack(items)
{% endhighlight %}
```
## Name and Count
{% highlight lua %}
```lua
local items = ItemStack("default:stone")
print(items:get_name()) -- default:stone
print(items:get_count()) -- 1
@ -64,7 +64,7 @@ if items:set_name("default:dirt") then
else
error("This shouldn't happen")
end
{% endhighlight %}
```
## Adding and Taking Items
@ -73,26 +73,26 @@ end
Use `add_item` to add items to an ItemStack.
An ItemStack of the items that could not be added is returned.
{% highlight lua %}
```lua
local items = ItemStack("default:stone 50")
local to_add = ItemStack("default:stone 100")
local leftovers = items:add_item(to_add)
print("Could not add" .. leftovers:get_count() .. " of the items.")
-- ^ will be 51
{% endhighlight %}
```
## Taking
The following code takes **up to** 100.
If there aren't enough items in the stack, it will take as much as it can.
{% highlight lua %}
```lua
local taken = items:take_item(100)
-- taken is the ItemStack taken from the main ItemStack
print("Took " .. taken:get_count() .. " items")
{% endhighlight %}
```
## Wear
@ -101,36 +101,36 @@ the more wear.
You use `add_wear()`, `get_wear()` and `set_wear(wear)`.
{% highlight lua %}
```lua
local items = ItemStack("default:dirt")
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.
items:add_wear(65535 / (max_uses - 1))
{% endhighlight %}
```
When digging a node, the amount of wear a tool gets may depends on the node
being dug. So max_uses varies depending on what is being dug.
## Lua Tables
{% highlight lua %}
```lua
local data = items:to_table()
local items2 = ItemStack(data)
{% endhighlight %}
```
## Metadata
ItemStacks can have metadata, and use the same API as [Node Metadata](node_metadata.html).
{% highlight lua %}
```lua
local meta = items:get_meta()
meta:set_string("foo", meta:get_string("foo") .. " ha!")
print(dump(meta:get_string("foo")))
-- if ran multiple times, would give " ha! ha! ha!"
{% endhighlight %}
```
## More Methods

View File

@ -54,7 +54,7 @@ Nodes that use this will be cubes with textures for each side, simple-as.\\
Here is the example from the [Nodes, Items and Crafting](nodes_items_crafting.html#registering-a-basic-node) chapter.
Notice how you don't need to declare the drawtype.
{% highlight lua %}
```lua
minetest.register_node("mymod:diamond", {
description = "Alien Diamond",
tiles = {
@ -69,14 +69,14 @@ minetest.register_node("mymod:diamond", {
groups = {cracky = 3},
drop = "mymod:diamond_fragments"
})
{% endhighlight %}
```
## Airlike
These nodes are see through and thus have no textures.
{% highlight lua %}
```lua
minetest.register_node("myair:air", {
description = "MyAir (you hacker you!)",
drawtype = "airlike",
@ -97,7 +97,7 @@ minetest.register_node("myair:air", {
drop = "",
groups = {not_in_creative_inventory=1}
})
{% endhighlight %}
```
## Liquid
@ -112,7 +112,7 @@ These nodes are complete liquid nodes, the liquid flows outwards from position
using the flowing liquid drawtype.
For each liquid node you should also have a flowing liquid node.
{% highlight lua %}
```lua
-- Some properties have been removed as they are beyond
-- the scope of this chapter.
minetest.register_node("default:water_source", {
@ -175,7 +175,7 @@ minetest.register_node("default:water_source", {
post_effect_color = {a=64, r=100, g=100, b=200},
-- ^ color of screen when the player is submerged
})
{% endhighlight %}
```
### FlowingLiquid
@ -201,7 +201,7 @@ edges are hidden, like this:
</figcaption>
</figure>
{% highlight lua %}
```lua
minetest.register_node("default:obsidian_glass", {
description = "Obsidian Glass",
drawtype = "glasslike",
@ -212,7 +212,7 @@ minetest.register_node("default:obsidian_glass", {
sounds = default.node_sound_glass_defaults(),
groups = {cracky=3,oddly_breakable_by_hand=3},
})
{% endhighlight %}
```
## Glasslike_Framed
@ -226,7 +226,7 @@ than individual nodes, like the following:
</figcaption>
</figure>
{% highlight lua %}
```lua
minetest.register_node("default:glass", {
description = "Glass",
drawtype = "glasslike_framed",
@ -241,7 +241,7 @@ minetest.register_node("default:glass", {
groups = {cracky = 3, oddly_breakable_by_hand = 3},
sounds = default.node_sound_glass_defaults()
})
{% endhighlight %}
```
### Glasslike_Framed_Optional
@ -260,13 +260,13 @@ Allfaces are nodes which show all of their faces, even if they're against
another node. This is mainly used by leaves as you don't want a gaping space when
looking through the transparent holes, but instead a nice leaves effect.
{% highlight lua %}
```lua
minetest.register_node("default:leaves", {
description = "Leaves",
drawtype = "allfaces_optional",
tiles = {"default_leaves.png"}
})
{% endhighlight %}
```
### Allfaces_Optional
@ -281,7 +281,7 @@ depending on whether they are placed against a wall, on the floor, or on the cei
TorchLike nodes are not restricted to torches, you could use them for switches or other
items which need to have different textures depending on where they are placed.
{% highlight lua %}
```lua
minetest.register_node("foobar:torch", {
description = "Foobar Torch",
drawtype = "torchlike",
@ -306,7 +306,7 @@ minetest.register_node("foobar:torch", {
wall_side = {-0.5, -0.3, -0.1, -0.5+0.3, 0.3, 0.1},
}
})
{% endhighlight %}
```
## Nodebox
@ -320,7 +320,7 @@ minetest.register_node("foobar:torch", {
Nodeboxes allow you to create a node which is not cubic, but is instead made out
of as many cuboids as you like.
{% highlight lua %}
```lua
minetest.register_node("stairs:stair_stone", {
drawtype = "nodebox",
paramtype = "light",
@ -332,14 +332,14 @@ minetest.register_node("stairs:stair_stone", {
},
}
})
{% endhighlight %}
```
The most important part is the nodebox table:
{% highlight lua %}
```lua
{-0.5, -0.5, -0.5, 0.5, 0, 0.5},
{-0.5, 0, 0, 0.5, 0.5, 0.5}
{% endhighlight %}
```
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
@ -356,7 +356,7 @@ create node boxes by dragging the edges, it is more visual than doing it by hand
Sometimes you want different nodeboxes for when it is placed on the floor, wall, or ceiling like with torches.
{% highlight lua %}
```lua
minetest.register_node("default:sign_wall", {
drawtype = "nodebox",
node_box = {
@ -378,7 +378,7 @@ minetest.register_node("default:sign_wall", {
}
},
})
{% endhighlight %}
```
## Mesh
@ -392,7 +392,7 @@ invisible but still rendered.
You can register a mesh node as so:
{% highlight lua %}
```lua
minetest.register_node("mymod:meshy", {
drawtype = "mesh",
@ -404,7 +404,7 @@ minetest.register_node("mymod:meshy", {
-- Path to the mesh
mesh = "mymod_meshy.b3d",
})
{% endhighlight %}
```
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
@ -420,7 +420,7 @@ 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.
{% highlight lua %}
```lua
minetest.register_node("default:ladder_wood", {
drawtype = "signlike",
@ -433,7 +433,7 @@ minetest.register_node("default:ladder_wood", {
type = "wallmounted",
},
})
{% endhighlight %}
```
## Plantlike
@ -446,7 +446,7 @@ minetest.register_node("default:ladder_wood", {
Plantlike nodes draw their tiles in an X like pattern.
{% highlight lua %}
```lua
minetest.register_node("default:papyrus", {
drawtype = "plantlike",
@ -458,7 +458,7 @@ minetest.register_node("default:papyrus", {
fixed = {-6 / 16, -0.5, -6 / 16, 6 / 16, 0.5, 6 / 16},
},
})
{% endhighlight %}
```
## Firelike
@ -472,11 +472,11 @@ and ceilings.
</figcaption>
</figure>
{% highlight lua %}
```lua
minetest.register_node("mymod:clingere", {
drawtype = "firelike",
-- Only one texture used
tiles = { "mymod:clinger" },
})
{% endhighlight %}
```

View File

@ -46,12 +46,12 @@ item, as the distinction between them is rather artificial.
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 %}
```lua
minetest.register_craftitem("modname:itemname", {
description = "My Special Item",
inventory_image = "modname_itemname.png"
})
{% endhighlight %}
```
### Item Names and Aliases
@ -80,17 +80,17 @@ Registering an alias is pretty simple.
A good way to remember is `from → to` where *from* is the alias and *to*
is the target.
{% highlight lua %}
```lua
minetest.register_alias("dirt", "default:dirt")
{% endhighlight %}
```
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 %}
```lua
itemname = minetest.registered_aliases[itemname] or itemname
{% endhighlight %}
```
### Textures
@ -108,14 +108,14 @@ resulting in lowered performance.
## Registering a basic node
{% highlight lua %}
```lua
minetest.register_node("mymod:diamond", {
description = "Alien Diamond",
tiles = {"mymod_diamond.png"},
is_ground_content = true,
groups = {cracky=3, stone=1}
})
{% endhighlight %}
```
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.
@ -126,7 +126,7 @@ To give a different texture per-side, supply the names of 6 textures in this ord
Remember: Just like with most 3D graphics, +Y is upwards in Minetest.
{% highlight lua %}
```lua
minetest.register_node("mymod:diamond", {
description = "Alien Diamond",
tiles = {
@ -142,7 +142,7 @@ minetest.register_node("mymod:diamond", {
drop = "mymod:diamond_fragments"
-- ^ Rather than dropping diamond, drop mymod:diamond_fragments
})
{% endhighlight %}
```
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.
@ -160,13 +160,13 @@ 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 %}
```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.
@ -178,7 +178,7 @@ 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 %}
```lua
minetest.register_craftitem("mymod:mudpie", {
description = "Alien Mud Pie",
inventory_image = "myfood_mudpie.png",
@ -186,7 +186,7 @@ minetest.register_craftitem("mymod:mudpie", {
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.
@ -212,7 +212,7 @@ Shaped recipes are when the ingredients need to be in the right shape or
pattern to work. In the below example, the fragments need to be in a
chair-like pattern for the craft to work.
{% highlight lua %}
```lua
minetest.register_craft({
type = "shaped",
output = "mymod:diamond_chair 99",
@ -222,7 +222,7 @@ minetest.register_craft({
{"mymod:diamond_fragments", "mymod:diamond_fragments", ""}
}
})
{% endhighlight %}
```
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
@ -230,7 +230,7 @@ this won't work.
If this empty column shouldn't be required, then the empty strings can be left
out like so:
{% highlight lua %}
```lua
minetest.register_craft({
output = "mymod:diamond_chair 99",
recipe = {
@ -239,7 +239,7 @@ minetest.register_craft({
{"mymod:diamond_fragments", "mymod:diamond_fragments"}
}
})
{% endhighlight %}
```
The type field isn't actually needed for shapeless crafts, as shapeless is the
default craft type.
@ -249,27 +249,27 @@ default craft type.
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.
{% highlight lua %}
```lua
minetest.register_craft({
type = "shapeless",
output = "mymod:diamond 3",
recipe = {"mymod:diamond_fragments", "mymod:diamond_fragments", "mymod:diamond_fragments"}
})
{% endhighlight %}
```
### 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.
{% highlight lua %}
```lua
minetest.register_craft({
type = "cooking",
output = "mymod:diamond_fragments",
recipe = "default:coalblock",
cooktime = 10,
})
{% endhighlight %}
```
The only real difference in the code is that the recipe is just a single item,
compared to being in a table (between braces).
@ -284,13 +284,13 @@ 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.
{% highlight lua %}
```lua
minetest.register_craft({
type = "fuel",
recipe = "mymod:diamond",
burntime = 300,
})
{% endhighlight %}
```
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.
@ -302,22 +302,22 @@ 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.
{% highlight lua %}
```lua
groups = {cracky = 3, wood = 1}
{% endhighlight %}
```
There are several reasons you use groups.
Firstly, groups are used to describe properties such as dig types and flammability.
Secondly, groups can be used in a craft recipe instead of an item name to allow
any item in group to be used.
{% highlight lua %}
```lua
minetest.register_craft({
type = "shapeless",
output = "mymod:diamond_thing 3",
recipe = {"group:wood", "mymod:diamond"}
})
{% endhighlight %}
```
## Tools, Capabilities and Dig Types
@ -351,7 +351,7 @@ 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 %}
```lua
minetest.register_tool("mymod:tool", {
description = "My Tool",
inventory_image = "mymod_tool.png",
@ -364,7 +364,7 @@ minetest.register_tool("mymod:tool", {
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

View File

@ -26,7 +26,7 @@ has a chance to appear near water.
To create it, you first need to register the grass, then
write an ABM.
{% highlight lua %}
```lua
minetest.register_node("aliens:grass", {
description = "Alien Grass",
light_source = 3, -- The node radiates light. Values can be from 1 to 15
@ -44,7 +44,7 @@ minetest.register_abm({
minetest.set_node({x = pos.x, y = pos.y + 1, z = pos.z}, {name = "aliens:grass"})
end
})
{% endhighlight %}
```
This ABM runs every ten seconds. There is a 1 in 5 chance of the ABM running on each
node that has the correct name and the correct neighbours. If the ABM runs on a

View File

@ -52,10 +52,10 @@ be a cube. -->
You can read from the map once you have a position:
{% highlight lua %}
```lua
local node = minetest.get_node({ x = 1, y = 3, z = 4 })
print(dump(node)) --> { name=.., param1=.., param2=.. }
{% endhighlight %}
```
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:
@ -82,30 +82,30 @@ 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.
{% highlight lua %}
```lua
local grow_speed = 1
local node_pos = minetest.find_node_near(pos, 5, { "default:mese" })
if node_pos then
minetest.chat_send_all("Node found at: " .. dump(node_pos))
grow_speed = 2
end
{% endhighlight %}
```
Let's say, for example, that the growth rate increases the more mese there is
nearby. You should then use a function which can find multiple nodes in area:
{% highlight lua %}
```lua
local pos1 = vector.subtract(pos, { x = 5, y = 5, z = 5 })
local pos2 = vector.add(pos, { x = 5, y = 5, z = 5 })
local pos_list = minetest.find_nodes_in_area(pos1, pos2, { "default:mese" })
local grow_speed = 1 + #pos_list
{% endhighlight %}
```
The above code doesn't quite do what we want, as it checks based on area, whereas
`find_node_near` checks based on range. In order to fix this we will,
unfortunately, need to manually check the range ourselves.
{% highlight lua %}
```lua
local pos1 = vector.subtract(pos, { x = 5, y = 5, z = 5 })
local pos2 = vector.add(pos, { x = 5, y = 5, z = 5 })
local pos_list = minetest.find_nodes_in_area(pos1, pos2, { "default:mese" })
@ -116,7 +116,7 @@ for i=1, #pos_list do
grow_speed = grow_speed + 1
end
end
{% endhighlight %}
```
Now your code will correctly increase `grow_speed` based on mese nodes in range.
Note how we compared the squared distance from the position, rather than square
@ -135,12 +135,12 @@ You can use `set_node` to write to the map. Each call to set_node will cause
lighting to be recalculated, which means that set_node is fairly slow for large
numbers of nodes.
{% highlight lua %}
```lua
minetest.set_node({ x = 1, y = 3, z = 4 }, { name = "default:mese" })
local node = minetest.get_node({ x = 1, y = 3, z = 4 })
print(node.name) --> default:mese
{% endhighlight %}
```
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
@ -150,9 +150,9 @@ two.
You can set a node without deleting metadata or the inventory like so:
{% highlight lua %}
```lua
minetest.swap_node({ x = 1, y = 3, z = 4 }, { name = "default:mese" })
{% endhighlight %}
```
### Removing Nodes
@ -160,10 +160,10 @@ 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:
{% highlight lua %}
```lua
minetest.remove_node(pos)
minetest.set_node(pos, { name = "air" })
{% endhighlight %}
```
In fact, remove_node will call set_node with name being air.
@ -173,7 +173,7 @@ You can use `minetest.emerge_area` to load map blocks. Emerge area is asynchrono
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.
{% highlight lua %}
```lua
-- Load a 20x20x20 area
local halfsize = { x = 10, y = 10, z = 10 }
local pos1 = vector.subtract(pos, halfsize)
@ -181,12 +181,12 @@ local pos2 = vector.add (pos, halfsize)
local context = {} -- persist data between callback calls
minetest.emerge_area(pos1, pos2, emerge_callback, context)
{% endhighlight %}
```
Minetest will call `emerge_callback` whenever it loads a block, with some
progress information.
{% highlight lua %}
```lua
local function emerge_callback(pos, action, num_calls_remaining, context)
-- On first call, record number of blocks
if not context.total_blocks then
@ -206,7 +206,7 @@ local function emerge_callback(pos, action, num_calls_remaining, context)
context.loaded_blocks, context.total_blocks, perc)
end
end
{% endhighlight %}
```
This is not the only way of loading blocks; Using a LVM will also cause the
encompassed blocks to be loaded synchronously.
@ -215,14 +215,14 @@ encompassed blocks to be loaded synchronously.
You can use delete_blocks to delete a range of map blocks:
{% highlight lua %}
```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)
minetest.delete_area(pos1, pos2)
{% endhighlight %}
```
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

View File

@ -36,10 +36,10 @@ 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 like so:
{% highlight lua %}
```lua
local vm = minetest.get_voxel_manip()
local emin, emax = vm:read_from_map(pos1, pos2)
{% endhighlight %}
```
An LVM may not read exactly the area you tell it to, for performance reasons.
Instead, it may read a larger area. The larger area is given by `emin` and `emax`,
@ -53,9 +53,9 @@ To read the types of nodes at particular positions, you'll need to use `get_data
`get_data()` returns a flat array where each entry represents the type of a
particular node.
{% highlight lua %}
```lua
local data = vm:get_data()
{% endhighlight %}
```
You can get param2 and lighting data using the methods `get_light_data()` and `get_param2_data()`.
@ -63,7 +63,7 @@ You'll need to use `emin` and `emax` to work out where a node is in the flat arr
given by the above methods. There's a helper class called `VoxelArea` which handles
the calculation for you:
{% highlight lua %}
```lua
local a = VoxelArea:new{
MinEdge = emin,
MaxEdge = emax
@ -74,25 +74,25 @@ local idx = a:index(x, y, z)
-- Read node
print(data[idx])
{% endhighlight %}
```
If you run that, you'll notice that `data[vi]` is an integer. This is because
the engine doesn't store nodes using their name string, as string comparision
is slow. Instead, the engine uses a content ID. You can find out the content
ID for a particular type of node like so:
{% highlight lua %}
```lua
local c_stone = minetest.get_content_id("default:stone")
{% endhighlight %}
```
and then you can check whether a node is stone like so:
{% highlight lua %}
```lua
local idx = a:index(x, y, z)
if data[idx] == c_stone then
print("is stone!")
end
{% endhighlight %}
```
It is recommended that you find out and store the content IDs of nodes types
using load time, as the IDs of a node type will never change. Make sure to store
@ -101,7 +101,7 @@ the IDs in a local for performance reasons.
Nodes in an LVM data are stored in reverse co-ordinate order, so you should
always iterate in the order of `z, y, x` like so:
{% highlight lua %}
```lua
for z = min.z, max.z do
for y = min.y, max.y do
for x = min.x, max.x do
@ -113,7 +113,7 @@ for z = min.z, max.z do
end
end
end
{% endhighlight %}
```
The reason for this touches computer architecture. Reading from RAM is rather
costly, so CPUs have multiple levels of caching. If the data a process requests
@ -128,7 +128,7 @@ another, and avoid *memory thrashing*.
First you need to set the new content ID in the data array:
{% highlight lua %}
```lua
for z = min.z, max.z do
for y = min.y, max.y do
for x = min.x, max.x do
@ -139,15 +139,15 @@ for z = min.z, max.z do
end
end
end
{% endhighlight %}
```
When you finished setting nodes in the LVM, you then need to upload the data
array to the engine:
{% highlight lua %}
```lua
vm:set_data(data)
vm:write_to_map(true)
{% endhighlight %}
```
For setting lighting and param2 data, there are the appropriately named
`set_light_data()` and `set_param2_data()` methods.
@ -158,7 +158,7 @@ date using `minetest.fix_light`.
## Example
{% highlight lua %}
```lua
-- Get content IDs during load time, and store into a local
local c_dirt = minetest.get_content_id("default:dirt")
local c_grass = minetest.get_content_id("default:dirt_with_grass")
@ -189,7 +189,7 @@ local function grass_to_dirt(pos1, pos2)
vm:set_data(data)
vm:write_to_map(true)
end
{% endhighlight %}
```
## Your Turn

View File

@ -44,16 +44,16 @@ Metadata is stored in a key value relationship. For example:
If you know the position of a node, you can retrieve its metadata:
{% highlight lua %}
```lua
local meta = minetest.get_meta(pos)
-- where pos = { x = 1, y = 5, z = 7 }
{% endhighlight %}
```
## Reading Metadata
After retrieving the metadata, you can read its values:
{% highlight lua %}
```lua
local value = meta:get_string("key")
if value then
@ -63,7 +63,7 @@ else
-- metadata of key "key" does not exist
print(value)
end
{% endhighlight %}
```
The functions available include:
@ -74,7 +74,7 @@ The functions available include:
To get a Boolean, you should use `get_string` and `minetest.is_yes`:
{% highlight lua %}
```lua
local value = minetest.is_yes(meta:get_string("key"))
if value then
@ -82,18 +82,18 @@ if value then
else
print("is no")
end
{% endhighlight %}
```
## Setting Metadata
You can set node metadata. For example:
{% highlight lua %}
```lua
local value = "one"
meta:set_string("key", value)
meta:set_string("foo", "bar")
{% endhighlight %}
```
This can be done using the following functions:
@ -105,11 +105,11 @@ This can be done using the following functions:
You can convert to and from Lua tables using `to_table` and `from_table`:
{% highlight lua %}
```lua
local tmp = meta:to_table()
tmp.foo = "bar"
meta:from_table(tmp)
{% endhighlight %}
```
## Infotext
@ -117,9 +117,9 @@ The Minetest engine reads the field `infotext` to make text
appear on mouse-over. This is used by furnaces to show progress and by signs
to show their text. For example:
{% highlight lua %}
```lua
meta:set_string("infotext", "Here is some text that will appear on mouse-over!")
{% endhighlight %}
```
## Your Turn

View File

@ -38,19 +38,19 @@ Don't be fooled though, all entities are Lua entities.
`get_pos` and `set_pos` exist to allow you to get and set an entity's position.
{% highlight lua %}
```lua
local object = minetest.get_player_by_name("bob")
local pos = object:get_pos()
object:set_pos({ x = pos.x, y = pos.y + 1, z = pos.z })
{% endhighlight %}
```
`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.
{% highlight lua %}
```lua
object:move_to({ x = pos.x, y = pos.y + 1, z = pos.z })
{% endhighlight %}
```
An important thing to think about when dealing with entities is network latency.
In an ideal world, messages about entity movements would arrive immediately,
@ -69,14 +69,14 @@ 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.
{% highlight lua %}
```lua
object:set_properties({
visual = "mesh",
mesh = "character.b3d",
textures = {"character_texture.png"},
visual_size = {x=1, y=1},
})
{% endhighlight %}
```
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
@ -93,7 +93,7 @@ joined players.
An Entity has a type table much like an item does.
This table can contain callback methods, default object properties, and custom elements.
{% highlight lua %}
```lua
local MyEntity = {
initial_properties = {
hp_max = 1,
@ -113,7 +113,7 @@ local MyEntity = {
function MyEntity:set_message(msg)
self.message = msg
end
{% endhighlight %}
```
When an entity is emerged, a table is created for it by copying everything from
its type table.
@ -121,16 +121,16 @@ This table can be used to store variables for that particular entity.
Both an ObjectRef and an entity table provide ways to get the counterpart:
{% highlight lua %}
```lua
local entity = object:get_luaentity()
local object = entity.object
print("entity is at " .. minetest.pos_to_string(object:get_pos()))
{% endhighlight %}
```
There are a number of available callbacks for use with entities.
A complete list can be found in [lua_api.txt]({{ page.root }}/lua_api.html#registered-entities)
{% highlight lua %}
```lua
function MyEntity:on_step(dtime)
local pos = self.object:get_pos()
@ -151,7 +151,7 @@ end
function MyEntity:on_punch(hitter)
minetest.chat_send_player(hitter:get_player_name(), self.message)
end
{% endhighlight %}
```
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.
@ -161,7 +161,7 @@ how to save things.
Staticdata is a string which contains all of the custom information that
needs to stored.
{% highlight lua %}
```lua
function MyEntity:get_staticdata()
return minetest.write_json({
message = self.message,
@ -174,7 +174,7 @@ function MyEntity:on_activate(staticdata, dtime_s)
self:set_message(data.message)
end
end
{% endhighlight %}
```
Minetest may call `get_staticdata()` as many times as it once and at any time.
This is because Minetest doesn't wait for a MapBlock to become inactive to save
@ -187,9 +187,9 @@ This means that staticdata could be empty.
Finally, you need to register the type table using the aptly named `register_entity`.
{% highlight lua %}
```lua
minetest.register_entity("9_entities:myentity", MyEntity)
{% endhighlight %}
```
## Attachments
@ -198,9 +198,9 @@ 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.
{% highlight lua %}
```lua
child:set_attach(parent, bone, position, rotation)
{% endhighlight %}
```
An Object's `get_pos()` will always return the global position of the object, no
matter whether it is attached or not.

View File

@ -39,9 +39,9 @@ sending messages, intercepting messages and registering chat commands.
To send a message to every player in the game, call the chat_send_all function.
{% highlight lua %}
```lua
minetest.chat_send_all("This is a chat message to all players")
{% endhighlight %}
```
Here is an example of how this appears in-game:
@ -55,9 +55,9 @@ The message appears on a separate line to distinguish it from in-game player cha
To send a message to a specific player, call the chat_send_player function:
{% highlight lua %}
```lua
minetest.chat_send_player("player1", "This is a chat message for player1")
{% endhighlight %}
```
This message displays in the same manner as messages to all players, but is
only visible to the named player, in this case player1.
@ -66,7 +66,7 @@ only visible to the named player, in this case player1.
To register a chat command, for example /foo, use register_chatcommand:
{% highlight lua %}
```lua
minetest.register_chatcommand("foo", {
privs = {
interact = true
@ -75,26 +75,26 @@ minetest.register_chatcommand("foo", {
return true, "You said " .. param .. "!"
end
})
{% endhighlight %}
```
Calling /foo bar will display `You said bar!` in the chat console.
You can restrict which players are able to run commands:
{% highlight lua %}
```lua
privs = {
interact = true
},
{% endhighlight %}
```
This means only players with the `interact` [privilege](privileges.html) can run the
command. Other players will see an error message informing them of which
privilege they're missing. If the player has the necessary privileges, the command
will run and the message will be sent:
{% highlight lua %}
```lua
return true, "You said " .. param .. "!"
{% endhighlight %}
```
This returns two values, a Boolean which shows the command succeeded
and the chat message to send to the player.
@ -113,9 +113,9 @@ It is often required to make complex chat commands, such as:
This is usually done using [Lua patterns](https://www.lua.org/pil/20.2.html).
Patterns are a way of extracting stuff from text using rules.
{% highlight lua %}
```lua
local to, msg = string.match(param, "^([%a%d_-]+) (*+)$")
{% endhighlight %}
```
The above implements `/msg <to> <message>`. Let's go through left to right:
@ -147,12 +147,12 @@ to make complex chat commands without Patterns called
To intercept a message, use register_on_chat_message:
{% highlight lua %}
```lua
minetest.register_on_chat_message(function(name, message)
print(name .. " said " .. message)
return false
end)
{% endhighlight %}
```
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
@ -163,7 +163,7 @@ work the same.
You should make sure you take into account that it may be a chat command,
or the user may not have `shout`.
{% highlight lua %}
```lua
minetest.register_on_chat_message(function(name, message)
if message:sub(1, 1) == "/" then
print(name .. " ran chat command")
@ -175,4 +175,4 @@ minetest.register_on_chat_message(function(name, message)
return false
end)
{% endhighlight %}
```

View File

@ -26,14 +26,14 @@ modders tend to use the method outlined in the
Traditionally mods implemented these complex commands using Lua patterns.
{% highlight lua %}
```lua
local name = string.match(param, "^join ([%a%d_-]+)")
{% endhighlight %}
```
I however find Lua patterns annoying to write and unreadable.
Because of this, I created a library to do this for you.
{% highlight lua %}
```lua
ChatCmdBuilder.new("sethp", function(cmd)
cmd:sub(":target :hp:int", function(name, target, hp)
local player = minetest.get_player_by_name(target)
@ -51,7 +51,7 @@ end, {
-- ^ probably better to register a custom priv
}
})
{% endhighlight %}
```
`ChatCmdBuilder.new(name, setup_func, def)` creates a new chat command called
`name`. It then calls the function passed to it (`setup_func`), which then creates
@ -99,11 +99,11 @@ In `:name :hp:int`, there are two variables there:
The first argument is the caller's name. The variables are then passed to the
function in order.
{% highlight lua %}
```lua
cmd:sub(":target :hp:int", function(name, target, hp)
-- subcommand function
end)
{% endhighlight %}
```
## Installing ChatCmdBuilder
@ -125,7 +125,7 @@ Here is an example that creates a chat command that allows us to do this:
* `/admin log <username>` - show report log
* `/admin log <username> <message>` - log to report log
{% highlight lua %}
```lua
local admin_log
local function load()
admin_log = {}
@ -179,4 +179,4 @@ end, {
ban = true
}
})
{% endhighlight %}
```

View File

@ -110,7 +110,7 @@ This example shows a formspec to a player when they use the /formspec command.
</figcaption>
</figure>
{% highlight lua %}
```lua
-- Show form when the /formspec command is used.
minetest.register_chatcommand("formspec", {
func = function(name, param)
@ -121,20 +121,20 @@ minetest.register_chatcommand("formspec", {
"button_exit[1,2;2,1;exit;Save]")
end
})
{% endhighlight %}
```
Note: the .. is used to join two strings together. The following two lines are equivalent:
{% highlight lua %}
```lua
"foobar"
"foo" .. "bar"
{% endhighlight %}
```
## Callbacks
It's possible to expand the previous example with a callback:
{% highlight lua %}
```lua
-- Show form when the /formspec command is used.
minetest.register_chatcommand("formspec", {
func = function(name, param)
@ -161,7 +161,7 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
-- from receiving this submission.
return true
end)
{% endhighlight %}
```
The function given in minetest.register_on_player_receive_fields is called
every time a user submits a form. Most callbacks will check the formname given
@ -185,14 +185,14 @@ Some elements can submit the form without the user clicking a button,
such as a check box. You can detect these cases by looking
for a clicked button.
{% highlight lua %}
```lua
-- An example of what fields could contain,
-- using the above code
{
name = "Foo Bar",
exit = true
}
{% endhighlight %}
```
## Contexts
@ -202,7 +202,7 @@ what a chat command was called with, or what the dialog is about.
For example, you might make a form to handle land protection information:
{% highlight lua %}
```lua
--
-- Step 1) set context when player requests the formspec
--
@ -256,7 +256,7 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
"Something went wrong, try again.")
end
end)
{% endhighlight %}
```
## Node Meta Formspecs
@ -265,7 +265,7 @@ add formspecs to a [node's meta data](node_metadata.html). For example,
this is used with chests to allow for faster opening times -
you don't need to wait for the server to send the player the chest formspec.
{% highlight lua %}
```lua
minetest.register_node("mymod:rightclick", {
description = "Rightclick me!",
tiles = {"mymod_rightclick.png"},
@ -286,7 +286,7 @@ minetest.register_node("mymod:rightclick", {
print(fields.x)
end
})
{% endhighlight %}
```
Formspecs set this way do not trigger the same callback. In order to
receive form input for meta formspecs, you must include an

View File

@ -87,7 +87,7 @@ to the right of the window, but to resize without breaking.
You can create a HUD element once you have a copy of the player object:
{% highlight lua %}
```lua
local player = minetest.get_player_by_name("username")
local idx = player:hud_add({
hud_elem_type = "text",
@ -97,7 +97,7 @@ local idx = player:hud_add({
alignment = {x = 0, y = 0}, -- center aligned
scale = {x = 100, y = 100}, -- covered later
})
{% endhighlight %}
```
The `hud_add` function returns an element ID - this can be used later to modify
or remove a HUD element.
@ -116,7 +116,7 @@ table. The meaning of other properties varies based on this type.
Let's go ahead, and place all the text in our score panel:
{% highlight lua %}
```lua
player:hud_add({
hud_elem_type = "text",
position = {x = 1, y = 0.5},
@ -152,7 +152,7 @@ player:hud_add({
scale = { x = 50, y = 10},
number = 0xFFFFFF,
})
{% endhighlight %}
```
This results in the following:
@ -167,7 +167,7 @@ This results in the following:
Image elements are created in a very similar way to text elements:
{% highlight lua %}
```lua
player:hud_add({
hud_elem_type = "image",
position = {x = 1, y = 0.5},
@ -176,7 +176,7 @@ player:hud_add({
scale = { x = 1, y = 1},
alignment = { x = 1, y = 0 },
})
{% endhighlight %}
```
You will now have this:
@ -199,7 +199,7 @@ For example, `x=-100` is 100% of the width.
Let's make the progress bar for our score panel as an example of scale:
{% highlight lua %}
```lua
local percent = tonumber(player:get_attribute("scoreboard:score") or 0.2)
player:hud_add({
@ -219,7 +219,7 @@ player:hud_add({
scale = { x = percent, y = 1},
alignment = { x = 1, y = 0 },
})
{% endhighlight %}
```
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.
@ -228,7 +228,7 @@ There is one problem however, it won't update when the stats change.
You can use the ID returned by the hud_add method to update or remove it later.
{% highlight lua %}
```lua
local idx = player:hud_add({
hud_elem_type = "text",
text = "Hello world!",
@ -237,7 +237,7 @@ local idx = player:hud_add({
player:hud_change(idx, "text", "New Text")
player:hud_remove(idx)
{% endhighlight %}
```
The `hud_change` method takes the element ID, the property to change, and the new
value. The above call changes the `text` property from "Hello World" to "Test".
@ -245,16 +245,16 @@ value. The above call changes the `text` property from "Hello World" to "Test".
This means that doing the `hud_change` immediately after the `hud_add` is
functionally equivalent to the following, in a rather inefficient way:
{% highlight lua %}
```lua
local idx = player:hud_add({
hud_elem_type = "text",
text = "New Text",
})
{% endhighlight %}
```
## Storing IDs
{% highlight lua %}
```lua
scoreboard = {}
local saved_huds = {}
@ -287,7 +287,7 @@ minetest.register_on_joinplayer(scoreboard.update_hud)
minetest.register_on_leaveplayer(function(player)
saved_huds[player:get_player_name()] = nil
end)
{% endhighlight %}
```
## Other Elements

View File

@ -23,7 +23,7 @@ gravity twice as strong.
Here is an example of how to add an antigravity command, which
puts the caller in low G:
{% highlight lua %}
```lua
minetest.register_chatcommand("antigravity", {
func = function(name, param)
local player = minetest.get_player_by_name(name)
@ -33,7 +33,7 @@ minetest.register_chatcommand("antigravity", {
})
end
})
{% endhighlight %}
```
## Available Overrides

View File

@ -50,37 +50,37 @@ minetest.conf file) is automatically given all available privileges.
Use `register_privilege` to declare a new privilege:
{% highlight lua %}
```lua
minetest.register_privilege("vote", {
description = "Can vote on issues",
give_to_singleplayer = true
})
{% endhighlight %}
```
If `give_to_singleplayer` is true, you can remove it, because true is the default
when it is not specified. This simplifies the privilege registration to:
{% highlight lua %}
```lua
minetest.register_privilege("vote", {
description = "Can vote on issues"
})
{% endhighlight %}
```
## Checking for Privileges
To quickly check whether a player has all the required privileges:
{% highlight lua %}
```lua
local has, missing = minetest.check_player_privs(player_or_name, {
interact = true,
vote = true })
{% endhighlight %}
```
In this example, `has` is true if the player has all the privileges needed.\\
If `has` is false, then `missing` will contain a dictionary
of missing privileges.
{% highlight lua %}
```lua
if minetest.check_player_privs(name, {interact=true, vote=true}) then
print("Player has all privs!")
else
@ -95,43 +95,43 @@ if has then
else
print("Player is missing privs: " .. dump(missing))
end
{% endhighlight %}
```
## Getting and Setting Privileges
To get a table containing a player's privileges, regardless of whether
the player is logged in, use `minetest.get_player_privs`:
{% highlight lua %}
```lua
local privs = minetest.get_player_privs(name)
print(dump(privs))
{% endhighlight %}
```
This example may give:
{% highlight lua %}
```lua
{
fly = true,
interact = true,
shout = true
}
{% endhighlight %}
```
To set a player's privileges, use `minetest.set_player_privs`:
{% highlight lua %}
```lua
minetest.set_player_privs(name, {
interact = true,
shout = true })
{% endhighlight %}
```
To grant a player privileges, use a combination of the above two functions:
{% highlight lua %}
```lua
local privs = minetest.get_player_privs(name)
privs.vote = true
minetest.set_player_privs(name, privs)
{% endhighlight %}
```
## Adding Privileges to basic_privs

View File

@ -27,14 +27,14 @@ some other format instead.
So, to register a page you need to call the aptly named `sfinv.register_page`
function with the page's name, and its definition. Here is a minimal example:
{% highlight lua %}
```lua
sfinv.register_page("mymod:hello", {
title = "Hello!",
get = function(self, player, context)
-- TODO: implement this
end
})
{% endhighlight %}
```
You can also override an existing page using `sfinv.override_page`.
@ -42,7 +42,7 @@ If you ran the above code and clicked the page's tab, it would probably crash
as sfinv is expecting a response from the `get` method. So let's add a response
to fix that:
{% highlight lua %}
```lua
sfinv.register_page("mymod:hello", {
title = "Hello!",
get = function(self, player, context)
@ -50,7 +50,7 @@ sfinv.register_page("mymod:hello", {
"label[0.1,0.1;Hello world!]", true)
end
})
{% endhighlight %}
```
The `make_formspec` function surrounds your formspec with sfinv's formspec code.
The fourth parameter, currently set as `true`, determines whether or not the
@ -69,7 +69,7 @@ Let's make things more exciting. Here is the code for the formspec generation
part of a player admin tab. This tab will allow admins to kick or ban players by
selecting them in a list and clicking a button.
{% highlight lua %}
```lua
sfinv.register_page("myadmin:myadmin", {
title = "Tab",
get = function(self, player, context)
@ -103,7 +103,7 @@ sfinv.register_page("myadmin:myadmin", {
table.concat(formspec, ""), false)
end,
})
{% endhighlight %}
```
There's nothing new about the above code, all the concepts are covered above and
in previous chapters.
@ -120,11 +120,11 @@ in previous chapters.
You can receive formspec events by adding a `on_player_receive_fields` function
to a sfinv definition.
{% highlight lua %}
```lua
on_player_receive_fields = function(self, player, context, fields)
-- TODO: implement this
end,
{% endhighlight %}
```
Fields is the exact same as the fields given to the subscribers of
`minetest.register_on_player_receive_fields`. The return value of
@ -134,7 +134,7 @@ navigation tab events, so you won't receive them in this callback.
Now let's implement the `on_player_receive_fields` for our admin mod:
{% highlight lua %}
```lua
on_player_receive_fields = function(self, player, context, fields)
-- text list event, check event type and set index if selection changed
if fields.playerlist then
@ -163,7 +163,7 @@ on_player_receive_fields = function(self, player, context, fields)
end
end
end,
{% endhighlight %}
```
There's a rather large problem with this, however. Anyone can kick or ban players! You
need a way to only show this to players with the kick or ban privileges.
@ -174,12 +174,12 @@ Luckily SFINV allows you to do this!
You can add an `is_in_nav` function to your page's definition if you'd like to
control when the page is shown:
{% highlight lua %}
```lua
is_in_nav = function(self, player, context)
local privs = minetest.get_player_privs(player:get_player_name())
return privs.kick or privs.ban
end,
{% endhighlight %}
```
If you only need to check one priv or want to perform an and, you should use
`minetest.check_player_privs()` instead of `get_player_privs`.
@ -192,7 +192,7 @@ This means that you need to manually request that SFINV regenerates the inventor
formspec on any events that may change `is_in_nav`'s result. In our case,
we need to do that whenever kick or ban is granted or revoked to a player:
{% highlight lua %}
```lua
local function on_grant_revoke(grantee, granter, priv)
if priv == "kick" or priv == "ban" then
local player = minetest.get_player_by_name(grantee)
@ -207,7 +207,7 @@ if minetest.register_on_priv_grant then
minetest.register_on_priv_grant(on_grant_revoke)
minetest.register_on_priv_revoke(on_grant_revoke)
end
{% endhighlight %}
```
## on_enter and on_leave callbacks
@ -221,7 +221,7 @@ Also, note that the inventory may not be visible at the time
these callbacks are called. For example, on_enter is called for the home page
when a player joins the game even before they open their inventory!
{% highlight lua %}
```lua
on_enter = function(self, player, context)
end,
@ -229,7 +229,7 @@ end,
on_leave = function(self, player, context)
end,
{% endhighlight %}
```
## Adding to an existing page

View File

@ -83,7 +83,7 @@ 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.
{% highlight lua %}
```lua
-- Data
function land.create(name, area_name)
land.lands[area_name] = {
@ -96,12 +96,12 @@ end
function land.get_by_name(area_name)
return land.lands[area_name]
end
{% endhighlight %}
```
Your actions should also be pure, however calling other functions is more
acceptable.
{% highlight lua %}
```lua
-- Controller
function land.handle_create_submit(name, area_name)
-- process stuff (ie: check for overlaps, check quotas, check permissions)
@ -113,13 +113,13 @@ function land.handle_creation_request(name)
-- This is a bad example, as explained later
land.show_create_formspec(name)
end
{% endhighlight %}
```
Your event handlers will have to interact with the Minetest API. You should keep
the amount of calculations to a minimum, as you won't be able to test this area
very easily.
{% highlight lua %}
```lua
-- View
function land.show_create_formspec(name)
-- Note how there's no complex calculations here!
@ -141,7 +141,7 @@ minetest.register_chatcommand("/land", {
minetest.register_on_player_receive_fields(function(player, formname, fields)
land.handle_create_submit(player:get_player_name(), fields.area_name)
end)
{% endhighlight %}
```
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
@ -217,7 +217,7 @@ Enter the Observer pattern. Instead of the mobs mod caring about awards, mobs
exposes a way for other areas of code to register their interest in an event
and receive data about the event.
{% highlight lua %}
```lua
mobs.registered_on_death = {}
function mobs.register_on_death(func)
table.insert(mobs.registered_on_death, func)
@ -227,11 +227,11 @@ end
for i=1, #mobs.registered_on_death do
mobs.registered_on_death[i](entity, reason)
end
{% endhighlight %}
```
Then the other code registers its interest:
{% highlight lua %}
```lua
-- awards
mobs.register_on_death(function(mob, reason)
@ -239,7 +239,7 @@ mobs.register_on_death(function(mob, reason)
awards.notify_mob_kill(reason.object, mob.name)
end
end)
{% endhighlight %}
```
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

View File

@ -21,7 +21,7 @@ offline or the entity is unloaded - then calling any methods will result in a cr
Don't do this:
{% highlight lua %}
```lua
minetest.register_on_joinplayer(function(player)
local function func()
local pos = player:get_pos() -- BAD!
@ -36,11 +36,11 @@ minetest.register_on_joinplayer(function(player)
-- It's recommended to just not do this
-- use minetest.get_connected_players() and minetest.get_player_by_name() instead.
end)
{% endhighlight %}
```
instead, do this:
{% highlight lua %}
```lua
minetest.register_on_joinplayer(function(player)
local function func(name)
-- Attempt to get the ref again
@ -56,7 +56,7 @@ minetest.register_on_joinplayer(function(player)
-- Pass the name into the function
minetest.after(1, func, player:get_player_name())
end)
{% endhighlight %}
```
## Don't Trust Formspec Submissions
@ -66,7 +66,7 @@ they like.
The following code has a vulnerability where any player can give
themselves moderator privileges:
{% highlight lua %}
```lua
local function show_formspec(name)
if not minetest.check_player_privs(name, { privs = true }) then
return false
@ -89,11 +89,11 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
minetest.set_player_privs(fields.target, privs)
return true
end)
{% endhighlight %}
```
Instead, do this:
{% highlight lua %}
```lua
minetest.register_on_player_receive_fields(function(player, formname, fields)
if not minetest.check_player_privs(name, { privs = true }) then
return false
@ -101,7 +101,7 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
-- code
end)
{% endhighlight %}
```
## Set ItemStacks After Changing Them
@ -112,22 +112,22 @@ This means that modifying a stack won't actually modify that stack in the invent
Don't do this:
{% highlight lua %}
```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
{% endhighlight %}
```
Do this:
{% highlight lua %}
```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
{% endhighlight %}
```
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,
@ -135,23 +135,23 @@ it will only be saved in the engine if the callback caller sets it.
Avoid this:
{% highlight lua %}
```lua
minetest.register_on_item_eat(function(hp_change, replace_with_item, itemstack, user, pointed_thing)
itemstack:get_meta():set_string("description", "Partially eaten")
-- Almost correct! Data will be lost if another callback cancels the behaviour
end)
{% endhighlight %}
```
If no callbacks cancel, then the stack will be set and the description will be updated.
If a callback cancels, then the update may be lost. It's better to do this instead:
{% highlight lua %}
```lua
minetest.register_on_item_eat(function(hp_change, replace_with_item, itemstack, user, pointed_thing)
itemstack:get_meta():set_string("description", "Partially eaten")
user:get_inventory():set_stack("main", user:get_wield_index(), itemstack)
-- Correct, description will always be set!
end)
{% endhighlight %}
```
If the callbacks cancel or the callback runner doesn't set the stack,
then our update will still be set.

View File

@ -59,7 +59,7 @@ root of your game, modpack, or mod.
Put the following contents in it:
{% highlight lua %}
```lua
unused_args = false
allow_defined_top = true
@ -78,7 +78,7 @@ read_globals = {
-- MTG
"default", "sfinv", "creative",
}
{% endhighlight %}
```
Next you'll need to test that it works by running LuaCheck. You should get a lot
less errors this time. Starting at the first error you get, either modify the
@ -121,7 +121,7 @@ and enable Travis by flipping the switch.
Next, create a file called .travis.yml with the following content:
{% highlight yml %}
```yml
language: generic
sudo: false
addons:
@ -134,14 +134,14 @@ script:
- $HOME/.luarocks/bin/luacheck --no-color .
notifications:
email: false
{% endhighlight %}
```
If your project is a game rather than a mod or mod pack,
change the line after `script:` to:
{% highlight yml %}
```yml
- $HOME/.luarocks/bin/luacheck --no-color mods/
{% endhighlight %}
```
Now commit and push to Github. Go to your project's page on Github, and click
commits. You should see an orange disc next to the commit you just made.

View File

@ -40,7 +40,7 @@ Any users can submit almost any formspec with any values at any time.
Here's some real code found in a mod:
{% highlight lua %}
```lua
minetest.register_on_player_receive_fields(function(player, formname, fields)
-- Todo: fix security issue here
local name = player:get_player_name()
@ -56,7 +56,7 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
end
end
end
{% endhighlight %}
```
Can you spot the issue? A malicious user could submit a formspec containing
their own position values, allowing them to teleport to anywhere they wish to.
@ -90,19 +90,19 @@ to the full Lua API.
Can you spot the vulnerability in the following?
{% highlight lua %}
```lua
local ie = minetest.request_insecure_environment()
ie.os.execute(("path/to/prog %d"):format(3))
{% endhighlight %}
```
`String.format` is a function in the global shared table `String`.
A malicious mod could override this function and pass stuff to os.execute:
{% highlight lua %}
```lua
String.format = function()
return "xdg-open 'http://example.com'"
end
{% endhighlight %}
```
The mod could pass something a lot more malicious than opening a website, such
as giving a remote user control over the machine.

View File

@ -49,25 +49,25 @@ names ending in `_spec`, and then executes them in a standalone Lua environment.
### init.lua
{% highlight lua %}
```lua
mymod = {}
dofile(minetest.get_modpath("mymod") .. "/api.lua")
{% endhighlight %}
```
### api.lua
{% highlight lua %}
```lua
function mymod.add(x, y)
return x + y
end
{% endhighlight %}
```
### tests/api_spec.lua
{% highlight lua %}
```lua
-- Look for required things in
package.path = "../?.lua;" .. package.path
@ -87,7 +87,7 @@ describe("add", function()
assert.equals(-2, mymod.add(-1, -1))
end)
end)
{% endhighlight %}
```
You can now run the tests by opening a terminal in the mod's directory and
running `busted .`
@ -114,7 +114,7 @@ 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.
{% highlight lua %}
```lua
-- As above, make a table
_G.minetest = {}
@ -157,7 +157,7 @@ describe("list_areas", function()
assert.same(expected, chat_send_all_calls)
end)
end)
{% endhighlight %}
```
## Checking Commits with Travis
@ -165,7 +165,7 @@ end)
The Travis script from the [Error Checking](luacheck.html)
chapter can be modified to also run Busted.
{% highlight yml %}
```yml
language: generic
sudo: false
addons:
@ -179,7 +179,7 @@ script:
- $HOME/.luarocks/bin/busted .
notifications:
email: false
{% endhighlight %}
```
## Conclusion