Use GH-markdown code highlighting instead of Liquid block

master
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 Paragraphs
{% highlight lua %} ```lua
code code
{% endhighlight %} ```
## Parts in ## 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 default
### init.lua ### init.lua
{% highlight lua %} ```lua
print("This file will be run at load time!") print("This file will be run at load time!")
minetest.register_node("mymod:node", { minetest.register_node("mymod:node", {
@ -128,7 +128,7 @@ minetest.register_node("mymod:node", {
}, },
groups = {cracky = 1} groups = {cracky = 1}
}) })
{% endhighlight %} ```
### mod.conf ### mod.conf
name = mymod 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 Code highlighting gives different colours to different words and characters
depending on what they mean. This allows you to spot mistakes. depending on what they mean. This allows you to spot mistakes.
{% highlight lua %} ```lua
function ctf.post(team,msg) function ctf.post(team,msg)
if not ctf.team(team) then if not ctf.team(team) then
return false return false
@ -44,7 +44,7 @@ function ctf.post(team,msg)
return true return true
end end
{% endhighlight %} ```
For example, keywords in the above snippet are highlighted such as if, then, end, return. 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. 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? So, what do statements in Lua look like?
{% highlight lua %} ```lua
local a = 2 -- Set 'a' to 2 local a = 2 -- Set 'a' to 2
local b = 2 -- Set 'b' to 2 local b = 2 -- Set 'b' to 2
local result = a + b -- Set 'result' to a + b, which is 4 local result = a + b -- Set 'result' to a + b, which is 4
a = a + 10 a = a + 10
print("Sum is "..result) print("Sum is "..result)
{% endhighlight %} ```
Whoa, what happened there? 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: 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. local random_number = math.random(1, 100) -- Between 1 and 100.
if random_number > 50 then if random_number > 50 then
print("Woohoo!") print("Woohoo!")
else else
print("No!") print("No!")
end end
{% endhighlight %} ```
That example generates a random number between 1 and 100. It then prints 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!". "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: That doesn't contain every possible operator, and you can combine operators like this:
{% highlight lua %} ```lua
if not A and B then if not A and B then
print("Yay!") print("Yay!")
end end
{% endhighlight %} ```
Which prints "Yay!" if A is false and B is true. Which prints "Yay!" if A is false and B is true.
Logical and arithmetic operators work exactly the same, they both accept inputs Logical and arithmetic operators work exactly the same, they both accept inputs
and return a value which can be stored. and return a value which can be stored.
{% highlight lua %} ```lua
local A = 5 local A = 5
local is_equal = (A == 5) local is_equal = (A == 5)
if is_equal then if is_equal then
print("Is equal!") print("Is equal!")
end end
{% endhighlight %} ```
## Programming ## 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. 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: A local variable is only accessible from where it is defined. Here are some examples:
{% highlight lua %} ```lua
-- Accessible from within this script file -- Accessible from within this script file
local one = 1 local one = 1
@ -221,11 +221,11 @@ function myfunc()
local three = one + two local three = one + two
end end
end end
{% endhighlight %} ```
Whereas global variables can be accessed from anywhere in the script file, and from any other mod. Whereas global variables can be accessed from anywhere in the script file, and from any other mod.
{% highlight lua %} ```lua
my_global_variable = "blah" my_global_variable = "blah"
function one() function one()
@ -235,7 +235,7 @@ end
print(my_global_variable) -- Output: "blah" print(my_global_variable) -- Output: "blah"
one() one()
print(my_global_variable) -- Output: "three" print(my_global_variable) -- Output: "three"
{% endhighlight %} ```
### Locals should be used as much as possible ### 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). Lua is global by default (unlike most other programming languages).
Local variables must be identified as such. Local variables must be identified as such.
{% highlight lua %} ```lua
function one() function one()
foo = "bar" foo = "bar"
end end
@ -254,7 +254,7 @@ end
one() one()
two() two()
{% endhighlight %} ```
dump() is a function that can turn any variable into a string so the programmer can 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 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": To correct this, use "local":
{% highlight lua %} ```lua
function one() function one()
local foo = "bar" local foo = "bar"
end end
@ -277,7 +277,7 @@ end
one() one()
two() two()
{% endhighlight %} ```
Remember that nil means **not initialised**. Remember that nil means **not initialised**.
The variable hasn't been assigned a value yet, 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 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. should be made local, as other mods could have functions of the same name.
{% highlight lua %} ```lua
local function foo(bar) local function foo(bar)
return bar * 2 return bar * 2
end end
{% endhighlight %} ```
API tables should be used to allow other mods to call the functions, like so: API tables should be used to allow other mods to call the functions, like so:
{% highlight lua %} ```lua
mymod = {} mymod = {}
function mymod.foo(bar) function mymod.foo(bar)
@ -303,27 +303,27 @@ end
-- In another mod, or script: -- In another mod, or script:
mymod.foo("foobar") mymod.foo("foobar")
{% endhighlight %} ```
## Including other Lua Scripts ## Including other Lua Scripts
The recommended way to include other Lua scripts in a mod is to use *dofile*. 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") 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. "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: A script can return a value, which is useful for sharing private locals:
{% highlight lua %} ```lua
-- script.lua -- script.lua
return "Hello world!" return "Hello world!"
-- init.lua -- init.lua
local ret = dofile(minetest.get_modpath("modname") .. "/script.lua") local ret = dofile(minetest.get_modpath("modname") .. "/script.lua")
print(ret) -- Hello world! print(ret) -- Hello world!
{% endhighlight %} ```
Later chapters will discuss how to split up the code of a mod in a lot of detail. 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 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: Use a player's name to get their inventory:
{% highlight lua %} ```lua
local inv = minetest.get_inventory({type="player", name="celeron55"}) local inv = minetest.get_inventory({type="player", name="celeron55"})
{% endhighlight %} ```
### Node Inventories ### 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: Use its position to get a node inventory:
{% highlight lua %} ```lua
local inv = minetest.get_inventory({type="node", pos={x=, y=, z=}}) local inv = minetest.get_inventory({type="node", pos={x=, y=, z=}})
{% endhighlight %} ```
### Detached Inventories ### 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: Use the inventory name to get a detached inventory:
{% highlight lua %} ```lua
local inv = minetest.get_inventory({type="detached", name="inventory_name"}) local inv = minetest.get_inventory({type="detached", name="inventory_name"})
{% endhighlight %} ```
You can create your own detached inventories: You can create your own detached inventories:
{% highlight lua %} ```lua
minetest.create_detached_inventory("inventory_name", callbacks) minetest.create_detached_inventory("inventory_name", callbacks)
{% endhighlight %} ```
This creates a detached inventory or, if the inventory already exists, it is cleared. 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). 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: You can check where an inventory is located:
{% highlight lua %} ```lua
local location = inv:get_location() local location = inv:get_location()
{% endhighlight %} ```
This will return a table like the one passed to `minetest.get_inventory()`. 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. 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. They also have a width, which is used to divide them into a grid.
{% highlight lua %} ```lua
if inv:set_size("main", 32) then if inv:set_size("main", 32) then
inv:set_width("main", 8) inv:set_width("main", 8)
print("size: " .. inv.get_size("main")) print("size: " .. inv.get_size("main"))
@ -133,7 +133,7 @@ if inv:set_size("main", 32) then
else else
print("Error!") print("Error!")
end end
{% endhighlight %} ```
<!--The width and height of an inventory in a [formspec](formspecs.html) is <!--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 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: You can use `list_is_empty` to check if a list is empty:
{% highlight lua %} ```lua
if inv:is_empty("main") then if inv:is_empty("main") then
print("The list is empty!") print("The list is empty!")
end end
{% endhighlight %} ```
### Lua Tables ### Lua Tables
You can convert an inventory to a Lua table: You can convert an inventory to a Lua table:
{% highlight lua %} ```lua
local lists = inv:get_lists() local lists = inv:get_lists()
{% endhighlight %} ```
The table will be in this form: The table will be in this form:
{% highlight lua %} ```lua
{ {
list_one = { list_one = {
ItemStack, ItemStack,
@ -176,13 +176,13 @@ The table will be in this form:
-- inv:get_size("list_two") elements -- inv:get_size("list_two") elements
} }
} }
{% endhighlight %} ```
You can then set the inventory: You can then set the inventory:
{% highlight lua %} ```lua
inv:set_lists(lists) inv:set_lists(lists)
{% endhighlight %} ```
Please note that the sizes of lists will not change. 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: You can do the above for individual lists:
{% highlight lua %} ```lua
local list = inv:get_list("list_one") local list = inv:get_list("list_one")
{% endhighlight %} ```
It will be in this form: It will be in this form:
{% highlight lua %} ```lua
{ {
ItemStack, ItemStack,
ItemStack, ItemStack,
@ -204,13 +204,13 @@ It will be in this form:
ItemStack, ItemStack,
-- inv:get_size("list_one") elements -- inv:get_size("list_one") elements
} }
{% endhighlight %} ```
You can then set the list: You can then set the list:
{% highlight lua %} ```lua
inv:set_list("list_one", list) inv:set_list("list_one", list)
{% endhighlight %} ```
Please note that the sizes of lists will not change. 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"`: To add items to a list named `"main"`:
{% highlight lua %} ```lua
local stack = ItemStack("default:stone 99") local stack = ItemStack("default:stone 99")
local leftover = inv:add_item("main", stack) local leftover = inv:add_item("main", stack)
if leftover:get_count() > 0 then if leftover:get_count() > 0 then
print("Inventory is full! " .. leftover:get_count() .. " items weren't added") print("Inventory is full! " .. leftover:get_count() .. " items weren't added")
end end
{% endhighlight %} ```
### Checking for Room ### Checking for Room
To check whether a list has room for items: To check whether a list has room for items:
{% highlight lua %} ```lua
if not inv:room_for_item("main", stack) then if not inv:room_for_item("main", stack) then
print("Not enough room!") print("Not enough room!")
end end
{% endhighlight %} ```
### Taking Items ### Taking Items
To remove items from a list: To remove items from a list:
{% highlight lua %} ```lua
local taken = inv:remove_item("main", stack) local taken = inv:remove_item("main", stack)
print("Took " .. taken:get_count()) print("Took " .. taken:get_count())
{% endhighlight %} ```
### Checking Inventory Contents ### Checking Inventory Contents
To check whether an inventory contains a specific quantity of an item: To check whether an inventory contains a specific quantity of an item:
{% highlight lua %} ```lua
if not inv:contains_item(listname, stack) then if not inv:contains_item(listname, stack) then
print("Item not in inventory!") print("Item not in inventory!")
end end
{% endhighlight %} ```
This works if the item count is split up over multiple stacks. This works if the item count is split up over multiple stacks.
For example checking for "default:stone 200" will work if there 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: You can manipulate individual stacks:
{% highlight lua %} ```lua
local stack = inv:get_stack(listname, 0) local stack = inv:get_stack(listname, 0)
inv:set_stack(listname, 0, stack) 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: You can create a stack like so:
{% highlight lua %} ```lua
local items = ItemStack("default:dirt") local items = ItemStack("default:dirt")
local items = ItemStack("default:stone 99") local items = ItemStack("default:stone 99")
{% endhighlight %} ```
You could alternatively create a blank ItemStack and fill it using methods: You could alternatively create a blank ItemStack and fill it using methods:
{% highlight lua %} ```lua
local items = ItemStack() local items = ItemStack()
if items:set_name("default:dirt") then if items:set_name("default:dirt") then
items:set_count(99) items:set_count(99)
else else
print("An error occured!") print("An error occured!")
end end
{% endhighlight %} ```
And you can copy stacks like this: And you can copy stacks like this:
{% highlight lua %} ```lua
local items2 = ItemStack(items) local items2 = ItemStack(items)
{% endhighlight %} ```
## Name and Count ## Name and Count
{% highlight lua %} ```lua
local items = ItemStack("default:stone") local items = ItemStack("default:stone")
print(items:get_name()) -- default:stone print(items:get_name()) -- default:stone
print(items:get_count()) -- 1 print(items:get_count()) -- 1
@ -64,7 +64,7 @@ if items:set_name("default:dirt") then
else else
error("This shouldn't happen") error("This shouldn't happen")
end end
{% endhighlight %} ```
## Adding and Taking Items ## Adding and Taking Items
@ -73,26 +73,26 @@ end
Use `add_item` to add items to an ItemStack. Use `add_item` to add items to an ItemStack.
An ItemStack of the items that could not be added is returned. An ItemStack of the items that could not be added is returned.
{% highlight lua %} ```lua
local items = ItemStack("default:stone 50") local items = ItemStack("default:stone 50")
local to_add = ItemStack("default:stone 100") local to_add = ItemStack("default:stone 100")
local leftovers = items:add_item(to_add) local leftovers = items:add_item(to_add)
print("Could not add" .. leftovers:get_count() .. " of the items.") print("Could not add" .. leftovers:get_count() .. " of the items.")
-- ^ will be 51 -- ^ will be 51
{% endhighlight %} ```
## Taking ## Taking
The following code takes **up to** 100. The following code takes **up to** 100.
If there aren't enough items in the stack, it will take as much as it can. 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) local taken = items:take_item(100)
-- taken is the ItemStack taken from the main ItemStack -- taken is the ItemStack taken from the main ItemStack
print("Took " .. taken:get_count() .. " items") print("Took " .. taken:get_count() .. " items")
{% endhighlight %} ```
## Wear ## Wear
@ -101,36 +101,36 @@ the more wear.
You use `add_wear()`, `get_wear()` and `set_wear(wear)`. You use `add_wear()`, `get_wear()` and `set_wear(wear)`.
{% highlight lua %} ```lua
local items = ItemStack("default:dirt") local items = ItemStack("default:dirt")
local max_uses = 10 local max_uses = 10
-- This is done automatically when you use a tool that digs things -- This is done automatically when you use a tool that digs things
-- It increases the wear of an item by one use. -- It increases the wear of an item by one use.
items:add_wear(65535 / (max_uses - 1)) items:add_wear(65535 / (max_uses - 1))
{% endhighlight %} ```
When digging a node, the amount of wear a tool gets may depends on the node When digging a node, the amount of wear a tool gets may depends on the node
being dug. So max_uses varies depending on what is being dug. being dug. So max_uses varies depending on what is being dug.
## Lua Tables ## Lua Tables
{% highlight lua %} ```lua
local data = items:to_table() local data = items:to_table()
local items2 = ItemStack(data) local items2 = ItemStack(data)
{% endhighlight %} ```
## Metadata ## Metadata
ItemStacks can have metadata, and use the same API as [Node Metadata](node_metadata.html). ItemStacks can have metadata, and use the same API as [Node Metadata](node_metadata.html).
{% highlight lua %} ```lua
local meta = items:get_meta() local meta = items:get_meta()
meta:set_string("foo", meta:get_string("foo") .. " ha!") meta:set_string("foo", meta:get_string("foo") .. " ha!")
print(dump(meta:get_string("foo"))) print(dump(meta:get_string("foo")))
-- if ran multiple times, would give " ha! ha! ha!" -- if ran multiple times, would give " ha! ha! ha!"
{% endhighlight %} ```
## More Methods ## 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. 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. Notice how you don't need to declare the drawtype.
{% highlight lua %} ```lua
minetest.register_node("mymod:diamond", { minetest.register_node("mymod:diamond", {
description = "Alien Diamond", description = "Alien Diamond",
tiles = { tiles = {
@ -69,14 +69,14 @@ minetest.register_node("mymod:diamond", {
groups = {cracky = 3}, groups = {cracky = 3},
drop = "mymod:diamond_fragments" drop = "mymod:diamond_fragments"
}) })
{% endhighlight %} ```
## Airlike ## Airlike
These nodes are see through and thus have no textures. These nodes are see through and thus have no textures.
{% highlight lua %} ```lua
minetest.register_node("myair:air", { minetest.register_node("myair:air", {
description = "MyAir (you hacker you!)", description = "MyAir (you hacker you!)",
drawtype = "airlike", drawtype = "airlike",
@ -97,7 +97,7 @@ minetest.register_node("myair:air", {
drop = "", drop = "",
groups = {not_in_creative_inventory=1} groups = {not_in_creative_inventory=1}
}) })
{% endhighlight %} ```
## Liquid ## Liquid
@ -112,7 +112,7 @@ These nodes are complete liquid nodes, the liquid flows outwards from position
using the flowing liquid drawtype. using the flowing liquid drawtype.
For each liquid node you should also have a flowing liquid node. For each liquid node you should also have a flowing liquid node.
{% highlight lua %} ```lua
-- Some properties have been removed as they are beyond -- Some properties have been removed as they are beyond
-- the scope of this chapter. -- the scope of this chapter.
minetest.register_node("default:water_source", { 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}, post_effect_color = {a=64, r=100, g=100, b=200},
-- ^ color of screen when the player is submerged -- ^ color of screen when the player is submerged
}) })
{% endhighlight %} ```
### FlowingLiquid ### FlowingLiquid
@ -201,7 +201,7 @@ edges are hidden, like this:
</figcaption> </figcaption>
</figure> </figure>
{% highlight lua %} ```lua
minetest.register_node("default:obsidian_glass", { minetest.register_node("default:obsidian_glass", {
description = "Obsidian Glass", description = "Obsidian Glass",
drawtype = "glasslike", drawtype = "glasslike",
@ -212,7 +212,7 @@ minetest.register_node("default:obsidian_glass", {
sounds = default.node_sound_glass_defaults(), sounds = default.node_sound_glass_defaults(),
groups = {cracky=3,oddly_breakable_by_hand=3}, groups = {cracky=3,oddly_breakable_by_hand=3},
}) })
{% endhighlight %} ```
## Glasslike_Framed ## Glasslike_Framed
@ -226,7 +226,7 @@ than individual nodes, like the following:
</figcaption> </figcaption>
</figure> </figure>
{% highlight lua %} ```lua
minetest.register_node("default:glass", { minetest.register_node("default:glass", {
description = "Glass", description = "Glass",
drawtype = "glasslike_framed", drawtype = "glasslike_framed",
@ -241,7 +241,7 @@ minetest.register_node("default:glass", {
groups = {cracky = 3, oddly_breakable_by_hand = 3}, groups = {cracky = 3, oddly_breakable_by_hand = 3},
sounds = default.node_sound_glass_defaults() sounds = default.node_sound_glass_defaults()
}) })
{% endhighlight %} ```
### Glasslike_Framed_Optional ### 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 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. looking through the transparent holes, but instead a nice leaves effect.
{% highlight lua %} ```lua
minetest.register_node("default:leaves", { minetest.register_node("default:leaves", {
description = "Leaves", description = "Leaves",
drawtype = "allfaces_optional", drawtype = "allfaces_optional",
tiles = {"default_leaves.png"} tiles = {"default_leaves.png"}
}) })
{% endhighlight %} ```
### Allfaces_Optional ### 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 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. items which need to have different textures depending on where they are placed.
{% highlight lua %} ```lua
minetest.register_node("foobar:torch", { minetest.register_node("foobar:torch", {
description = "Foobar Torch", description = "Foobar Torch",
drawtype = "torchlike", 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}, wall_side = {-0.5, -0.3, -0.1, -0.5+0.3, 0.3, 0.1},
} }
}) })
{% endhighlight %} ```
## Nodebox ## 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 Nodeboxes allow you to create a node which is not cubic, but is instead made out
of as many cuboids as you like. of as many cuboids as you like.
{% highlight lua %} ```lua
minetest.register_node("stairs:stair_stone", { minetest.register_node("stairs:stair_stone", {
drawtype = "nodebox", drawtype = "nodebox",
paramtype = "light", paramtype = "light",
@ -332,14 +332,14 @@ minetest.register_node("stairs:stair_stone", {
}, },
} }
}) })
{% endhighlight %} ```
The most important part is the nodebox table: 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.5, -0.5, 0.5, 0, 0.5},
{-0.5, 0, 0, 0.5, 0.5, 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. 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 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. 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", { minetest.register_node("default:sign_wall", {
drawtype = "nodebox", drawtype = "nodebox",
node_box = { node_box = {
@ -378,7 +378,7 @@ minetest.register_node("default:sign_wall", {
} }
}, },
}) })
{% endhighlight %} ```
## Mesh ## Mesh
@ -392,7 +392,7 @@ invisible but still rendered.
You can register a mesh node as so: You can register a mesh node as so:
{% highlight lua %} ```lua
minetest.register_node("mymod:meshy", { minetest.register_node("mymod:meshy", {
drawtype = "mesh", drawtype = "mesh",
@ -404,7 +404,7 @@ minetest.register_node("mymod:meshy", {
-- Path to the mesh -- Path to the mesh
mesh = "mymod_meshy.b3d", mesh = "mymod_meshy.b3d",
}) })
{% endhighlight %} ```
Make sure that the mesh is available in a `models` directory. Make sure that the mesh is available in a `models` directory.
Most of the time the mesh should be in your mod's folder, however it's okay to Most of the time the mesh should be in your mod's folder, however it's okay to
@ -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 instead use the `nodebox` drawtype to provide a 3D effect. The `signlike` drawtype
is, however, commonly used by ladders. is, however, commonly used by ladders.
{% highlight lua %} ```lua
minetest.register_node("default:ladder_wood", { minetest.register_node("default:ladder_wood", {
drawtype = "signlike", drawtype = "signlike",
@ -433,7 +433,7 @@ minetest.register_node("default:ladder_wood", {
type = "wallmounted", type = "wallmounted",
}, },
}) })
{% endhighlight %} ```
## Plantlike ## Plantlike
@ -446,7 +446,7 @@ minetest.register_node("default:ladder_wood", {
Plantlike nodes draw their tiles in an X like pattern. Plantlike nodes draw their tiles in an X like pattern.
{% highlight lua %} ```lua
minetest.register_node("default:papyrus", { minetest.register_node("default:papyrus", {
drawtype = "plantlike", drawtype = "plantlike",
@ -458,7 +458,7 @@ minetest.register_node("default:papyrus", {
fixed = {-6 / 16, -0.5, -6 / 16, 6 / 16, 0.5, 6 / 16}, fixed = {-6 / 16, -0.5, -6 / 16, 6 / 16, 0.5, 6 / 16},
}, },
}) })
{% endhighlight %} ```
## Firelike ## Firelike
@ -472,11 +472,11 @@ and ceilings.
</figcaption> </figcaption>
</figure> </figure>
{% highlight lua %} ```lua
minetest.register_node("mymod:clingere", { minetest.register_node("mymod:clingere", {
drawtype = "firelike", drawtype = "firelike",
-- Only one texture used -- Only one texture used
tiles = { "mymod:clinger" }, 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*. Item definitions consist of an *item name* and a *definition table*.
The definition table contains attributes which affect the behaviour of the item. The definition table contains attributes which affect the behaviour of the item.
{% highlight lua %} ```lua
minetest.register_craftitem("modname:itemname", { minetest.register_craftitem("modname:itemname", {
description = "My Special Item", description = "My Special Item",
inventory_image = "modname_itemname.png" inventory_image = "modname_itemname.png"
}) })
{% endhighlight %} ```
### Item Names and Aliases ### 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* A good way to remember is `from → to` where *from* is the alias and *to*
is the target. is the target.
{% highlight lua %} ```lua
minetest.register_alias("dirt", "default:dirt") minetest.register_alias("dirt", "default:dirt")
{% endhighlight %} ```
Mods need to make sure to resolve aliases before dealing directly with item names, Mods need to make sure to resolve aliases before dealing directly with item names,
as the engine won't do this. as the engine won't do this.
This is pretty simple though: This is pretty simple though:
{% highlight lua %} ```lua
itemname = minetest.registered_aliases[itemname] or itemname itemname = minetest.registered_aliases[itemname] or itemname
{% endhighlight %} ```
### Textures ### Textures
@ -108,14 +108,14 @@ resulting in lowered performance.
## Registering a basic node ## Registering a basic node
{% highlight lua %} ```lua
minetest.register_node("mymod:diamond", { minetest.register_node("mymod:diamond", {
description = "Alien Diamond", description = "Alien Diamond",
tiles = {"mymod_diamond.png"}, tiles = {"mymod_diamond.png"},
is_ground_content = true, is_ground_content = true,
groups = {cracky=3, stone=1} groups = {cracky=3, stone=1}
}) })
{% endhighlight %} ```
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.
@ -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. Remember: Just like with most 3D graphics, +Y is upwards in Minetest.
{% highlight lua %} ```lua
minetest.register_node("mymod:diamond", { minetest.register_node("mymod:diamond", {
description = "Alien Diamond", description = "Alien Diamond",
tiles = { tiles = {
@ -142,7 +142,7 @@ minetest.register_node("mymod:diamond", {
drop = "mymod:diamond_fragments" drop = "mymod:diamond_fragments"
-- ^ Rather than dropping 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. 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. 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. Having a use callback prevents the item being used to dig nodes.
One common use of the use callback is for food: One common use of the use callback is for food:
{% highlight lua %} ```lua
minetest.register_craftitem("mymod:mudpie", { minetest.register_craftitem("mymod:mudpie", {
description = "Alien Mud Pie", description = "Alien Mud Pie",
inventory_image = "myfood_mudpie.png", inventory_image = "myfood_mudpie.png",
on_use = minetest.item_eat(20), on_use = minetest.item_eat(20),
}) })
{% endhighlight %} ```
The number supplied to the minetest.item_eat function is the number of hit points The number supplied to the minetest.item_eat function is the number of hit points
healed when this food is consumed. 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. as the on_use callback.
This means the code above is roughly similar to this: This means the code above is roughly similar to this:
{% highlight lua %} ```lua
minetest.register_craftitem("mymod:mudpie", { minetest.register_craftitem("mymod:mudpie", {
description = "Alien Mud Pie", description = "Alien Mud Pie",
inventory_image = "myfood_mudpie.png", inventory_image = "myfood_mudpie.png",
@ -186,7 +186,7 @@ minetest.register_craftitem("mymod:mudpie", {
return minetest.do_item_eat(20, nil, ...) return minetest.do_item_eat(20, nil, ...)
end, end,
}) })
{% endhighlight %} ```
By, understanding how item_eat works by simply returning a function, it's 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. 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 pattern to work. In the below example, the fragments need to be in a
chair-like pattern for the craft to work. chair-like pattern for the craft to work.
{% highlight lua %} ```lua
minetest.register_craft({ minetest.register_craft({
type = "shaped", type = "shaped",
output = "mymod:diamond_chair 99", output = "mymod:diamond_chair 99",
@ -222,7 +222,7 @@ minetest.register_craft({
{"mymod:diamond_fragments", "mymod:diamond_fragments", ""} {"mymod:diamond_fragments", "mymod:diamond_fragments", ""}
} }
}) })
{% endhighlight %} ```
One thing to note is the blank column on the right-hand side. 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 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 If this empty column shouldn't be required, then the empty strings can be left
out like so: out like so:
{% highlight lua %} ```lua
minetest.register_craft({ minetest.register_craft({
output = "mymod:diamond_chair 99", output = "mymod:diamond_chair 99",
recipe = { recipe = {
@ -239,7 +239,7 @@ minetest.register_craft({
{"mymod:diamond_fragments", "mymod:diamond_fragments"} {"mymod:diamond_fragments", "mymod:diamond_fragments"}
} }
}) })
{% endhighlight %} ```
The type field isn't actually needed for shapeless crafts, as shapeless is the The type field isn't actually needed for shapeless crafts, as shapeless is the
default craft type. 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 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.
{% highlight lua %} ```lua
minetest.register_craft({ minetest.register_craft({
type = "shapeless", type = "shapeless",
output = "mymod:diamond 3", 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 %} ```
### Cooking and Fuel ### Cooking and Fuel
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.
{% highlight lua %} ```lua
minetest.register_craft({ minetest.register_craft({
type = "cooking", type = "cooking",
output = "mymod:diamond_fragments", output = "mymod:diamond_fragments",
recipe = "default:coalblock", recipe = "default:coalblock",
cooktime = 10, cooktime = 10,
}) })
{% endhighlight %} ```
The only real difference in the code is that the recipe is just a single item, The only real difference in the code is that the recipe is just a single item,
compared to being in a table (between braces). 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 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.
{% highlight lua %} ```lua
minetest.register_craft({ minetest.register_craft({
type = "fuel", type = "fuel",
recipe = "mymod:diamond", recipe = "mymod:diamond",
burntime = 300, burntime = 300,
}) })
{% endhighlight %} ```
They don't have an output like other recipes, but they have a burn time 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. 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, Groups are defined using the `groups` property in the definition table,
and have an associated value. and have an associated value.
{% highlight lua %} ```lua
groups = {cracky = 3, wood = 1} groups = {cracky = 3, wood = 1}
{% endhighlight %} ```
There are several reasons you use groups. There are several reasons you use groups.
Firstly, groups are used to describe properties such as dig types and flammability. 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 Secondly, groups can be used in a craft recipe instead of an item name to allow
any item in group to be used. any item in group to be used.
{% highlight lua %} ```lua
minetest.register_craft({ minetest.register_craft({
type = "shapeless", type = "shapeless",
output = "mymod:diamond_thing 3", output = "mymod:diamond_thing 3",
recipe = {"group:wood", "mymod:diamond"} recipe = {"group:wood", "mymod:diamond"}
}) })
{% endhighlight %} ```
## Tools, Capabilities and Dig Types ## 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 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. capability, then the capability of the current hand is used instead.
{% highlight lua %} ```lua
minetest.register_tool("mymod:tool", { minetest.register_tool("mymod:tool", {
description = "My Tool", description = "My Tool",
inventory_image = "mymod_tool.png", inventory_image = "mymod_tool.png",
@ -364,7 +364,7 @@ minetest.register_tool("mymod:tool", {
damage_groups = {fleshy=2}, damage_groups = {fleshy=2},
}, },
}) })
{% endhighlight %} ```
Groupcaps is the list of supported dig types for digging nodes. Groupcaps is the list of supported dig types for digging nodes.
Damage groups are for controlling how tools damage objects, which will be 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 To create it, you first need to register the grass, then
write an ABM. write an ABM.
{% highlight lua %} ```lua
minetest.register_node("aliens:grass", { minetest.register_node("aliens:grass", {
description = "Alien Grass", description = "Alien Grass",
light_source = 3, -- The node radiates light. Values can be from 1 to 15 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"}) minetest.set_node({x = pos.x, y = pos.y + 1, z = pos.z}, {name = "aliens:grass"})
end end
}) })
{% endhighlight %} ```
This ABM runs every ten seconds. There is a 1 in 5 chance of the ABM running on each 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 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: 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 }) local node = minetest.get_node({ x = 1, y = 3, z = 4 })
print(dump(node)) --> { name=.., param1=.., param2=.. } print(dump(node)) --> { name=.., param1=.., param2=.. }
{% endhighlight %} ```
If the position is a decimal, it will be rounded to the containing node. 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: 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, better near mese. You would need to search for any nearby mese nodes,
and adapt the growth rate accordingly. and adapt the growth rate accordingly.
{% highlight lua %} ```lua
local grow_speed = 1 local grow_speed = 1
local node_pos = minetest.find_node_near(pos, 5, { "default:mese" }) local node_pos = minetest.find_node_near(pos, 5, { "default:mese" })
if node_pos then if node_pos then
minetest.chat_send_all("Node found at: " .. dump(node_pos)) minetest.chat_send_all("Node found at: " .. dump(node_pos))
grow_speed = 2 grow_speed = 2
end end
{% endhighlight %} ```
Let's say, for example, that the growth rate increases the more mese there is Let's say, for example, that the growth rate increases the more mese there is
nearby. You should then use a function which can find multiple nodes in area: nearby. You should then use a function which can find multiple nodes in area:
{% highlight lua %} ```lua
local pos1 = vector.subtract(pos, { x = 5, y = 5, z = 5 }) local pos1 = vector.subtract(pos, { x = 5, y = 5, z = 5 })
local pos2 = vector.add(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 pos_list = minetest.find_nodes_in_area(pos1, pos2, { "default:mese" })
local grow_speed = 1 + #pos_list local grow_speed = 1 + #pos_list
{% endhighlight %} ```
The above code doesn't quite do what we want, as it checks based on area, whereas 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, `find_node_near` checks based on range. In order to fix this we will,
unfortunately, need to manually check the range ourselves. unfortunately, need to manually check the range ourselves.
{% highlight lua %} ```lua
local pos1 = vector.subtract(pos, { x = 5, y = 5, z = 5 }) local pos1 = vector.subtract(pos, { x = 5, y = 5, z = 5 })
local pos2 = vector.add(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 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 grow_speed = grow_speed + 1
end end
end end
{% endhighlight %} ```
Now your code will correctly increase `grow_speed` based on mese nodes in range. Now your code will correctly increase `grow_speed` based on mese nodes in range.
Note how we compared the squared distance from the position, rather than square 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 lighting to be recalculated, which means that set_node is fairly slow for large
numbers of nodes. numbers of nodes.
{% highlight lua %} ```lua
minetest.set_node({ x = 1, y = 3, z = 4 }, { name = "default:mese" }) minetest.set_node({ x = 1, y = 3, z = 4 }, { name = "default:mese" })
local node = minetest.get_node({ x = 1, y = 3, z = 4 }) local node = minetest.get_node({ x = 1, y = 3, z = 4 })
print(node.name) --> default:mese print(node.name) --> default:mese
{% endhighlight %} ```
set_node will remove any associated metadata or inventory from that position. 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 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: 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" }) minetest.swap_node({ x = 1, y = 3, z = 4 }, { name = "default:mese" })
{% endhighlight %} ```
### Removing Nodes ### 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: The following two lines will both remove a node, and are both identical:
{% highlight lua %} ```lua
minetest.remove_node(pos) minetest.remove_node(pos)
minetest.set_node(pos, { name = "air" }) minetest.set_node(pos, { name = "air" })
{% endhighlight %} ```
In fact, remove_node will call set_node with name being air. 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 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. soon in the future, and the callback will be called each time.
{% highlight lua %} ```lua
-- Load a 20x20x20 area -- Load a 20x20x20 area
local halfsize = { x = 10, y = 10, z = 10 } local halfsize = { x = 10, y = 10, z = 10 }
local pos1 = vector.subtract(pos, halfsize) local pos1 = vector.subtract(pos, halfsize)
@ -181,12 +181,12 @@ local pos2 = vector.add (pos, halfsize)
local context = {} -- persist data between callback calls local context = {} -- persist data between callback calls
minetest.emerge_area(pos1, pos2, emerge_callback, context) minetest.emerge_area(pos1, pos2, emerge_callback, context)
{% endhighlight %} ```
Minetest will call `emerge_callback` whenever it loads a block, with some Minetest will call `emerge_callback` whenever it loads a block, with some
progress information. progress information.
{% highlight lua %} ```lua
local function emerge_callback(pos, action, num_calls_remaining, context) local function emerge_callback(pos, action, num_calls_remaining, context)
-- On first call, record number of blocks -- On first call, record number of blocks
if not context.total_blocks then 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) context.loaded_blocks, context.total_blocks, perc)
end end
end end
{% endhighlight %} ```
This is not the only way of loading blocks; Using a LVM will also cause the This is not the only way of loading blocks; Using a LVM will also cause the
encompassed blocks to be loaded synchronously. 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: You can use delete_blocks to delete a range of map blocks:
{% highlight lua %} ```lua
-- Delete a 20x20x20 area -- Delete a 20x20x20 area
local halfsize = { x = 10, y = 10, z = 10 } local halfsize = { x = 10, y = 10, z = 10 }
local pos1 = vector.subtract(pos, halfsize) local pos1 = vector.subtract(pos, halfsize)
local pos2 = vector.add (pos, halfsize) local pos2 = vector.add (pos, halfsize)
minetest.delete_area(pos1, pos2) minetest.delete_area(pos1, pos2)
{% endhighlight %} ```
This will delete all map blocks in that area, *inclusive*. This means that some 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 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 and maximum positions that you need to modify. Then you can create and read into
an LVM like so: an LVM like so:
{% highlight lua %} ```lua
local vm = minetest.get_voxel_manip() local vm = minetest.get_voxel_manip()
local emin, emax = vm:read_from_map(pos1, pos2) 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. 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`, 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 `get_data()` returns a flat array where each entry represents the type of a
particular node. particular node.
{% highlight lua %} ```lua
local data = vm:get_data() local data = vm:get_data()
{% endhighlight %} ```
You can get param2 and lighting data using the methods `get_light_data()` and `get_param2_data()`. 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 given by the above methods. There's a helper class called `VoxelArea` which handles
the calculation for you: the calculation for you:
{% highlight lua %} ```lua
local a = VoxelArea:new{ local a = VoxelArea:new{
MinEdge = emin, MinEdge = emin,
MaxEdge = emax MaxEdge = emax
@ -74,25 +74,25 @@ local idx = a:index(x, y, z)
-- Read node -- Read node
print(data[idx]) print(data[idx])
{% endhighlight %} ```
If you run that, you'll notice that `data[vi]` is an integer. This is because 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 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 is slow. Instead, the engine uses a content ID. You can find out the content
ID for a particular type of node like so: ID for a particular type of node like so:
{% highlight lua %} ```lua
local c_stone = minetest.get_content_id("default:stone") local c_stone = minetest.get_content_id("default:stone")
{% endhighlight %} ```
and then you can check whether a node is stone like so: and then you can check whether a node is stone like so:
{% highlight lua %} ```lua
local idx = a:index(x, y, z) local idx = a:index(x, y, z)
if data[idx] == c_stone then if data[idx] == c_stone then
print("is stone!") print("is stone!")
end end
{% endhighlight %} ```
It is recommended that you find out and store the content IDs of nodes types 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 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 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: always iterate in the order of `z, y, x` like so:
{% highlight lua %} ```lua
for z = min.z, max.z do for z = min.z, max.z do
for y = min.y, max.y do for y = min.y, max.y do
for x = min.x, max.x do for x = min.x, max.x do
@ -113,7 +113,7 @@ for z = min.z, max.z do
end end
end end
end end
{% endhighlight %} ```
The reason for this touches computer architecture. Reading from RAM is rather 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 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: First you need to set the new content ID in the data array:
{% highlight lua %} ```lua
for z = min.z, max.z do for z = min.z, max.z do
for y = min.y, max.y do for y = min.y, max.y do
for x = min.x, max.x do for x = min.x, max.x do
@ -139,15 +139,15 @@ for z = min.z, max.z do
end end
end end
end end
{% endhighlight %} ```
When you finished setting nodes in the LVM, you then need to upload the data When you finished setting nodes in the LVM, you then need to upload the data
array to the engine: array to the engine:
{% highlight lua %} ```lua
vm:set_data(data) vm:set_data(data)
vm:write_to_map(true) vm:write_to_map(true)
{% endhighlight %} ```
For setting lighting and param2 data, there are the appropriately named For setting lighting and param2 data, there are the appropriately named
`set_light_data()` and `set_param2_data()` methods. `set_light_data()` and `set_param2_data()` methods.
@ -158,7 +158,7 @@ date using `minetest.fix_light`.
## Example ## Example
{% highlight lua %} ```lua
-- Get content IDs during load time, and store into a local -- Get content IDs during load time, and store into a local
local c_dirt = minetest.get_content_id("default:dirt") local c_dirt = minetest.get_content_id("default:dirt")
local c_grass = minetest.get_content_id("default:dirt_with_grass") 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:set_data(data)
vm:write_to_map(true) vm:write_to_map(true)
end end
{% endhighlight %} ```
## Your Turn ## 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: If you know the position of a node, you can retrieve its metadata:
{% highlight lua %} ```lua
local meta = minetest.get_meta(pos) local meta = minetest.get_meta(pos)
-- where pos = { x = 1, y = 5, z = 7 } -- where pos = { x = 1, y = 5, z = 7 }
{% endhighlight %} ```
## Reading Metadata ## Reading Metadata
After retrieving the metadata, you can read its values: After retrieving the metadata, you can read its values:
{% highlight lua %} ```lua
local value = meta:get_string("key") local value = meta:get_string("key")
if value then if value then
@ -63,7 +63,7 @@ else
-- metadata of key "key" does not exist -- metadata of key "key" does not exist
print(value) print(value)
end end
{% endhighlight %} ```
The functions available include: 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`: 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")) local value = minetest.is_yes(meta:get_string("key"))
if value then if value then
@ -82,18 +82,18 @@ if value then
else else
print("is no") print("is no")
end end
{% endhighlight %} ```
## Setting Metadata ## Setting Metadata
You can set node metadata. For example: You can set node metadata. For example:
{% highlight lua %} ```lua
local value = "one" local value = "one"
meta:set_string("key", value) meta:set_string("key", value)
meta:set_string("foo", "bar") meta:set_string("foo", "bar")
{% endhighlight %} ```
This can be done using the following functions: 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`: You can convert to and from Lua tables using `to_table` and `from_table`:
{% highlight lua %} ```lua
local tmp = meta:to_table() local tmp = meta:to_table()
tmp.foo = "bar" tmp.foo = "bar"
meta:from_table(tmp) meta:from_table(tmp)
{% endhighlight %} ```
## Infotext ## 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 appear on mouse-over. This is used by furnaces to show progress and by signs
to show their text. For example: to show their text. For example:
{% highlight lua %} ```lua
meta:set_string("infotext", "Here is some text that will appear on mouse-over!") meta:set_string("infotext", "Here is some text that will appear on mouse-over!")
{% endhighlight %} ```
## Your Turn ## 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. `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 object = minetest.get_player_by_name("bob")
local pos = object:get_pos() local pos = object:get_pos()
object:set_pos({ x = pos.x, y = pos.y + 1, z = pos.z }) 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 `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`. smoothly animate an object to the new position, you should use `move_to`.
This, unfortunately, only works for entities. This, unfortunately, only works for entities.
{% highlight lua %} ```lua
object:move_to({ x = pos.x, y = pos.y + 1, z = pos.z }) 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. 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 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 You can change how an object looks, among other things, at any time by updating
its properties. its properties.
{% highlight lua %} ```lua
object:set_properties({ object:set_properties({
visual = "mesh", visual = "mesh",
mesh = "character.b3d", mesh = "character.b3d",
textures = {"character_texture.png"}, textures = {"character_texture.png"},
visual_size = {x=1, y=1}, visual_size = {x=1, y=1},
}) })
{% endhighlight %} ```
The updated properties will be sent to all players in range. 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 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. An Entity has a type table much like an item does.
This table can contain callback methods, default object properties, and custom elements. This table can contain callback methods, default object properties, and custom elements.
{% highlight lua %} ```lua
local MyEntity = { local MyEntity = {
initial_properties = { initial_properties = {
hp_max = 1, hp_max = 1,
@ -113,7 +113,7 @@ local MyEntity = {
function MyEntity:set_message(msg) function MyEntity:set_message(msg)
self.message = msg self.message = msg
end end
{% endhighlight %} ```
When an entity is emerged, a table is created for it by copying everything from When an entity is emerged, a table is created for it by copying everything from
its type table. 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: Both an ObjectRef and an entity table provide ways to get the counterpart:
{% highlight lua %} ```lua
local entity = object:get_luaentity() local entity = object:get_luaentity()
local object = entity.object local object = entity.object
print("entity is at " .. minetest.pos_to_string(object:get_pos())) print("entity is at " .. minetest.pos_to_string(object:get_pos()))
{% endhighlight %} ```
There are a number of available callbacks for use with entities. There are a number of available callbacks for use with entities.
A complete list can be found in [lua_api.txt]({{ page.root }}/lua_api.html#registered-entities) A complete list can be found in [lua_api.txt]({{ page.root }}/lua_api.html#registered-entities)
{% highlight lua %} ```lua
function MyEntity:on_step(dtime) function MyEntity:on_step(dtime)
local pos = self.object:get_pos() local pos = self.object:get_pos()
@ -151,7 +151,7 @@ end
function MyEntity:on_punch(hitter) function MyEntity:on_punch(hitter)
minetest.chat_send_player(hitter:get_player_name(), self.message) minetest.chat_send_player(hitter:get_player_name(), self.message)
end end
{% endhighlight %} ```
Now, if you were to spawn and use this entity, you'd notice that the message 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. 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 Staticdata is a string which contains all of the custom information that
needs to stored. needs to stored.
{% highlight lua %} ```lua
function MyEntity:get_staticdata() function MyEntity:get_staticdata()
return minetest.write_json({ return minetest.write_json({
message = self.message, message = self.message,
@ -174,7 +174,7 @@ function MyEntity:on_activate(staticdata, dtime_s)
self:set_message(data.message) self:set_message(data.message)
end end
end end
{% endhighlight %} ```
Minetest may call `get_staticdata()` as many times as it once and at any time. Minetest may call `get_staticdata()` as many times as it once and at any time.
This is because Minetest doesn't wait for a MapBlock to become inactive to save 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`. Finally, you need to register the type table using the aptly named `register_entity`.
{% highlight lua %} ```lua
minetest.register_entity("9_entities:myentity", MyEntity) minetest.register_entity("9_entities:myentity", MyEntity)
{% endhighlight %} ```
## Attachments ## 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. 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. An object can have an unlimited number of children, but at most one parent.
{% highlight lua %} ```lua
child:set_attach(parent, bone, position, rotation) child:set_attach(parent, bone, position, rotation)
{% endhighlight %} ```
An Object's `get_pos()` will always return the global position of the object, no An Object's `get_pos()` will always return the global position of the object, no
matter whether it is attached or not. 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. 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") minetest.chat_send_all("This is a chat message to all players")
{% endhighlight %} ```
Here is an example of how this appears in-game: 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: 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") 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 This message displays in the same manner as messages to all players, but is
only visible to the named player, in this case player1. only visible to the named player, in this case player1.
@ -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: To register a chat command, for example /foo, use register_chatcommand:
{% highlight lua %} ```lua
minetest.register_chatcommand("foo", { minetest.register_chatcommand("foo", {
privs = { privs = {
interact = true interact = true
@ -75,26 +75,26 @@ minetest.register_chatcommand("foo", {
return true, "You said " .. param .. "!" return true, "You said " .. param .. "!"
end end
}) })
{% endhighlight %} ```
Calling /foo bar will display `You said bar!` in the chat console. Calling /foo bar will display `You said bar!` in the chat console.
You can restrict which players are able to run commands: You can restrict which players are able to run commands:
{% highlight lua %} ```lua
privs = { privs = {
interact = true interact = true
}, },
{% endhighlight %} ```
This means only players with the `interact` [privilege](privileges.html) can run the 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 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 privilege they're missing. If the player has the necessary privileges, the command
will run and the message will be sent: will run and the message will be sent:
{% highlight lua %} ```lua
return true, "You said " .. param .. "!" return true, "You said " .. param .. "!"
{% endhighlight %} ```
This returns two values, a Boolean which shows the command succeeded This returns two values, a Boolean which shows the command succeeded
and the chat message to send to the player. 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). 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. Patterns are a way of extracting stuff from text using rules.
{% highlight lua %} ```lua
local to, msg = string.match(param, "^([%a%d_-]+) (*+)$") local to, msg = string.match(param, "^([%a%d_-]+) (*+)$")
{% endhighlight %} ```
The above implements `/msg <to> <message>`. Let's go through left to right: 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: To intercept a message, use register_on_chat_message:
{% highlight lua %} ```lua
minetest.register_on_chat_message(function(name, message) minetest.register_on_chat_message(function(name, message)
print(name .. " said " .. message) print(name .. " said " .. message)
return false return false
end) end)
{% endhighlight %} ```
By returning false, you allow the chat message to be sent by the default 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 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, You should make sure you take into account that it may be a chat command,
or the user may not have `shout`. or the user may not have `shout`.
{% highlight lua %} ```lua
minetest.register_on_chat_message(function(name, message) minetest.register_on_chat_message(function(name, message)
if message:sub(1, 1) == "/" then if message:sub(1, 1) == "/" then
print(name .. " ran chat command") print(name .. " ran chat command")
@ -175,4 +175,4 @@ minetest.register_on_chat_message(function(name, message)
return false return false
end) 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. Traditionally mods implemented these complex commands using Lua patterns.
{% highlight lua %} ```lua
local name = string.match(param, "^join ([%a%d_-]+)") local name = string.match(param, "^join ([%a%d_-]+)")
{% endhighlight %} ```
I however find Lua patterns annoying to write and unreadable. I however find Lua patterns annoying to write and unreadable.
Because of this, I created a library to do this for you. Because of this, I created a library to do this for you.
{% highlight lua %} ```lua
ChatCmdBuilder.new("sethp", function(cmd) ChatCmdBuilder.new("sethp", function(cmd)
cmd:sub(":target :hp:int", function(name, target, hp) cmd:sub(":target :hp:int", function(name, target, hp)
local player = minetest.get_player_by_name(target) local player = minetest.get_player_by_name(target)
@ -51,7 +51,7 @@ end, {
-- ^ probably better to register a custom priv -- ^ probably better to register a custom priv
} }
}) })
{% endhighlight %} ```
`ChatCmdBuilder.new(name, setup_func, def)` creates a new chat command called `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 `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 The first argument is the caller's name. The variables are then passed to the
function in order. function in order.
{% highlight lua %} ```lua
cmd:sub(":target :hp:int", function(name, target, hp) cmd:sub(":target :hp:int", function(name, target, hp)
-- subcommand function -- subcommand function
end) end)
{% endhighlight %} ```
## Installing ChatCmdBuilder ## 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>` - show report log
* `/admin log <username> <message>` - log to report log * `/admin log <username> <message>` - log to report log
{% highlight lua %} ```lua
local admin_log local admin_log
local function load() local function load()
admin_log = {} admin_log = {}
@ -179,4 +179,4 @@ end, {
ban = true ban = true
} }
}) })
{% endhighlight %} ```

View File

@ -110,7 +110,7 @@ This example shows a formspec to a player when they use the /formspec command.
</figcaption> </figcaption>
</figure> </figure>
{% highlight lua %} ```lua
-- Show form when the /formspec command is used. -- Show form when the /formspec command is used.
minetest.register_chatcommand("formspec", { minetest.register_chatcommand("formspec", {
func = function(name, param) func = function(name, param)
@ -121,20 +121,20 @@ minetest.register_chatcommand("formspec", {
"button_exit[1,2;2,1;exit;Save]") "button_exit[1,2;2,1;exit;Save]")
end end
}) })
{% endhighlight %} ```
Note: the .. is used to join two strings together. The following two lines are equivalent: Note: the .. is used to join two strings together. The following two lines are equivalent:
{% highlight lua %} ```lua
"foobar" "foobar"
"foo" .. "bar" "foo" .. "bar"
{% endhighlight %} ```
## Callbacks ## Callbacks
It's possible to expand the previous example with a callback: It's possible to expand the previous example with a callback:
{% highlight lua %} ```lua
-- Show form when the /formspec command is used. -- Show form when the /formspec command is used.
minetest.register_chatcommand("formspec", { minetest.register_chatcommand("formspec", {
func = function(name, param) func = function(name, param)
@ -161,7 +161,7 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
-- from receiving this submission. -- from receiving this submission.
return true return true
end) end)
{% endhighlight %} ```
The function given in minetest.register_on_player_receive_fields is called The function given in minetest.register_on_player_receive_fields is called
every time a user submits a form. Most callbacks will check the formname given 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 such as a check box. You can detect these cases by looking
for a clicked button. for a clicked button.
{% highlight lua %} ```lua
-- An example of what fields could contain, -- An example of what fields could contain,
-- using the above code -- using the above code
{ {
name = "Foo Bar", name = "Foo Bar",
exit = true exit = true
} }
{% endhighlight %} ```
## Contexts ## 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: For example, you might make a form to handle land protection information:
{% highlight lua %} ```lua
-- --
-- Step 1) set context when player requests the formspec -- 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.") "Something went wrong, try again.")
end end
end) end)
{% endhighlight %} ```
## Node Meta Formspecs ## 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 - 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. you don't need to wait for the server to send the player the chest formspec.
{% highlight lua %} ```lua
minetest.register_node("mymod:rightclick", { minetest.register_node("mymod:rightclick", {
description = "Rightclick me!", description = "Rightclick me!",
tiles = {"mymod_rightclick.png"}, tiles = {"mymod_rightclick.png"},
@ -286,7 +286,7 @@ minetest.register_node("mymod:rightclick", {
print(fields.x) print(fields.x)
end end
}) })
{% endhighlight %} ```
Formspecs set this way do not trigger the same callback. In order to Formspecs set this way do not trigger the same callback. In order to
receive form input for meta formspecs, you must include an 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: 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 player = minetest.get_player_by_name("username")
local idx = player:hud_add({ local idx = player:hud_add({
hud_elem_type = "text", hud_elem_type = "text",
@ -97,7 +97,7 @@ local idx = player:hud_add({
alignment = {x = 0, y = 0}, -- center aligned alignment = {x = 0, y = 0}, -- center aligned
scale = {x = 100, y = 100}, -- covered later scale = {x = 100, y = 100}, -- covered later
}) })
{% endhighlight %} ```
The `hud_add` function returns an element ID - this can be used later to modify The `hud_add` function returns an element ID - this can be used later to modify
or remove a HUD element. 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: Let's go ahead, and place all the text in our score panel:
{% highlight lua %} ```lua
player:hud_add({ player:hud_add({
hud_elem_type = "text", hud_elem_type = "text",
position = {x = 1, y = 0.5}, position = {x = 1, y = 0.5},
@ -152,7 +152,7 @@ player:hud_add({
scale = { x = 50, y = 10}, scale = { x = 50, y = 10},
number = 0xFFFFFF, number = 0xFFFFFF,
}) })
{% endhighlight %} ```
This results in the following: 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: Image elements are created in a very similar way to text elements:
{% highlight lua %} ```lua
player:hud_add({ player:hud_add({
hud_elem_type = "image", hud_elem_type = "image",
position = {x = 1, y = 0.5}, position = {x = 1, y = 0.5},
@ -176,7 +176,7 @@ player:hud_add({
scale = { x = 1, y = 1}, scale = { x = 1, y = 1},
alignment = { x = 1, y = 0 }, alignment = { x = 1, y = 0 },
}) })
{% endhighlight %} ```
You will now have this: 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: 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) local percent = tonumber(player:get_attribute("scoreboard:score") or 0.2)
player:hud_add({ player:hud_add({
@ -219,7 +219,7 @@ player:hud_add({
scale = { x = percent, y = 1}, scale = { x = percent, y = 1},
alignment = { x = 1, y = 0 }, alignment = { x = 1, y = 0 },
}) })
{% endhighlight %} ```
We now have a HUD that looks like the one in the first post! 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. 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. 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({ local idx = player:hud_add({
hud_elem_type = "text", hud_elem_type = "text",
text = "Hello world!", text = "Hello world!",
@ -237,7 +237,7 @@ local idx = player:hud_add({
player:hud_change(idx, "text", "New Text") player:hud_change(idx, "text", "New Text")
player:hud_remove(idx) player:hud_remove(idx)
{% endhighlight %} ```
The `hud_change` method takes the element ID, the property to change, and the new The `hud_change` method takes the element ID, the property to change, and the new
value. The above call changes the `text` property from "Hello World" to "Test". value. The above call changes the `text` property from "Hello World" to "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 This means that doing the `hud_change` immediately after the `hud_add` is
functionally equivalent to the following, in a rather inefficient way: functionally equivalent to the following, in a rather inefficient way:
{% highlight lua %} ```lua
local idx = player:hud_add({ local idx = player:hud_add({
hud_elem_type = "text", hud_elem_type = "text",
text = "New Text", text = "New Text",
}) })
{% endhighlight %} ```
## Storing IDs ## Storing IDs
{% highlight lua %} ```lua
scoreboard = {} scoreboard = {}
local saved_huds = {} local saved_huds = {}
@ -287,7 +287,7 @@ minetest.register_on_joinplayer(scoreboard.update_hud)
minetest.register_on_leaveplayer(function(player) minetest.register_on_leaveplayer(function(player)
saved_huds[player:get_player_name()] = nil saved_huds[player:get_player_name()] = nil
end) end)
{% endhighlight %} ```
## Other Elements ## Other Elements

View File

@ -23,7 +23,7 @@ gravity twice as strong.
Here is an example of how to add an antigravity command, which Here is an example of how to add an antigravity command, which
puts the caller in low G: puts the caller in low G:
{% highlight lua %} ```lua
minetest.register_chatcommand("antigravity", { minetest.register_chatcommand("antigravity", {
func = function(name, param) func = function(name, param)
local player = minetest.get_player_by_name(name) local player = minetest.get_player_by_name(name)
@ -33,7 +33,7 @@ minetest.register_chatcommand("antigravity", {
}) })
end end
}) })
{% endhighlight %} ```
## Available Overrides ## 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: Use `register_privilege` to declare a new privilege:
{% highlight lua %} ```lua
minetest.register_privilege("vote", { minetest.register_privilege("vote", {
description = "Can vote on issues", description = "Can vote on issues",
give_to_singleplayer = true give_to_singleplayer = true
}) })
{% endhighlight %} ```
If `give_to_singleplayer` is true, you can remove it, because true is the default 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: when it is not specified. This simplifies the privilege registration to:
{% highlight lua %} ```lua
minetest.register_privilege("vote", { minetest.register_privilege("vote", {
description = "Can vote on issues" description = "Can vote on issues"
}) })
{% endhighlight %} ```
## Checking for Privileges ## Checking for Privileges
To quickly check whether a player has all the required 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, { local has, missing = minetest.check_player_privs(player_or_name, {
interact = true, interact = true,
vote = true }) vote = true })
{% endhighlight %} ```
In this example, `has` is true if the player has all the privileges needed.\\ In this example, `has` is true if the player has all the privileges needed.\\
If `has` is false, then `missing` will contain a dictionary If `has` is false, then `missing` will contain a dictionary
of missing privileges. of missing privileges.
{% highlight lua %} ```lua
if minetest.check_player_privs(name, {interact=true, vote=true}) then if minetest.check_player_privs(name, {interact=true, vote=true}) then
print("Player has all privs!") print("Player has all privs!")
else else
@ -95,43 +95,43 @@ if has then
else else
print("Player is missing privs: " .. dump(missing)) print("Player is missing privs: " .. dump(missing))
end end
{% endhighlight %} ```
## Getting and Setting Privileges ## Getting and Setting Privileges
To get a table containing a player's privileges, regardless of whether To get a table containing a player's privileges, regardless of whether
the player is logged in, use `minetest.get_player_privs`: the player is logged in, use `minetest.get_player_privs`:
{% highlight lua %} ```lua
local privs = minetest.get_player_privs(name) local privs = minetest.get_player_privs(name)
print(dump(privs)) print(dump(privs))
{% endhighlight %} ```
This example may give: This example may give:
{% highlight lua %} ```lua
{ {
fly = true, fly = true,
interact = true, interact = true,
shout = true shout = true
} }
{% endhighlight %} ```
To set a player's privileges, use `minetest.set_player_privs`: To set a player's privileges, use `minetest.set_player_privs`:
{% highlight lua %} ```lua
minetest.set_player_privs(name, { minetest.set_player_privs(name, {
interact = true, interact = true,
shout = true }) shout = true })
{% endhighlight %} ```
To grant a player privileges, use a combination of the above two functions: To grant a player privileges, use a combination of the above two functions:
{% highlight lua %} ```lua
local privs = minetest.get_player_privs(name) local privs = minetest.get_player_privs(name)
privs.vote = true privs.vote = true
minetest.set_player_privs(name, privs) minetest.set_player_privs(name, privs)
{% endhighlight %} ```
## Adding Privileges to basic_privs ## 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` 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: function with the page's name, and its definition. Here is a minimal example:
{% highlight lua %} ```lua
sfinv.register_page("mymod:hello", { sfinv.register_page("mymod:hello", {
title = "Hello!", title = "Hello!",
get = function(self, player, context) get = function(self, player, context)
-- TODO: implement this -- TODO: implement this
end end
}) })
{% endhighlight %} ```
You can also override an existing page using `sfinv.override_page`. 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 as sfinv is expecting a response from the `get` method. So let's add a response
to fix that: to fix that:
{% highlight lua %} ```lua
sfinv.register_page("mymod:hello", { sfinv.register_page("mymod:hello", {
title = "Hello!", title = "Hello!",
get = function(self, player, context) get = function(self, player, context)
@ -50,7 +50,7 @@ sfinv.register_page("mymod:hello", {
"label[0.1,0.1;Hello world!]", true) "label[0.1,0.1;Hello world!]", true)
end end
}) })
{% endhighlight %} ```
The `make_formspec` function surrounds your formspec with sfinv's formspec code. The `make_formspec` function surrounds your formspec with sfinv's formspec code.
The fourth parameter, currently set as `true`, determines whether or not the 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 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. selecting them in a list and clicking a button.
{% highlight lua %} ```lua
sfinv.register_page("myadmin:myadmin", { sfinv.register_page("myadmin:myadmin", {
title = "Tab", title = "Tab",
get = function(self, player, context) get = function(self, player, context)
@ -103,7 +103,7 @@ sfinv.register_page("myadmin:myadmin", {
table.concat(formspec, ""), false) table.concat(formspec, ""), false)
end, end,
}) })
{% endhighlight %} ```
There's nothing new about the above code, all the concepts are covered above and There's nothing new about the above code, all the concepts are covered above and
in previous chapters. in previous chapters.
@ -120,11 +120,11 @@ in previous chapters.
You can receive formspec events by adding a `on_player_receive_fields` function You can receive formspec events by adding a `on_player_receive_fields` function
to a sfinv definition. to a sfinv definition.
{% highlight lua %} ```lua
on_player_receive_fields = function(self, player, context, fields) on_player_receive_fields = function(self, player, context, fields)
-- TODO: implement this -- TODO: implement this
end, end,
{% endhighlight %} ```
Fields is the exact same as the fields given to the subscribers of Fields is the exact same as the fields given to the subscribers of
`minetest.register_on_player_receive_fields`. The return value 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: 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) on_player_receive_fields = function(self, player, context, fields)
-- text list event, check event type and set index if selection changed -- text list event, check event type and set index if selection changed
if fields.playerlist then if fields.playerlist then
@ -163,7 +163,7 @@ on_player_receive_fields = function(self, player, context, fields)
end end
end end
end, end,
{% endhighlight %} ```
There's a rather large problem with this, however. Anyone can kick or ban players! You 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. 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 You can add an `is_in_nav` function to your page's definition if you'd like to
control when the page is shown: control when the page is shown:
{% highlight lua %} ```lua
is_in_nav = function(self, player, context) is_in_nav = function(self, player, context)
local privs = minetest.get_player_privs(player:get_player_name()) local privs = minetest.get_player_privs(player:get_player_name())
return privs.kick or privs.ban return privs.kick or privs.ban
end, end,
{% endhighlight %} ```
If you only need to check one priv or want to perform an and, you should use 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`. `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, 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: 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) local function on_grant_revoke(grantee, granter, priv)
if priv == "kick" or priv == "ban" then if priv == "kick" or priv == "ban" then
local player = minetest.get_player_by_name(grantee) 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_grant(on_grant_revoke)
minetest.register_on_priv_revoke(on_grant_revoke) minetest.register_on_priv_revoke(on_grant_revoke)
end end
{% endhighlight %} ```
## on_enter and on_leave callbacks ## 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 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! when a player joins the game even before they open their inventory!
{% highlight lua %} ```lua
on_enter = function(self, player, context) on_enter = function(self, player, context)
end, end,
@ -229,7 +229,7 @@ end,
on_leave = function(self, player, context) on_leave = function(self, player, context)
end, end,
{% endhighlight %} ```
## Adding to an existing page ## 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 means that the functions could run outside of Minetest - none of the engine's
functions are called. functions are called.
{% highlight lua %} ```lua
-- Data -- Data
function land.create(name, area_name) function land.create(name, area_name)
land.lands[area_name] = { land.lands[area_name] = {
@ -96,12 +96,12 @@ end
function land.get_by_name(area_name) function land.get_by_name(area_name)
return land.lands[area_name] return land.lands[area_name]
end end
{% endhighlight %} ```
Your actions should also be pure, however calling other functions is more Your actions should also be pure, however calling other functions is more
acceptable. acceptable.
{% highlight lua %} ```lua
-- Controller -- Controller
function land.handle_create_submit(name, area_name) function land.handle_create_submit(name, area_name)
-- process stuff (ie: check for overlaps, check quotas, check permissions) -- 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 -- This is a bad example, as explained later
land.show_create_formspec(name) land.show_create_formspec(name)
end end
{% endhighlight %} ```
Your event handlers will have to interact with the Minetest API. You should keep Your event handlers will have to interact with the Minetest API. You should keep
the amount of calculations to a minimum, as you won't be able to test this area the amount of calculations to a minimum, as you won't be able to test this area
very easily. very easily.
{% highlight lua %} ```lua
-- View -- View
function land.show_create_formspec(name) function land.show_create_formspec(name)
-- Note how there's no complex calculations here! -- 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) minetest.register_on_player_receive_fields(function(player, formname, fields)
land.handle_create_submit(player:get_player_name(), fields.area_name) land.handle_create_submit(player:get_player_name(), fields.area_name)
end) end)
{% endhighlight %} ```
The above is the Model-View-Controller pattern. The model is a collection of data 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 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 exposes a way for other areas of code to register their interest in an event
and receive data about the event. and receive data about the event.
{% highlight lua %} ```lua
mobs.registered_on_death = {} mobs.registered_on_death = {}
function mobs.register_on_death(func) function mobs.register_on_death(func)
table.insert(mobs.registered_on_death, func) table.insert(mobs.registered_on_death, func)
@ -227,11 +227,11 @@ end
for i=1, #mobs.registered_on_death do for i=1, #mobs.registered_on_death do
mobs.registered_on_death[i](entity, reason) mobs.registered_on_death[i](entity, reason)
end end
{% endhighlight %} ```
Then the other code registers its interest: Then the other code registers its interest:
{% highlight lua %} ```lua
-- awards -- awards
mobs.register_on_death(function(mob, reason) 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) awards.notify_mob_kill(reason.object, mob.name)
end end
end) end)
{% endhighlight %} ```
You may be thinking - wait a second, this looks awfully familiar. And you're right! 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 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: Don't do this:
{% highlight lua %} ```lua
minetest.register_on_joinplayer(function(player) minetest.register_on_joinplayer(function(player)
local function func() local function func()
local pos = player:get_pos() -- BAD! local pos = player:get_pos() -- BAD!
@ -36,11 +36,11 @@ minetest.register_on_joinplayer(function(player)
-- It's recommended to just not do this -- It's recommended to just not do this
-- use minetest.get_connected_players() and minetest.get_player_by_name() instead. -- use minetest.get_connected_players() and minetest.get_player_by_name() instead.
end) end)
{% endhighlight %} ```
instead, do this: instead, do this:
{% highlight lua %} ```lua
minetest.register_on_joinplayer(function(player) minetest.register_on_joinplayer(function(player)
local function func(name) local function func(name)
-- Attempt to get the ref again -- Attempt to get the ref again
@ -56,7 +56,7 @@ minetest.register_on_joinplayer(function(player)
-- Pass the name into the function -- Pass the name into the function
minetest.after(1, func, player:get_player_name()) minetest.after(1, func, player:get_player_name())
end) end)
{% endhighlight %} ```
## Don't Trust Formspec Submissions ## Don't Trust Formspec Submissions
@ -66,7 +66,7 @@ they like.
The following code has a vulnerability where any player can give The following code has a vulnerability where any player can give
themselves moderator privileges: themselves moderator privileges:
{% highlight lua %} ```lua
local function show_formspec(name) local function show_formspec(name)
if not minetest.check_player_privs(name, { privs = true }) then if not minetest.check_player_privs(name, { privs = true }) then
return false return false
@ -89,11 +89,11 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
minetest.set_player_privs(fields.target, privs) minetest.set_player_privs(fields.target, privs)
return true return true
end) end)
{% endhighlight %} ```
Instead, do this: Instead, do this:
{% highlight lua %} ```lua
minetest.register_on_player_receive_fields(function(player, formname, fields) minetest.register_on_player_receive_fields(function(player, formname, fields)
if not minetest.check_player_privs(name, { privs = true }) then if not minetest.check_player_privs(name, { privs = true }) then
return false return false
@ -101,7 +101,7 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
-- code -- code
end) end)
{% endhighlight %} ```
## Set ItemStacks After Changing Them ## 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: Don't do this:
{% highlight lua %} ```lua
local inv = player:get_inventory() local inv = player:get_inventory()
local stack = inv:get_stack("main", 1) local stack = inv:get_stack("main", 1)
stack:get_meta():set_string("description", "Partially eaten") stack:get_meta():set_string("description", "Partially eaten")
-- BAD! Modification will be lost -- BAD! Modification will be lost
{% endhighlight %} ```
Do this: Do this:
{% highlight lua %} ```lua
local inv = player:get_inventory() local inv = player:get_inventory()
local stack = inv:get_stack("main", 1) local stack = inv:get_stack("main", 1)
stack:get_meta():set_string("description", "Partially eaten") stack:get_meta():set_string("description", "Partially eaten")
inv:set_stack("main", 1, stack) inv:set_stack("main", 1, stack)
-- Correct! Item stack is set -- Correct! Item stack is set
{% endhighlight %} ```
The behaviour of callbacks is slightly more complicated. Modifying an itemstack you 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, 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: Avoid this:
{% highlight lua %} ```lua
minetest.register_on_item_eat(function(hp_change, replace_with_item, itemstack, user, pointed_thing) minetest.register_on_item_eat(function(hp_change, replace_with_item, itemstack, user, pointed_thing)
itemstack:get_meta():set_string("description", "Partially eaten") itemstack:get_meta():set_string("description", "Partially eaten")
-- Almost correct! Data will be lost if another callback cancels the behaviour -- Almost correct! Data will be lost if another callback cancels the behaviour
end) end)
{% endhighlight %} ```
If no callbacks cancel, then the stack will be set and the description will be updated. 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: 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) minetest.register_on_item_eat(function(hp_change, replace_with_item, itemstack, user, pointed_thing)
itemstack:get_meta():set_string("description", "Partially eaten") itemstack:get_meta():set_string("description", "Partially eaten")
user:get_inventory():set_stack("main", user:get_wield_index(), itemstack) user:get_inventory():set_stack("main", user:get_wield_index(), itemstack)
-- Correct, description will always be set! -- Correct, description will always be set!
end) end)
{% endhighlight %} ```
If the callbacks cancel or the callback runner doesn't set the stack, If the callbacks cancel or the callback runner doesn't set the stack,
then our update will still be set. 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: Put the following contents in it:
{% highlight lua %} ```lua
unused_args = false unused_args = false
allow_defined_top = true allow_defined_top = true
@ -78,7 +78,7 @@ read_globals = {
-- MTG -- MTG
"default", "sfinv", "creative", "default", "sfinv", "creative",
} }
{% endhighlight %} ```
Next you'll need to test that it works by running LuaCheck. You should get a lot 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 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: Next, create a file called .travis.yml with the following content:
{% highlight yml %} ```yml
language: generic language: generic
sudo: false sudo: false
addons: addons:
@ -134,14 +134,14 @@ script:
- $HOME/.luarocks/bin/luacheck --no-color . - $HOME/.luarocks/bin/luacheck --no-color .
notifications: notifications:
email: false email: false
{% endhighlight %} ```
If your project is a game rather than a mod or mod pack, If your project is a game rather than a mod or mod pack,
change the line after `script:` to: change the line after `script:` to:
{% highlight yml %} ```yml
- $HOME/.luarocks/bin/luacheck --no-color mods/ - $HOME/.luarocks/bin/luacheck --no-color mods/
{% endhighlight %} ```
Now commit and push to Github. Go to your project's page on Github, and click 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. 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: Here's some real code found in a mod:
{% highlight lua %} ```lua
minetest.register_on_player_receive_fields(function(player, formname, fields) minetest.register_on_player_receive_fields(function(player, formname, fields)
-- Todo: fix security issue here -- Todo: fix security issue here
local name = player:get_player_name() local name = player:get_player_name()
@ -56,7 +56,7 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
end end
end end
end end
{% endhighlight %} ```
Can you spot the issue? A malicious user could submit a formspec containing 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. 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? Can you spot the vulnerability in the following?
{% highlight lua %} ```lua
local ie = minetest.request_insecure_environment() local ie = minetest.request_insecure_environment()
ie.os.execute(("path/to/prog %d"):format(3)) ie.os.execute(("path/to/prog %d"):format(3))
{% endhighlight %} ```
`String.format` is a function in the global shared table `String`. `String.format` is a function in the global shared table `String`.
A malicious mod could override this function and pass stuff to os.execute: A malicious mod could override this function and pass stuff to os.execute:
{% highlight lua %} ```lua
String.format = function() String.format = function()
return "xdg-open 'http://example.com'" return "xdg-open 'http://example.com'"
end end
{% endhighlight %} ```
The mod could pass something a lot more malicious than opening a website, such The mod could pass something a lot more malicious than opening a website, such
as giving a remote user control over the machine. 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 ### init.lua
{% highlight lua %} ```lua
mymod = {} mymod = {}
dofile(minetest.get_modpath("mymod") .. "/api.lua") dofile(minetest.get_modpath("mymod") .. "/api.lua")
{% endhighlight %} ```
### api.lua ### api.lua
{% highlight lua %} ```lua
function mymod.add(x, y) function mymod.add(x, y)
return x + y return x + y
end end
{% endhighlight %} ```
### tests/api_spec.lua ### tests/api_spec.lua
{% highlight lua %} ```lua
-- Look for required things in -- Look for required things in
package.path = "../?.lua;" .. package.path package.path = "../?.lua;" .. package.path
@ -87,7 +87,7 @@ describe("add", function()
assert.equals(-2, mymod.add(-1, -1)) assert.equals(-2, mymod.add(-1, -1))
end) end)
end) end)
{% endhighlight %} ```
You can now run the tests by opening a terminal in the mod's directory and You can now run the tests by opening a terminal in the mod's directory and
running `busted .` 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 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. little harder as you may have to mock the Minetest API.
{% highlight lua %} ```lua
-- As above, make a table -- As above, make a table
_G.minetest = {} _G.minetest = {}
@ -157,7 +157,7 @@ describe("list_areas", function()
assert.same(expected, chat_send_all_calls) assert.same(expected, chat_send_all_calls)
end) end)
end) end)
{% endhighlight %} ```
## Checking Commits with Travis ## Checking Commits with Travis
@ -165,7 +165,7 @@ end)
The Travis script from the [Error Checking](luacheck.html) The Travis script from the [Error Checking](luacheck.html)
chapter can be modified to also run Busted. chapter can be modified to also run Busted.
{% highlight yml %} ```yml
language: generic language: generic
sudo: false sudo: false
addons: addons:
@ -179,7 +179,7 @@ script:
- $HOME/.luarocks/bin/busted . - $HOME/.luarocks/bin/busted .
notifications: notifications:
email: false email: false
{% endhighlight %} ```
## Conclusion ## Conclusion