Use GH-markdown code highlighting instead of Liquid block
This commit is contained in:
parent
c0045d484a
commit
386febe45d
@ -67,9 +67,9 @@ Explain why/how these concepts are useful in modding
|
||||
|
||||
Paragraphs
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
code
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
## Parts in
|
||||
|
||||
|
@ -113,7 +113,7 @@ Are you confused? Don't worry, here is an example which puts all of this togethe
|
||||
default
|
||||
|
||||
### init.lua
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
print("This file will be run at load time!")
|
||||
|
||||
minetest.register_node("mymod:node", {
|
||||
@ -128,7 +128,7 @@ minetest.register_node("mymod:node", {
|
||||
},
|
||||
groups = {cracky = 1}
|
||||
})
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
### mod.conf
|
||||
name = mymod
|
||||
|
@ -30,7 +30,7 @@ A code editor with code highlighting is sufficient for writing scripts in Lua.
|
||||
Code highlighting gives different colours to different words and characters
|
||||
depending on what they mean. This allows you to spot mistakes.
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
function ctf.post(team,msg)
|
||||
if not ctf.team(team) then
|
||||
return false
|
||||
@ -44,7 +44,7 @@ function ctf.post(team,msg)
|
||||
|
||||
return true
|
||||
end
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
For example, keywords in the above snippet are highlighted such as if, then, end, return.
|
||||
table.insert is a function which comes with Lua by default.
|
||||
@ -88,13 +88,13 @@ There are three main types of flow:
|
||||
|
||||
So, what do statements in Lua look like?
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
local a = 2 -- Set 'a' to 2
|
||||
local b = 2 -- Set 'b' to 2
|
||||
local result = a + b -- Set 'result' to a + b, which is 4
|
||||
a = a + 10
|
||||
print("Sum is "..result)
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
Whoa, what happened there?
|
||||
|
||||
@ -140,14 +140,14 @@ Not an exhaustive list. Doesn't contain every possible operator.
|
||||
|
||||
The most basic selection is the if statement. It looks like this:
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
local random_number = math.random(1, 100) -- Between 1 and 100.
|
||||
if random_number > 50 then
|
||||
print("Woohoo!")
|
||||
else
|
||||
print("No!")
|
||||
end
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
That example generates a random number between 1 and 100. It then prints
|
||||
"Woohoo!" if that number is bigger than 50, otherwise it prints "No!".
|
||||
@ -169,24 +169,24 @@ What else can you get apart from '>'?
|
||||
|
||||
That doesn't contain every possible operator, and you can combine operators like this:
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
if not A and B then
|
||||
print("Yay!")
|
||||
end
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
Which prints "Yay!" if A is false and B is true.
|
||||
|
||||
Logical and arithmetic operators work exactly the same, they both accept inputs
|
||||
and return a value which can be stored.
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
local A = 5
|
||||
local is_equal = (A == 5)
|
||||
if is_equal then
|
||||
print("Is equal!")
|
||||
end
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
## Programming
|
||||
|
||||
@ -208,7 +208,7 @@ however, the following websites are quite useful in developing this:
|
||||
Whether a variable is local or global determines where it can be written to or read to.
|
||||
A local variable is only accessible from where it is defined. Here are some examples:
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
-- Accessible from within this script file
|
||||
local one = 1
|
||||
|
||||
@ -221,11 +221,11 @@ function myfunc()
|
||||
local three = one + two
|
||||
end
|
||||
end
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
Whereas global variables can be accessed from anywhere in the script file, and from any other mod.
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
my_global_variable = "blah"
|
||||
|
||||
function one()
|
||||
@ -235,7 +235,7 @@ end
|
||||
print(my_global_variable) -- Output: "blah"
|
||||
one()
|
||||
print(my_global_variable) -- Output: "three"
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
|
||||
### Locals should be used as much as possible
|
||||
@ -243,7 +243,7 @@ print(my_global_variable) -- Output: "three"
|
||||
Lua is global by default (unlike most other programming languages).
|
||||
Local variables must be identified as such.
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
function one()
|
||||
foo = "bar"
|
||||
end
|
||||
@ -254,7 +254,7 @@ end
|
||||
|
||||
one()
|
||||
two()
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
dump() is a function that can turn any variable into a string so the programmer can
|
||||
see what it is. The foo variable will be printed as "bar", including the quotes
|
||||
@ -266,7 +266,7 @@ This is sloppy coding, and Minetest will in fact warn about this:
|
||||
|
||||
To correct this, use "local":
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
function one()
|
||||
local foo = "bar"
|
||||
end
|
||||
@ -277,7 +277,7 @@ end
|
||||
|
||||
one()
|
||||
two()
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
Remember that nil means **not initialised**.
|
||||
The variable hasn't been assigned a value yet,
|
||||
@ -286,15 +286,15 @@ doesn't exist, or has been uninitialised (ie: set to nil).
|
||||
The same goes for functions. Functions are variables of a special type, and
|
||||
should be made local, as other mods could have functions of the same name.
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
local function foo(bar)
|
||||
return bar * 2
|
||||
end
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
API tables should be used to allow other mods to call the functions, like so:
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
mymod = {}
|
||||
|
||||
function mymod.foo(bar)
|
||||
@ -303,27 +303,27 @@ end
|
||||
|
||||
-- In another mod, or script:
|
||||
mymod.foo("foobar")
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
## Including other Lua Scripts
|
||||
|
||||
The recommended way to include other Lua scripts in a mod is to use *dofile*.
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
dofile(minetest.get_modpath("modname") .. "/script.lua")
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
"local" variables declared outside of any functions in a script file will be local to that script.
|
||||
A script can return a value, which is useful for sharing private locals:
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
-- script.lua
|
||||
return "Hello world!"
|
||||
|
||||
-- init.lua
|
||||
local ret = dofile(minetest.get_modpath("modname") .. "/script.lua")
|
||||
print(ret) -- Hello world!
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
Later chapters will discuss how to split up the code of a mod in a lot of detail.
|
||||
However, the simplistic approach for now is to have different files for different
|
||||
|
@ -69,9 +69,9 @@ Press i in game to see your player inventory.
|
||||
|
||||
Use a player's name to get their inventory:
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
local inv = minetest.get_inventory({type="player", name="celeron55"})
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
### Node Inventories
|
||||
|
||||
@ -80,9 +80,9 @@ The node must be loaded, because it is stored in [node metadata](node_metadata.h
|
||||
|
||||
Use its position to get a node inventory:
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
local inv = minetest.get_inventory({type="node", pos={x=, y=, z=}})
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
### Detached Inventories
|
||||
|
||||
@ -93,15 +93,15 @@ A detached inventory would also allow multiple chests to share the same inventor
|
||||
|
||||
Use the inventory name to get a detached inventory:
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
local inv = minetest.get_inventory({type="detached", name="inventory_name"})
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
You can create your own detached inventories:
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
minetest.create_detached_inventory("inventory_name", callbacks)
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
This creates a detached inventory or, if the inventory already exists, it is cleared.
|
||||
You can also supply a [table of callbacks]({{ page.root }}/lua_api.html#detached-inventory-callbacks).
|
||||
@ -112,9 +112,9 @@ You can also supply a [table of callbacks]({{ page.root }}/lua_api.html#detached
|
||||
|
||||
You can check where an inventory is located:
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
local location = inv:get_location()
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
This will return a table like the one passed to `minetest.get_inventory()`.
|
||||
|
||||
@ -125,7 +125,7 @@ If the location is unknown, `{type="undefined"}` is returned.
|
||||
Inventory lists have a size, for example `main` has size of 32 slots by default.
|
||||
They also have a width, which is used to divide them into a grid.
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
if inv:set_size("main", 32) then
|
||||
inv:set_width("main", 8)
|
||||
print("size: " .. inv.get_size("main"))
|
||||
@ -133,7 +133,7 @@ if inv:set_size("main", 32) then
|
||||
else
|
||||
print("Error!")
|
||||
end
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
<!--The width and height of an inventory in a [formspec](formspecs.html) is
|
||||
determined by the formspec element, not by the inventory. By that I mean
|
||||
@ -143,23 +143,23 @@ a list doesn't have a width or height, only the maximum number of stacks/slots.-
|
||||
|
||||
You can use `list_is_empty` to check if a list is empty:
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
if inv:is_empty("main") then
|
||||
print("The list is empty!")
|
||||
end
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
### Lua Tables
|
||||
|
||||
You can convert an inventory to a Lua table:
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
local lists = inv:get_lists()
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
The table will be in this form:
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
{
|
||||
list_one = {
|
||||
ItemStack,
|
||||
@ -176,13 +176,13 @@ The table will be in this form:
|
||||
-- inv:get_size("list_two") elements
|
||||
}
|
||||
}
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
You can then set the inventory:
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
inv:set_lists(lists)
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
Please note that the sizes of lists will not change.
|
||||
|
||||
@ -190,13 +190,13 @@ Please note that the sizes of lists will not change.
|
||||
|
||||
You can do the above for individual lists:
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
local list = inv:get_list("list_one")
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
It will be in this form:
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
{
|
||||
ItemStack,
|
||||
ItemStack,
|
||||
@ -204,13 +204,13 @@ It will be in this form:
|
||||
ItemStack,
|
||||
-- inv:get_size("list_one") elements
|
||||
}
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
You can then set the list:
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
inv:set_list("list_one", list)
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
Please note that the sizes of lists will not change.
|
||||
|
||||
@ -220,42 +220,42 @@ Please note that the sizes of lists will not change.
|
||||
|
||||
To add items to a list named `"main"`:
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
local stack = ItemStack("default:stone 99")
|
||||
local leftover = inv:add_item("main", stack)
|
||||
if leftover:get_count() > 0 then
|
||||
print("Inventory is full! " .. leftover:get_count() .. " items weren't added")
|
||||
end
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
### Checking for Room
|
||||
|
||||
To check whether a list has room for items:
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
if not inv:room_for_item("main", stack) then
|
||||
print("Not enough room!")
|
||||
end
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
### Taking Items
|
||||
|
||||
To remove items from a list:
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
local taken = inv:remove_item("main", stack)
|
||||
print("Took " .. taken:get_count())
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
### Checking Inventory Contents
|
||||
|
||||
To check whether an inventory contains a specific quantity of an item:
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
if not inv:contains_item(listname, stack) then
|
||||
print("Item not in inventory!")
|
||||
end
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
This works if the item count is split up over multiple stacks.
|
||||
For example checking for "default:stone 200" will work if there
|
||||
@ -265,7 +265,7 @@ are stacks of 99 + 95 + 6.
|
||||
|
||||
You can manipulate individual stacks:
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
local stack = inv:get_stack(listname, 0)
|
||||
inv:set_stack(listname, 0, stack)
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
@ -25,31 +25,31 @@ It's basically just an item type with a count of items in the stack.
|
||||
|
||||
You can create a stack like so:
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
local items = ItemStack("default:dirt")
|
||||
local items = ItemStack("default:stone 99")
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
You could alternatively create a blank ItemStack and fill it using methods:
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
local items = ItemStack()
|
||||
if items:set_name("default:dirt") then
|
||||
items:set_count(99)
|
||||
else
|
||||
print("An error occured!")
|
||||
end
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
And you can copy stacks like this:
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
local items2 = ItemStack(items)
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
## Name and Count
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
local items = ItemStack("default:stone")
|
||||
print(items:get_name()) -- default:stone
|
||||
print(items:get_count()) -- 1
|
||||
@ -64,7 +64,7 @@ if items:set_name("default:dirt") then
|
||||
else
|
||||
error("This shouldn't happen")
|
||||
end
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
## Adding and Taking Items
|
||||
|
||||
@ -73,26 +73,26 @@ end
|
||||
Use `add_item` to add items to an ItemStack.
|
||||
An ItemStack of the items that could not be added is returned.
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
local items = ItemStack("default:stone 50")
|
||||
local to_add = ItemStack("default:stone 100")
|
||||
local leftovers = items:add_item(to_add)
|
||||
|
||||
print("Could not add" .. leftovers:get_count() .. " of the items.")
|
||||
-- ^ will be 51
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
## Taking
|
||||
|
||||
The following code takes **up to** 100.
|
||||
If there aren't enough items in the stack, it will take as much as it can.
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
local taken = items:take_item(100)
|
||||
-- taken is the ItemStack taken from the main ItemStack
|
||||
|
||||
print("Took " .. taken:get_count() .. " items")
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
## Wear
|
||||
|
||||
@ -101,36 +101,36 @@ the more wear.
|
||||
|
||||
You use `add_wear()`, `get_wear()` and `set_wear(wear)`.
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
local items = ItemStack("default:dirt")
|
||||
local max_uses = 10
|
||||
|
||||
-- This is done automatically when you use a tool that digs things
|
||||
-- It increases the wear of an item by one use.
|
||||
items:add_wear(65535 / (max_uses - 1))
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
When digging a node, the amount of wear a tool gets may depends on the node
|
||||
being dug. So max_uses varies depending on what is being dug.
|
||||
|
||||
## Lua Tables
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
local data = items:to_table()
|
||||
local items2 = ItemStack(data)
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
## Metadata
|
||||
|
||||
ItemStacks can have metadata, and use the same API as [Node Metadata](node_metadata.html).
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
local meta = items:get_meta()
|
||||
meta:set_string("foo", meta:get_string("foo") .. " ha!")
|
||||
|
||||
print(dump(meta:get_string("foo")))
|
||||
-- if ran multiple times, would give " ha! ha! ha!"
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
## More Methods
|
||||
|
||||
|
@ -54,7 +54,7 @@ Nodes that use this will be cubes with textures for each side, simple-as.\\
|
||||
Here is the example from the [Nodes, Items and Crafting](nodes_items_crafting.html#registering-a-basic-node) chapter.
|
||||
Notice how you don't need to declare the drawtype.
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
minetest.register_node("mymod:diamond", {
|
||||
description = "Alien Diamond",
|
||||
tiles = {
|
||||
@ -69,14 +69,14 @@ minetest.register_node("mymod:diamond", {
|
||||
groups = {cracky = 3},
|
||||
drop = "mymod:diamond_fragments"
|
||||
})
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
|
||||
## Airlike
|
||||
|
||||
These nodes are see through and thus have no textures.
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
minetest.register_node("myair:air", {
|
||||
description = "MyAir (you hacker you!)",
|
||||
drawtype = "airlike",
|
||||
@ -97,7 +97,7 @@ minetest.register_node("myair:air", {
|
||||
drop = "",
|
||||
groups = {not_in_creative_inventory=1}
|
||||
})
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
## Liquid
|
||||
|
||||
@ -112,7 +112,7 @@ These nodes are complete liquid nodes, the liquid flows outwards from position
|
||||
using the flowing liquid drawtype.
|
||||
For each liquid node you should also have a flowing liquid node.
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
-- Some properties have been removed as they are beyond
|
||||
-- the scope of this chapter.
|
||||
minetest.register_node("default:water_source", {
|
||||
@ -175,7 +175,7 @@ minetest.register_node("default:water_source", {
|
||||
post_effect_color = {a=64, r=100, g=100, b=200},
|
||||
-- ^ color of screen when the player is submerged
|
||||
})
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
### FlowingLiquid
|
||||
|
||||
@ -201,7 +201,7 @@ edges are hidden, like this:
|
||||
</figcaption>
|
||||
</figure>
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
minetest.register_node("default:obsidian_glass", {
|
||||
description = "Obsidian Glass",
|
||||
drawtype = "glasslike",
|
||||
@ -212,7 +212,7 @@ minetest.register_node("default:obsidian_glass", {
|
||||
sounds = default.node_sound_glass_defaults(),
|
||||
groups = {cracky=3,oddly_breakable_by_hand=3},
|
||||
})
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
## Glasslike_Framed
|
||||
|
||||
@ -226,7 +226,7 @@ than individual nodes, like the following:
|
||||
</figcaption>
|
||||
</figure>
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
minetest.register_node("default:glass", {
|
||||
description = "Glass",
|
||||
drawtype = "glasslike_framed",
|
||||
@ -241,7 +241,7 @@ minetest.register_node("default:glass", {
|
||||
groups = {cracky = 3, oddly_breakable_by_hand = 3},
|
||||
sounds = default.node_sound_glass_defaults()
|
||||
})
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
### Glasslike_Framed_Optional
|
||||
|
||||
@ -260,13 +260,13 @@ Allfaces are nodes which show all of their faces, even if they're against
|
||||
another node. This is mainly used by leaves as you don't want a gaping space when
|
||||
looking through the transparent holes, but instead a nice leaves effect.
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
minetest.register_node("default:leaves", {
|
||||
description = "Leaves",
|
||||
drawtype = "allfaces_optional",
|
||||
tiles = {"default_leaves.png"}
|
||||
})
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
### Allfaces_Optional
|
||||
|
||||
@ -281,7 +281,7 @@ depending on whether they are placed against a wall, on the floor, or on the cei
|
||||
TorchLike nodes are not restricted to torches, you could use them for switches or other
|
||||
items which need to have different textures depending on where they are placed.
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
minetest.register_node("foobar:torch", {
|
||||
description = "Foobar Torch",
|
||||
drawtype = "torchlike",
|
||||
@ -306,7 +306,7 @@ minetest.register_node("foobar:torch", {
|
||||
wall_side = {-0.5, -0.3, -0.1, -0.5+0.3, 0.3, 0.1},
|
||||
}
|
||||
})
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
## Nodebox
|
||||
|
||||
@ -320,7 +320,7 @@ minetest.register_node("foobar:torch", {
|
||||
Nodeboxes allow you to create a node which is not cubic, but is instead made out
|
||||
of as many cuboids as you like.
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
minetest.register_node("stairs:stair_stone", {
|
||||
drawtype = "nodebox",
|
||||
paramtype = "light",
|
||||
@ -332,14 +332,14 @@ minetest.register_node("stairs:stair_stone", {
|
||||
},
|
||||
}
|
||||
})
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
The most important part is the nodebox table:
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
{-0.5, -0.5, -0.5, 0.5, 0, 0.5},
|
||||
{-0.5, 0, 0, 0.5, 0.5, 0.5}
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
Each row is a cuboid which are joined to make a single node.
|
||||
The first three numbers are the co-ordinates, from -0.5 to 0.5 inclusive, of
|
||||
@ -356,7 +356,7 @@ create node boxes by dragging the edges, it is more visual than doing it by hand
|
||||
|
||||
Sometimes you want different nodeboxes for when it is placed on the floor, wall, or ceiling like with torches.
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
minetest.register_node("default:sign_wall", {
|
||||
drawtype = "nodebox",
|
||||
node_box = {
|
||||
@ -378,7 +378,7 @@ minetest.register_node("default:sign_wall", {
|
||||
}
|
||||
},
|
||||
})
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
## Mesh
|
||||
|
||||
@ -392,7 +392,7 @@ invisible but still rendered.
|
||||
|
||||
You can register a mesh node as so:
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
minetest.register_node("mymod:meshy", {
|
||||
drawtype = "mesh",
|
||||
|
||||
@ -404,7 +404,7 @@ minetest.register_node("mymod:meshy", {
|
||||
-- Path to the mesh
|
||||
mesh = "mymod_meshy.b3d",
|
||||
})
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
Make sure that the mesh is available in a `models` directory.
|
||||
Most of the time the mesh should be in your mod's folder, however it's okay to
|
||||
@ -420,7 +420,7 @@ Despite the name of this drawtype, signs don't actually tend to use signlike but
|
||||
instead use the `nodebox` drawtype to provide a 3D effect. The `signlike` drawtype
|
||||
is, however, commonly used by ladders.
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
minetest.register_node("default:ladder_wood", {
|
||||
drawtype = "signlike",
|
||||
|
||||
@ -433,7 +433,7 @@ minetest.register_node("default:ladder_wood", {
|
||||
type = "wallmounted",
|
||||
},
|
||||
})
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
## Plantlike
|
||||
|
||||
@ -446,7 +446,7 @@ minetest.register_node("default:ladder_wood", {
|
||||
|
||||
Plantlike nodes draw their tiles in an X like pattern.
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
minetest.register_node("default:papyrus", {
|
||||
drawtype = "plantlike",
|
||||
|
||||
@ -458,7 +458,7 @@ minetest.register_node("default:papyrus", {
|
||||
fixed = {-6 / 16, -0.5, -6 / 16, 6 / 16, 0.5, 6 / 16},
|
||||
},
|
||||
})
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
## Firelike
|
||||
|
||||
@ -472,11 +472,11 @@ and ceilings.
|
||||
</figcaption>
|
||||
</figure>
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
minetest.register_node("mymod:clingere", {
|
||||
drawtype = "firelike",
|
||||
|
||||
-- Only one texture used
|
||||
tiles = { "mymod:clinger" },
|
||||
})
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
@ -46,12 +46,12 @@ item, as the distinction between them is rather artificial.
|
||||
Item definitions consist of an *item name* and a *definition table*.
|
||||
The definition table contains attributes which affect the behaviour of the item.
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
minetest.register_craftitem("modname:itemname", {
|
||||
description = "My Special Item",
|
||||
inventory_image = "modname_itemname.png"
|
||||
})
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
### Item Names and Aliases
|
||||
|
||||
@ -80,17 +80,17 @@ Registering an alias is pretty simple.
|
||||
A good way to remember is `from → to` where *from* is the alias and *to*
|
||||
is the target.
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
minetest.register_alias("dirt", "default:dirt")
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
Mods need to make sure to resolve aliases before dealing directly with item names,
|
||||
as the engine won't do this.
|
||||
This is pretty simple though:
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
itemname = minetest.registered_aliases[itemname] or itemname
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
### Textures
|
||||
|
||||
@ -108,14 +108,14 @@ resulting in lowered performance.
|
||||
|
||||
## Registering a basic node
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
minetest.register_node("mymod:diamond", {
|
||||
description = "Alien Diamond",
|
||||
tiles = {"mymod_diamond.png"},
|
||||
is_ground_content = true,
|
||||
groups = {cracky=3, stone=1}
|
||||
})
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
The `tiles` property is a table of texture names the node will use.
|
||||
When there is only one texture, this texture is used on every side.
|
||||
@ -126,7 +126,7 @@ To give a different texture per-side, supply the names of 6 textures in this ord
|
||||
|
||||
Remember: Just like with most 3D graphics, +Y is upwards in Minetest.
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
minetest.register_node("mymod:diamond", {
|
||||
description = "Alien Diamond",
|
||||
tiles = {
|
||||
@ -142,7 +142,7 @@ minetest.register_node("mymod:diamond", {
|
||||
drop = "mymod:diamond_fragments"
|
||||
-- ^ Rather than dropping diamond, drop mymod:diamond_fragments
|
||||
})
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
The is_ground_content attribute allows caves to be generated over the stone.
|
||||
This is essential for any node which may be placed during map generation underground.
|
||||
@ -160,13 +160,13 @@ By default, the use callback is triggered when a player left-clicks with an item
|
||||
Having a use callback prevents the item being used to dig nodes.
|
||||
One common use of the use callback is for food:
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
minetest.register_craftitem("mymod:mudpie", {
|
||||
description = "Alien Mud Pie",
|
||||
inventory_image = "myfood_mudpie.png",
|
||||
on_use = minetest.item_eat(20),
|
||||
})
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
The number supplied to the minetest.item_eat function is the number of hit points
|
||||
healed when this food is consumed.
|
||||
@ -178,7 +178,7 @@ minetest.item_eat() is a function which returns a function, setting it
|
||||
as the on_use callback.
|
||||
This means the code above is roughly similar to this:
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
minetest.register_craftitem("mymod:mudpie", {
|
||||
description = "Alien Mud Pie",
|
||||
inventory_image = "myfood_mudpie.png",
|
||||
@ -186,7 +186,7 @@ minetest.register_craftitem("mymod:mudpie", {
|
||||
return minetest.do_item_eat(20, nil, ...)
|
||||
end,
|
||||
})
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
By, understanding how item_eat works by simply returning a function, it's
|
||||
possible to modify it to do more complex behaviour such as play a custom sound.
|
||||
@ -212,7 +212,7 @@ Shaped recipes are when the ingredients need to be in the right shape or
|
||||
pattern to work. In the below example, the fragments need to be in a
|
||||
chair-like pattern for the craft to work.
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
minetest.register_craft({
|
||||
type = "shaped",
|
||||
output = "mymod:diamond_chair 99",
|
||||
@ -222,7 +222,7 @@ minetest.register_craft({
|
||||
{"mymod:diamond_fragments", "mymod:diamond_fragments", ""}
|
||||
}
|
||||
})
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
One thing to note is the blank column on the right-hand side.
|
||||
This means that there *must* be an empty column to the right of the shape, otherwise
|
||||
@ -230,7 +230,7 @@ this won't work.
|
||||
If this empty column shouldn't be required, then the empty strings can be left
|
||||
out like so:
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
minetest.register_craft({
|
||||
output = "mymod:diamond_chair 99",
|
||||
recipe = {
|
||||
@ -239,7 +239,7 @@ minetest.register_craft({
|
||||
{"mymod:diamond_fragments", "mymod:diamond_fragments"}
|
||||
}
|
||||
})
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
The type field isn't actually needed for shapeless crafts, as shapeless is the
|
||||
default craft type.
|
||||
@ -249,27 +249,27 @@ default craft type.
|
||||
Shapeless recipes are a type of recipe which is used when it doesn't matter
|
||||
where the ingredients are placed, just that they're there.
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
minetest.register_craft({
|
||||
type = "shapeless",
|
||||
output = "mymod:diamond 3",
|
||||
recipe = {"mymod:diamond_fragments", "mymod:diamond_fragments", "mymod:diamond_fragments"}
|
||||
})
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
### Cooking and Fuel
|
||||
|
||||
Recipes with the type "cooking" are not made in the crafting grid,
|
||||
but are cooked in furnaces, or other cooking tools that might be found in mods.
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
minetest.register_craft({
|
||||
type = "cooking",
|
||||
output = "mymod:diamond_fragments",
|
||||
recipe = "default:coalblock",
|
||||
cooktime = 10,
|
||||
})
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
The only real difference in the code is that the recipe is just a single item,
|
||||
compared to being in a table (between braces).
|
||||
@ -284,13 +284,13 @@ It creates diamond fragments after 10 seconds!
|
||||
This type is an accompaniment to the cooking type, as it defines
|
||||
what can be burned in furnaces and other cooking tools from mods.
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
minetest.register_craft({
|
||||
type = "fuel",
|
||||
recipe = "mymod:diamond",
|
||||
burntime = 300,
|
||||
})
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
They don't have an output like other recipes, but they have a burn time
|
||||
which defines how long they will last as fuel in seconds.
|
||||
@ -302,22 +302,22 @@ Items can be members of many groups and groups can have many members.
|
||||
Groups are defined using the `groups` property in the definition table,
|
||||
and have an associated value.
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
groups = {cracky = 3, wood = 1}
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
There are several reasons you use groups.
|
||||
Firstly, groups are used to describe properties such as dig types and flammability.
|
||||
Secondly, groups can be used in a craft recipe instead of an item name to allow
|
||||
any item in group to be used.
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
minetest.register_craft({
|
||||
type = "shapeless",
|
||||
output = "mymod:diamond_thing 3",
|
||||
recipe = {"group:wood", "mymod:diamond"}
|
||||
})
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
## Tools, Capabilities and Dig Types
|
||||
|
||||
@ -351,7 +351,7 @@ with the less suitable ones having very inefficient properties.
|
||||
If the item a player is currently wielding doesn't have an explicit tool
|
||||
capability, then the capability of the current hand is used instead.
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
minetest.register_tool("mymod:tool", {
|
||||
description = "My Tool",
|
||||
inventory_image = "mymod_tool.png",
|
||||
@ -364,7 +364,7 @@ minetest.register_tool("mymod:tool", {
|
||||
damage_groups = {fleshy=2},
|
||||
},
|
||||
})
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
Groupcaps is the list of supported dig types for digging nodes.
|
||||
Damage groups are for controlling how tools damage objects, which will be
|
||||
|
@ -26,7 +26,7 @@ has a chance to appear near water.
|
||||
To create it, you first need to register the grass, then
|
||||
write an ABM.
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
minetest.register_node("aliens:grass", {
|
||||
description = "Alien Grass",
|
||||
light_source = 3, -- The node radiates light. Values can be from 1 to 15
|
||||
@ -44,7 +44,7 @@ minetest.register_abm({
|
||||
minetest.set_node({x = pos.x, y = pos.y + 1, z = pos.z}, {name = "aliens:grass"})
|
||||
end
|
||||
})
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
This ABM runs every ten seconds. There is a 1 in 5 chance of the ABM running on each
|
||||
node that has the correct name and the correct neighbours. If the ABM runs on a
|
||||
|
@ -52,10 +52,10 @@ be a cube. -->
|
||||
|
||||
You can read from the map once you have a position:
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
local node = minetest.get_node({ x = 1, y = 3, z = 4 })
|
||||
print(dump(node)) --> { name=.., param1=.., param2=.. }
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
If the position is a decimal, it will be rounded to the containing node.
|
||||
The function will always return a table containing the node information:
|
||||
@ -82,30 +82,30 @@ For example, say we wanted to make a certain type of plant that grows
|
||||
better near mese. You would need to search for any nearby mese nodes,
|
||||
and adapt the growth rate accordingly.
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
local grow_speed = 1
|
||||
local node_pos = minetest.find_node_near(pos, 5, { "default:mese" })
|
||||
if node_pos then
|
||||
minetest.chat_send_all("Node found at: " .. dump(node_pos))
|
||||
grow_speed = 2
|
||||
end
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
Let's say, for example, that the growth rate increases the more mese there is
|
||||
nearby. You should then use a function which can find multiple nodes in area:
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
local pos1 = vector.subtract(pos, { x = 5, y = 5, z = 5 })
|
||||
local pos2 = vector.add(pos, { x = 5, y = 5, z = 5 })
|
||||
local pos_list = minetest.find_nodes_in_area(pos1, pos2, { "default:mese" })
|
||||
local grow_speed = 1 + #pos_list
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
The above code doesn't quite do what we want, as it checks based on area, whereas
|
||||
`find_node_near` checks based on range. In order to fix this we will,
|
||||
unfortunately, need to manually check the range ourselves.
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
local pos1 = vector.subtract(pos, { x = 5, y = 5, z = 5 })
|
||||
local pos2 = vector.add(pos, { x = 5, y = 5, z = 5 })
|
||||
local pos_list = minetest.find_nodes_in_area(pos1, pos2, { "default:mese" })
|
||||
@ -116,7 +116,7 @@ for i=1, #pos_list do
|
||||
grow_speed = grow_speed + 1
|
||||
end
|
||||
end
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
Now your code will correctly increase `grow_speed` based on mese nodes in range.
|
||||
Note how we compared the squared distance from the position, rather than square
|
||||
@ -135,12 +135,12 @@ You can use `set_node` to write to the map. Each call to set_node will cause
|
||||
lighting to be recalculated, which means that set_node is fairly slow for large
|
||||
numbers of nodes.
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
minetest.set_node({ x = 1, y = 3, z = 4 }, { name = "default:mese" })
|
||||
|
||||
local node = minetest.get_node({ x = 1, y = 3, z = 4 })
|
||||
print(node.name) --> default:mese
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
set_node will remove any associated metadata or inventory from that position.
|
||||
This isn't desirable in all circumstances, especially if you're using multiple
|
||||
@ -150,9 +150,9 @@ two.
|
||||
|
||||
You can set a node without deleting metadata or the inventory like so:
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
minetest.swap_node({ x = 1, y = 3, z = 4 }, { name = "default:mese" })
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
### Removing Nodes
|
||||
|
||||
@ -160,10 +160,10 @@ A node must always be present. To remove a node, you set the position to `air`.
|
||||
|
||||
The following two lines will both remove a node, and are both identical:
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
minetest.remove_node(pos)
|
||||
minetest.set_node(pos, { name = "air" })
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
In fact, remove_node will call set_node with name being air.
|
||||
|
||||
@ -173,7 +173,7 @@ You can use `minetest.emerge_area` to load map blocks. Emerge area is asynchrono
|
||||
meaning the blocks won't be loaded instantly. Instead, they will be loaded
|
||||
soon in the future, and the callback will be called each time.
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
-- Load a 20x20x20 area
|
||||
local halfsize = { x = 10, y = 10, z = 10 }
|
||||
local pos1 = vector.subtract(pos, halfsize)
|
||||
@ -181,12 +181,12 @@ local pos2 = vector.add (pos, halfsize)
|
||||
|
||||
local context = {} -- persist data between callback calls
|
||||
minetest.emerge_area(pos1, pos2, emerge_callback, context)
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
Minetest will call `emerge_callback` whenever it loads a block, with some
|
||||
progress information.
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
local function emerge_callback(pos, action, num_calls_remaining, context)
|
||||
-- On first call, record number of blocks
|
||||
if not context.total_blocks then
|
||||
@ -206,7 +206,7 @@ local function emerge_callback(pos, action, num_calls_remaining, context)
|
||||
context.loaded_blocks, context.total_blocks, perc)
|
||||
end
|
||||
end
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
This is not the only way of loading blocks; Using a LVM will also cause the
|
||||
encompassed blocks to be loaded synchronously.
|
||||
@ -215,14 +215,14 @@ encompassed blocks to be loaded synchronously.
|
||||
|
||||
You can use delete_blocks to delete a range of map blocks:
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
-- Delete a 20x20x20 area
|
||||
local halfsize = { x = 10, y = 10, z = 10 }
|
||||
local pos1 = vector.subtract(pos, halfsize)
|
||||
local pos2 = vector.add (pos, halfsize)
|
||||
|
||||
minetest.delete_area(pos1, pos2)
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
This will delete all map blocks in that area, *inclusive*. This means that some
|
||||
nodes will be deleted outside the area as they will be on a mapblock which overlaps
|
||||
|
@ -36,10 +36,10 @@ You can only load a cubic area into an LVM, so you need to work out the minimum
|
||||
and maximum positions that you need to modify. Then you can create and read into
|
||||
an LVM like so:
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
local vm = minetest.get_voxel_manip()
|
||||
local emin, emax = vm:read_from_map(pos1, pos2)
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
An LVM may not read exactly the area you tell it to, for performance reasons.
|
||||
Instead, it may read a larger area. The larger area is given by `emin` and `emax`,
|
||||
@ -53,9 +53,9 @@ To read the types of nodes at particular positions, you'll need to use `get_data
|
||||
`get_data()` returns a flat array where each entry represents the type of a
|
||||
particular node.
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
local data = vm:get_data()
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
You can get param2 and lighting data using the methods `get_light_data()` and `get_param2_data()`.
|
||||
|
||||
@ -63,7 +63,7 @@ You'll need to use `emin` and `emax` to work out where a node is in the flat arr
|
||||
given by the above methods. There's a helper class called `VoxelArea` which handles
|
||||
the calculation for you:
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
local a = VoxelArea:new{
|
||||
MinEdge = emin,
|
||||
MaxEdge = emax
|
||||
@ -74,25 +74,25 @@ local idx = a:index(x, y, z)
|
||||
|
||||
-- Read node
|
||||
print(data[idx])
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
If you run that, you'll notice that `data[vi]` is an integer. This is because
|
||||
the engine doesn't store nodes using their name string, as string comparision
|
||||
is slow. Instead, the engine uses a content ID. You can find out the content
|
||||
ID for a particular type of node like so:
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
local c_stone = minetest.get_content_id("default:stone")
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
and then you can check whether a node is stone like so:
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
local idx = a:index(x, y, z)
|
||||
if data[idx] == c_stone then
|
||||
print("is stone!")
|
||||
end
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
It is recommended that you find out and store the content IDs of nodes types
|
||||
using load time, as the IDs of a node type will never change. Make sure to store
|
||||
@ -101,7 +101,7 @@ the IDs in a local for performance reasons.
|
||||
Nodes in an LVM data are stored in reverse co-ordinate order, so you should
|
||||
always iterate in the order of `z, y, x` like so:
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
for z = min.z, max.z do
|
||||
for y = min.y, max.y do
|
||||
for x = min.x, max.x do
|
||||
@ -113,7 +113,7 @@ for z = min.z, max.z do
|
||||
end
|
||||
end
|
||||
end
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
The reason for this touches computer architecture. Reading from RAM is rather
|
||||
costly, so CPUs have multiple levels of caching. If the data a process requests
|
||||
@ -128,7 +128,7 @@ another, and avoid *memory thrashing*.
|
||||
|
||||
First you need to set the new content ID in the data array:
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
for z = min.z, max.z do
|
||||
for y = min.y, max.y do
|
||||
for x = min.x, max.x do
|
||||
@ -139,15 +139,15 @@ for z = min.z, max.z do
|
||||
end
|
||||
end
|
||||
end
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
When you finished setting nodes in the LVM, you then need to upload the data
|
||||
array to the engine:
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
vm:set_data(data)
|
||||
vm:write_to_map(true)
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
For setting lighting and param2 data, there are the appropriately named
|
||||
`set_light_data()` and `set_param2_data()` methods.
|
||||
@ -158,7 +158,7 @@ date using `minetest.fix_light`.
|
||||
|
||||
## Example
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
-- Get content IDs during load time, and store into a local
|
||||
local c_dirt = minetest.get_content_id("default:dirt")
|
||||
local c_grass = minetest.get_content_id("default:dirt_with_grass")
|
||||
@ -189,7 +189,7 @@ local function grass_to_dirt(pos1, pos2)
|
||||
vm:set_data(data)
|
||||
vm:write_to_map(true)
|
||||
end
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
## Your Turn
|
||||
|
||||
|
@ -44,16 +44,16 @@ Metadata is stored in a key value relationship. For example:
|
||||
|
||||
If you know the position of a node, you can retrieve its metadata:
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
local meta = minetest.get_meta(pos)
|
||||
-- where pos = { x = 1, y = 5, z = 7 }
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
## Reading Metadata
|
||||
|
||||
After retrieving the metadata, you can read its values:
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
local value = meta:get_string("key")
|
||||
|
||||
if value then
|
||||
@ -63,7 +63,7 @@ else
|
||||
-- metadata of key "key" does not exist
|
||||
print(value)
|
||||
end
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
The functions available include:
|
||||
|
||||
@ -74,7 +74,7 @@ The functions available include:
|
||||
|
||||
To get a Boolean, you should use `get_string` and `minetest.is_yes`:
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
local value = minetest.is_yes(meta:get_string("key"))
|
||||
|
||||
if value then
|
||||
@ -82,18 +82,18 @@ if value then
|
||||
else
|
||||
print("is no")
|
||||
end
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
## Setting Metadata
|
||||
|
||||
You can set node metadata. For example:
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
local value = "one"
|
||||
meta:set_string("key", value)
|
||||
|
||||
meta:set_string("foo", "bar")
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
This can be done using the following functions:
|
||||
|
||||
@ -105,11 +105,11 @@ This can be done using the following functions:
|
||||
|
||||
You can convert to and from Lua tables using `to_table` and `from_table`:
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
local tmp = meta:to_table()
|
||||
tmp.foo = "bar"
|
||||
meta:from_table(tmp)
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
## Infotext
|
||||
|
||||
@ -117,9 +117,9 @@ The Minetest engine reads the field `infotext` to make text
|
||||
appear on mouse-over. This is used by furnaces to show progress and by signs
|
||||
to show their text. For example:
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
meta:set_string("infotext", "Here is some text that will appear on mouse-over!")
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
## Your Turn
|
||||
|
||||
|
@ -38,19 +38,19 @@ Don't be fooled though, all entities are Lua entities.
|
||||
|
||||
`get_pos` and `set_pos` exist to allow you to get and set an entity's position.
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
local object = minetest.get_player_by_name("bob")
|
||||
local pos = object:get_pos()
|
||||
object:set_pos({ x = pos.x, y = pos.y + 1, z = pos.z })
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
`set_pos` immediately sets the position, with no animation. If you'd like to
|
||||
smoothly animate an object to the new position, you should use `move_to`.
|
||||
This, unfortunately, only works for entities.
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
object:move_to({ x = pos.x, y = pos.y + 1, z = pos.z })
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
An important thing to think about when dealing with entities is network latency.
|
||||
In an ideal world, messages about entity movements would arrive immediately,
|
||||
@ -69,14 +69,14 @@ Unlike nodes, objects have a dynamic rather than set appearance.
|
||||
You can change how an object looks, among other things, at any time by updating
|
||||
its properties.
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
object:set_properties({
|
||||
visual = "mesh",
|
||||
mesh = "character.b3d",
|
||||
textures = {"character_texture.png"},
|
||||
visual_size = {x=1, y=1},
|
||||
})
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
The updated properties will be sent to all players in range.
|
||||
This is very useful to get a large amount of variety very cheaply, such as having
|
||||
@ -93,7 +93,7 @@ joined players.
|
||||
An Entity has a type table much like an item does.
|
||||
This table can contain callback methods, default object properties, and custom elements.
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
local MyEntity = {
|
||||
initial_properties = {
|
||||
hp_max = 1,
|
||||
@ -113,7 +113,7 @@ local MyEntity = {
|
||||
function MyEntity:set_message(msg)
|
||||
self.message = msg
|
||||
end
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
When an entity is emerged, a table is created for it by copying everything from
|
||||
its type table.
|
||||
@ -121,16 +121,16 @@ This table can be used to store variables for that particular entity.
|
||||
|
||||
Both an ObjectRef and an entity table provide ways to get the counterpart:
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
local entity = object:get_luaentity()
|
||||
local object = entity.object
|
||||
print("entity is at " .. minetest.pos_to_string(object:get_pos()))
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
There are a number of available callbacks for use with entities.
|
||||
A complete list can be found in [lua_api.txt]({{ page.root }}/lua_api.html#registered-entities)
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
function MyEntity:on_step(dtime)
|
||||
local pos = self.object:get_pos()
|
||||
|
||||
@ -151,7 +151,7 @@ end
|
||||
function MyEntity:on_punch(hitter)
|
||||
minetest.chat_send_player(hitter:get_player_name(), self.message)
|
||||
end
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
Now, if you were to spawn and use this entity, you'd notice that the message
|
||||
would be forgotten when the entity becomes inactive then active again.
|
||||
@ -161,7 +161,7 @@ how to save things.
|
||||
Staticdata is a string which contains all of the custom information that
|
||||
needs to stored.
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
function MyEntity:get_staticdata()
|
||||
return minetest.write_json({
|
||||
message = self.message,
|
||||
@ -174,7 +174,7 @@ function MyEntity:on_activate(staticdata, dtime_s)
|
||||
self:set_message(data.message)
|
||||
end
|
||||
end
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
Minetest may call `get_staticdata()` as many times as it once and at any time.
|
||||
This is because Minetest doesn't wait for a MapBlock to become inactive to save
|
||||
@ -187,9 +187,9 @@ This means that staticdata could be empty.
|
||||
|
||||
Finally, you need to register the type table using the aptly named `register_entity`.
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
minetest.register_entity("9_entities:myentity", MyEntity)
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
|
||||
## Attachments
|
||||
@ -198,9 +198,9 @@ Attached objects will move when the parent - the object they are attached to -
|
||||
is moved. An attached object is said to be a child of the parent.
|
||||
An object can have an unlimited number of children, but at most one parent.
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
child:set_attach(parent, bone, position, rotation)
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
An Object's `get_pos()` will always return the global position of the object, no
|
||||
matter whether it is attached or not.
|
||||
|
@ -39,9 +39,9 @@ sending messages, intercepting messages and registering chat commands.
|
||||
|
||||
To send a message to every player in the game, call the chat_send_all function.
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
minetest.chat_send_all("This is a chat message to all players")
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
Here is an example of how this appears in-game:
|
||||
|
||||
@ -55,9 +55,9 @@ The message appears on a separate line to distinguish it from in-game player cha
|
||||
|
||||
To send a message to a specific player, call the chat_send_player function:
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
minetest.chat_send_player("player1", "This is a chat message for player1")
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
This message displays in the same manner as messages to all players, but is
|
||||
only visible to the named player, in this case player1.
|
||||
@ -66,7 +66,7 @@ only visible to the named player, in this case player1.
|
||||
|
||||
To register a chat command, for example /foo, use register_chatcommand:
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
minetest.register_chatcommand("foo", {
|
||||
privs = {
|
||||
interact = true
|
||||
@ -75,26 +75,26 @@ minetest.register_chatcommand("foo", {
|
||||
return true, "You said " .. param .. "!"
|
||||
end
|
||||
})
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
Calling /foo bar will display `You said bar!` in the chat console.
|
||||
|
||||
You can restrict which players are able to run commands:
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
privs = {
|
||||
interact = true
|
||||
},
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
This means only players with the `interact` [privilege](privileges.html) can run the
|
||||
command. Other players will see an error message informing them of which
|
||||
privilege they're missing. If the player has the necessary privileges, the command
|
||||
will run and the message will be sent:
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
return true, "You said " .. param .. "!"
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
This returns two values, a Boolean which shows the command succeeded
|
||||
and the chat message to send to the player.
|
||||
@ -113,9 +113,9 @@ It is often required to make complex chat commands, such as:
|
||||
This is usually done using [Lua patterns](https://www.lua.org/pil/20.2.html).
|
||||
Patterns are a way of extracting stuff from text using rules.
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
local to, msg = string.match(param, "^([%a%d_-]+) (*+)$")
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
The above implements `/msg <to> <message>`. Let's go through left to right:
|
||||
|
||||
@ -147,12 +147,12 @@ to make complex chat commands without Patterns called
|
||||
|
||||
To intercept a message, use register_on_chat_message:
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
minetest.register_on_chat_message(function(name, message)
|
||||
print(name .. " said " .. message)
|
||||
return false
|
||||
end)
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
By returning false, you allow the chat message to be sent by the default
|
||||
handler. You can actually remove the line `return false`, and it would still
|
||||
@ -163,7 +163,7 @@ work the same.
|
||||
You should make sure you take into account that it may be a chat command,
|
||||
or the user may not have `shout`.
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
minetest.register_on_chat_message(function(name, message)
|
||||
if message:sub(1, 1) == "/" then
|
||||
print(name .. " ran chat command")
|
||||
@ -175,4 +175,4 @@ minetest.register_on_chat_message(function(name, message)
|
||||
|
||||
return false
|
||||
end)
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
@ -26,14 +26,14 @@ modders tend to use the method outlined in the
|
||||
|
||||
Traditionally mods implemented these complex commands using Lua patterns.
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
local name = string.match(param, "^join ([%a%d_-]+)")
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
I however find Lua patterns annoying to write and unreadable.
|
||||
Because of this, I created a library to do this for you.
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
ChatCmdBuilder.new("sethp", function(cmd)
|
||||
cmd:sub(":target :hp:int", function(name, target, hp)
|
||||
local player = minetest.get_player_by_name(target)
|
||||
@ -51,7 +51,7 @@ end, {
|
||||
-- ^ probably better to register a custom priv
|
||||
}
|
||||
})
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
`ChatCmdBuilder.new(name, setup_func, def)` creates a new chat command called
|
||||
`name`. It then calls the function passed to it (`setup_func`), which then creates
|
||||
@ -99,11 +99,11 @@ In `:name :hp:int`, there are two variables there:
|
||||
The first argument is the caller's name. The variables are then passed to the
|
||||
function in order.
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
cmd:sub(":target :hp:int", function(name, target, hp)
|
||||
-- subcommand function
|
||||
end)
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
## Installing ChatCmdBuilder
|
||||
|
||||
@ -125,7 +125,7 @@ Here is an example that creates a chat command that allows us to do this:
|
||||
* `/admin log <username>` - show report log
|
||||
* `/admin log <username> <message>` - log to report log
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
local admin_log
|
||||
local function load()
|
||||
admin_log = {}
|
||||
@ -179,4 +179,4 @@ end, {
|
||||
ban = true
|
||||
}
|
||||
})
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
@ -110,7 +110,7 @@ This example shows a formspec to a player when they use the /formspec command.
|
||||
</figcaption>
|
||||
</figure>
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
-- Show form when the /formspec command is used.
|
||||
minetest.register_chatcommand("formspec", {
|
||||
func = function(name, param)
|
||||
@ -121,20 +121,20 @@ minetest.register_chatcommand("formspec", {
|
||||
"button_exit[1,2;2,1;exit;Save]")
|
||||
end
|
||||
})
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
Note: the .. is used to join two strings together. The following two lines are equivalent:
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
"foobar"
|
||||
"foo" .. "bar"
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
## Callbacks
|
||||
|
||||
It's possible to expand the previous example with a callback:
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
-- Show form when the /formspec command is used.
|
||||
minetest.register_chatcommand("formspec", {
|
||||
func = function(name, param)
|
||||
@ -161,7 +161,7 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
|
||||
-- from receiving this submission.
|
||||
return true
|
||||
end)
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
The function given in minetest.register_on_player_receive_fields is called
|
||||
every time a user submits a form. Most callbacks will check the formname given
|
||||
@ -185,14 +185,14 @@ Some elements can submit the form without the user clicking a button,
|
||||
such as a check box. You can detect these cases by looking
|
||||
for a clicked button.
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
-- An example of what fields could contain,
|
||||
-- using the above code
|
||||
{
|
||||
name = "Foo Bar",
|
||||
exit = true
|
||||
}
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
## Contexts
|
||||
|
||||
@ -202,7 +202,7 @@ what a chat command was called with, or what the dialog is about.
|
||||
|
||||
For example, you might make a form to handle land protection information:
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
--
|
||||
-- Step 1) set context when player requests the formspec
|
||||
--
|
||||
@ -256,7 +256,7 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
|
||||
"Something went wrong, try again.")
|
||||
end
|
||||
end)
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
## Node Meta Formspecs
|
||||
|
||||
@ -265,7 +265,7 @@ add formspecs to a [node's meta data](node_metadata.html). For example,
|
||||
this is used with chests to allow for faster opening times -
|
||||
you don't need to wait for the server to send the player the chest formspec.
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
minetest.register_node("mymod:rightclick", {
|
||||
description = "Rightclick me!",
|
||||
tiles = {"mymod_rightclick.png"},
|
||||
@ -286,7 +286,7 @@ minetest.register_node("mymod:rightclick", {
|
||||
print(fields.x)
|
||||
end
|
||||
})
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
Formspecs set this way do not trigger the same callback. In order to
|
||||
receive form input for meta formspecs, you must include an
|
||||
|
@ -87,7 +87,7 @@ to the right of the window, but to resize without breaking.
|
||||
|
||||
You can create a HUD element once you have a copy of the player object:
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
local player = minetest.get_player_by_name("username")
|
||||
local idx = player:hud_add({
|
||||
hud_elem_type = "text",
|
||||
@ -97,7 +97,7 @@ local idx = player:hud_add({
|
||||
alignment = {x = 0, y = 0}, -- center aligned
|
||||
scale = {x = 100, y = 100}, -- covered later
|
||||
})
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
The `hud_add` function returns an element ID - this can be used later to modify
|
||||
or remove a HUD element.
|
||||
@ -116,7 +116,7 @@ table. The meaning of other properties varies based on this type.
|
||||
|
||||
Let's go ahead, and place all the text in our score panel:
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
player:hud_add({
|
||||
hud_elem_type = "text",
|
||||
position = {x = 1, y = 0.5},
|
||||
@ -152,7 +152,7 @@ player:hud_add({
|
||||
scale = { x = 50, y = 10},
|
||||
number = 0xFFFFFF,
|
||||
})
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
This results in the following:
|
||||
|
||||
@ -167,7 +167,7 @@ This results in the following:
|
||||
|
||||
Image elements are created in a very similar way to text elements:
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
player:hud_add({
|
||||
hud_elem_type = "image",
|
||||
position = {x = 1, y = 0.5},
|
||||
@ -176,7 +176,7 @@ player:hud_add({
|
||||
scale = { x = 1, y = 1},
|
||||
alignment = { x = 1, y = 0 },
|
||||
})
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
You will now have this:
|
||||
|
||||
@ -199,7 +199,7 @@ For example, `x=-100` is 100% of the width.
|
||||
|
||||
Let's make the progress bar for our score panel as an example of scale:
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
local percent = tonumber(player:get_attribute("scoreboard:score") or 0.2)
|
||||
|
||||
player:hud_add({
|
||||
@ -219,7 +219,7 @@ player:hud_add({
|
||||
scale = { x = percent, y = 1},
|
||||
alignment = { x = 1, y = 0 },
|
||||
})
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
We now have a HUD that looks like the one in the first post!
|
||||
There is one problem however, it won't update when the stats change.
|
||||
@ -228,7 +228,7 @@ There is one problem however, it won't update when the stats change.
|
||||
|
||||
You can use the ID returned by the hud_add method to update or remove it later.
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
local idx = player:hud_add({
|
||||
hud_elem_type = "text",
|
||||
text = "Hello world!",
|
||||
@ -237,7 +237,7 @@ local idx = player:hud_add({
|
||||
|
||||
player:hud_change(idx, "text", "New Text")
|
||||
player:hud_remove(idx)
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
The `hud_change` method takes the element ID, the property to change, and the new
|
||||
value. The above call changes the `text` property from "Hello World" to "Test".
|
||||
@ -245,16 +245,16 @@ value. The above call changes the `text` property from "Hello World" to "Test".
|
||||
This means that doing the `hud_change` immediately after the `hud_add` is
|
||||
functionally equivalent to the following, in a rather inefficient way:
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
local idx = player:hud_add({
|
||||
hud_elem_type = "text",
|
||||
text = "New Text",
|
||||
})
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
## Storing IDs
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
scoreboard = {}
|
||||
local saved_huds = {}
|
||||
|
||||
@ -287,7 +287,7 @@ minetest.register_on_joinplayer(scoreboard.update_hud)
|
||||
minetest.register_on_leaveplayer(function(player)
|
||||
saved_huds[player:get_player_name()] = nil
|
||||
end)
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
|
||||
## Other Elements
|
||||
|
@ -23,7 +23,7 @@ gravity twice as strong.
|
||||
Here is an example of how to add an antigravity command, which
|
||||
puts the caller in low G:
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
minetest.register_chatcommand("antigravity", {
|
||||
func = function(name, param)
|
||||
local player = minetest.get_player_by_name(name)
|
||||
@ -33,7 +33,7 @@ minetest.register_chatcommand("antigravity", {
|
||||
})
|
||||
end
|
||||
})
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
## Available Overrides
|
||||
|
||||
|
@ -50,37 +50,37 @@ minetest.conf file) is automatically given all available privileges.
|
||||
|
||||
Use `register_privilege` to declare a new privilege:
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
minetest.register_privilege("vote", {
|
||||
description = "Can vote on issues",
|
||||
give_to_singleplayer = true
|
||||
})
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
If `give_to_singleplayer` is true, you can remove it, because true is the default
|
||||
when it is not specified. This simplifies the privilege registration to:
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
minetest.register_privilege("vote", {
|
||||
description = "Can vote on issues"
|
||||
})
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
## Checking for Privileges
|
||||
|
||||
To quickly check whether a player has all the required privileges:
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
local has, missing = minetest.check_player_privs(player_or_name, {
|
||||
interact = true,
|
||||
vote = true })
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
In this example, `has` is true if the player has all the privileges needed.\\
|
||||
If `has` is false, then `missing` will contain a dictionary
|
||||
of missing privileges.
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
if minetest.check_player_privs(name, {interact=true, vote=true}) then
|
||||
print("Player has all privs!")
|
||||
else
|
||||
@ -95,43 +95,43 @@ if has then
|
||||
else
|
||||
print("Player is missing privs: " .. dump(missing))
|
||||
end
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
## Getting and Setting Privileges
|
||||
|
||||
To get a table containing a player's privileges, regardless of whether
|
||||
the player is logged in, use `minetest.get_player_privs`:
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
local privs = minetest.get_player_privs(name)
|
||||
print(dump(privs))
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
This example may give:
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
{
|
||||
fly = true,
|
||||
interact = true,
|
||||
shout = true
|
||||
}
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
To set a player's privileges, use `minetest.set_player_privs`:
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
minetest.set_player_privs(name, {
|
||||
interact = true,
|
||||
shout = true })
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
To grant a player privileges, use a combination of the above two functions:
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
local privs = minetest.get_player_privs(name)
|
||||
privs.vote = true
|
||||
minetest.set_player_privs(name, privs)
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
## Adding Privileges to basic_privs
|
||||
|
||||
|
@ -27,14 +27,14 @@ some other format instead.
|
||||
So, to register a page you need to call the aptly named `sfinv.register_page`
|
||||
function with the page's name, and its definition. Here is a minimal example:
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
sfinv.register_page("mymod:hello", {
|
||||
title = "Hello!",
|
||||
get = function(self, player, context)
|
||||
-- TODO: implement this
|
||||
end
|
||||
})
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
You can also override an existing page using `sfinv.override_page`.
|
||||
|
||||
@ -42,7 +42,7 @@ If you ran the above code and clicked the page's tab, it would probably crash
|
||||
as sfinv is expecting a response from the `get` method. So let's add a response
|
||||
to fix that:
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
sfinv.register_page("mymod:hello", {
|
||||
title = "Hello!",
|
||||
get = function(self, player, context)
|
||||
@ -50,7 +50,7 @@ sfinv.register_page("mymod:hello", {
|
||||
"label[0.1,0.1;Hello world!]", true)
|
||||
end
|
||||
})
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
The `make_formspec` function surrounds your formspec with sfinv's formspec code.
|
||||
The fourth parameter, currently set as `true`, determines whether or not the
|
||||
@ -69,7 +69,7 @@ Let's make things more exciting. Here is the code for the formspec generation
|
||||
part of a player admin tab. This tab will allow admins to kick or ban players by
|
||||
selecting them in a list and clicking a button.
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
sfinv.register_page("myadmin:myadmin", {
|
||||
title = "Tab",
|
||||
get = function(self, player, context)
|
||||
@ -103,7 +103,7 @@ sfinv.register_page("myadmin:myadmin", {
|
||||
table.concat(formspec, ""), false)
|
||||
end,
|
||||
})
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
There's nothing new about the above code, all the concepts are covered above and
|
||||
in previous chapters.
|
||||
@ -120,11 +120,11 @@ in previous chapters.
|
||||
You can receive formspec events by adding a `on_player_receive_fields` function
|
||||
to a sfinv definition.
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
on_player_receive_fields = function(self, player, context, fields)
|
||||
-- TODO: implement this
|
||||
end,
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
Fields is the exact same as the fields given to the subscribers of
|
||||
`minetest.register_on_player_receive_fields`. The return value of
|
||||
@ -134,7 +134,7 @@ navigation tab events, so you won't receive them in this callback.
|
||||
|
||||
Now let's implement the `on_player_receive_fields` for our admin mod:
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
on_player_receive_fields = function(self, player, context, fields)
|
||||
-- text list event, check event type and set index if selection changed
|
||||
if fields.playerlist then
|
||||
@ -163,7 +163,7 @@ on_player_receive_fields = function(self, player, context, fields)
|
||||
end
|
||||
end
|
||||
end,
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
There's a rather large problem with this, however. Anyone can kick or ban players! You
|
||||
need a way to only show this to players with the kick or ban privileges.
|
||||
@ -174,12 +174,12 @@ Luckily SFINV allows you to do this!
|
||||
You can add an `is_in_nav` function to your page's definition if you'd like to
|
||||
control when the page is shown:
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
is_in_nav = function(self, player, context)
|
||||
local privs = minetest.get_player_privs(player:get_player_name())
|
||||
return privs.kick or privs.ban
|
||||
end,
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
If you only need to check one priv or want to perform an and, you should use
|
||||
`minetest.check_player_privs()` instead of `get_player_privs`.
|
||||
@ -192,7 +192,7 @@ This means that you need to manually request that SFINV regenerates the inventor
|
||||
formspec on any events that may change `is_in_nav`'s result. In our case,
|
||||
we need to do that whenever kick or ban is granted or revoked to a player:
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
local function on_grant_revoke(grantee, granter, priv)
|
||||
if priv == "kick" or priv == "ban" then
|
||||
local player = minetest.get_player_by_name(grantee)
|
||||
@ -207,7 +207,7 @@ if minetest.register_on_priv_grant then
|
||||
minetest.register_on_priv_grant(on_grant_revoke)
|
||||
minetest.register_on_priv_revoke(on_grant_revoke)
|
||||
end
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
## on_enter and on_leave callbacks
|
||||
|
||||
@ -221,7 +221,7 @@ Also, note that the inventory may not be visible at the time
|
||||
these callbacks are called. For example, on_enter is called for the home page
|
||||
when a player joins the game even before they open their inventory!
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
on_enter = function(self, player, context)
|
||||
|
||||
end,
|
||||
@ -229,7 +229,7 @@ end,
|
||||
on_leave = function(self, player, context)
|
||||
|
||||
end,
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
## Adding to an existing page
|
||||
|
||||
|
@ -83,7 +83,7 @@ You should write your data representation using Pure Lua. "Pure" in this context
|
||||
means that the functions could run outside of Minetest - none of the engine's
|
||||
functions are called.
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
-- Data
|
||||
function land.create(name, area_name)
|
||||
land.lands[area_name] = {
|
||||
@ -96,12 +96,12 @@ end
|
||||
function land.get_by_name(area_name)
|
||||
return land.lands[area_name]
|
||||
end
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
Your actions should also be pure, however calling other functions is more
|
||||
acceptable.
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
-- Controller
|
||||
function land.handle_create_submit(name, area_name)
|
||||
-- process stuff (ie: check for overlaps, check quotas, check permissions)
|
||||
@ -113,13 +113,13 @@ function land.handle_creation_request(name)
|
||||
-- This is a bad example, as explained later
|
||||
land.show_create_formspec(name)
|
||||
end
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
Your event handlers will have to interact with the Minetest API. You should keep
|
||||
the amount of calculations to a minimum, as you won't be able to test this area
|
||||
very easily.
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
-- View
|
||||
function land.show_create_formspec(name)
|
||||
-- Note how there's no complex calculations here!
|
||||
@ -141,7 +141,7 @@ minetest.register_chatcommand("/land", {
|
||||
minetest.register_on_player_receive_fields(function(player, formname, fields)
|
||||
land.handle_create_submit(player:get_player_name(), fields.area_name)
|
||||
end)
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
The above is the Model-View-Controller pattern. The model is a collection of data
|
||||
with minimal functions. The view is a collection of functions which listen to
|
||||
@ -217,7 +217,7 @@ Enter the Observer pattern. Instead of the mobs mod caring about awards, mobs
|
||||
exposes a way for other areas of code to register their interest in an event
|
||||
and receive data about the event.
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
mobs.registered_on_death = {}
|
||||
function mobs.register_on_death(func)
|
||||
table.insert(mobs.registered_on_death, func)
|
||||
@ -227,11 +227,11 @@ end
|
||||
for i=1, #mobs.registered_on_death do
|
||||
mobs.registered_on_death[i](entity, reason)
|
||||
end
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
Then the other code registers its interest:
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
|
||||
-- awards
|
||||
mobs.register_on_death(function(mob, reason)
|
||||
@ -239,7 +239,7 @@ mobs.register_on_death(function(mob, reason)
|
||||
awards.notify_mob_kill(reason.object, mob.name)
|
||||
end
|
||||
end)
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
You may be thinking - wait a second, this looks awfully familiar. And you're right!
|
||||
The Minetest API is heavily Observer based to stop the engine having to care about
|
||||
|
@ -21,7 +21,7 @@ offline or the entity is unloaded - then calling any methods will result in a cr
|
||||
|
||||
Don't do this:
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
minetest.register_on_joinplayer(function(player)
|
||||
local function func()
|
||||
local pos = player:get_pos() -- BAD!
|
||||
@ -36,11 +36,11 @@ minetest.register_on_joinplayer(function(player)
|
||||
-- It's recommended to just not do this
|
||||
-- use minetest.get_connected_players() and minetest.get_player_by_name() instead.
|
||||
end)
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
instead, do this:
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
minetest.register_on_joinplayer(function(player)
|
||||
local function func(name)
|
||||
-- Attempt to get the ref again
|
||||
@ -56,7 +56,7 @@ minetest.register_on_joinplayer(function(player)
|
||||
-- Pass the name into the function
|
||||
minetest.after(1, func, player:get_player_name())
|
||||
end)
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
## Don't Trust Formspec Submissions
|
||||
|
||||
@ -66,7 +66,7 @@ they like.
|
||||
The following code has a vulnerability where any player can give
|
||||
themselves moderator privileges:
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
local function show_formspec(name)
|
||||
if not minetest.check_player_privs(name, { privs = true }) then
|
||||
return false
|
||||
@ -89,11 +89,11 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
|
||||
minetest.set_player_privs(fields.target, privs)
|
||||
return true
|
||||
end)
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
Instead, do this:
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
minetest.register_on_player_receive_fields(function(player, formname, fields)
|
||||
if not minetest.check_player_privs(name, { privs = true }) then
|
||||
return false
|
||||
@ -101,7 +101,7 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
|
||||
|
||||
-- code
|
||||
end)
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
## Set ItemStacks After Changing Them
|
||||
|
||||
@ -112,22 +112,22 @@ This means that modifying a stack won't actually modify that stack in the invent
|
||||
|
||||
Don't do this:
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
local inv = player:get_inventory()
|
||||
local stack = inv:get_stack("main", 1)
|
||||
stack:get_meta():set_string("description", "Partially eaten")
|
||||
-- BAD! Modification will be lost
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
Do this:
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
local inv = player:get_inventory()
|
||||
local stack = inv:get_stack("main", 1)
|
||||
stack:get_meta():set_string("description", "Partially eaten")
|
||||
inv:set_stack("main", 1, stack)
|
||||
-- Correct! Item stack is set
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
The behaviour of callbacks is slightly more complicated. Modifying an itemstack you
|
||||
are given will change it for the caller too, and any subsequent callbacks. However,
|
||||
@ -135,23 +135,23 @@ it will only be saved in the engine if the callback caller sets it.
|
||||
|
||||
Avoid this:
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
minetest.register_on_item_eat(function(hp_change, replace_with_item, itemstack, user, pointed_thing)
|
||||
itemstack:get_meta():set_string("description", "Partially eaten")
|
||||
-- Almost correct! Data will be lost if another callback cancels the behaviour
|
||||
end)
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
If no callbacks cancel, then the stack will be set and the description will be updated.
|
||||
If a callback cancels, then the update may be lost. It's better to do this instead:
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
minetest.register_on_item_eat(function(hp_change, replace_with_item, itemstack, user, pointed_thing)
|
||||
itemstack:get_meta():set_string("description", "Partially eaten")
|
||||
user:get_inventory():set_stack("main", user:get_wield_index(), itemstack)
|
||||
-- Correct, description will always be set!
|
||||
end)
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
If the callbacks cancel or the callback runner doesn't set the stack,
|
||||
then our update will still be set.
|
||||
|
@ -59,7 +59,7 @@ root of your game, modpack, or mod.
|
||||
|
||||
Put the following contents in it:
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
unused_args = false
|
||||
allow_defined_top = true
|
||||
|
||||
@ -78,7 +78,7 @@ read_globals = {
|
||||
-- MTG
|
||||
"default", "sfinv", "creative",
|
||||
}
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
Next you'll need to test that it works by running LuaCheck. You should get a lot
|
||||
less errors this time. Starting at the first error you get, either modify the
|
||||
@ -121,7 +121,7 @@ and enable Travis by flipping the switch.
|
||||
|
||||
Next, create a file called .travis.yml with the following content:
|
||||
|
||||
{% highlight yml %}
|
||||
```yml
|
||||
language: generic
|
||||
sudo: false
|
||||
addons:
|
||||
@ -134,14 +134,14 @@ script:
|
||||
- $HOME/.luarocks/bin/luacheck --no-color .
|
||||
notifications:
|
||||
email: false
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
If your project is a game rather than a mod or mod pack,
|
||||
change the line after `script:` to:
|
||||
|
||||
{% highlight yml %}
|
||||
```yml
|
||||
- $HOME/.luarocks/bin/luacheck --no-color mods/
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
Now commit and push to Github. Go to your project's page on Github, and click
|
||||
commits. You should see an orange disc next to the commit you just made.
|
||||
|
@ -40,7 +40,7 @@ Any users can submit almost any formspec with any values at any time.
|
||||
|
||||
Here's some real code found in a mod:
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
minetest.register_on_player_receive_fields(function(player, formname, fields)
|
||||
-- Todo: fix security issue here
|
||||
local name = player:get_player_name()
|
||||
@ -56,7 +56,7 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
|
||||
end
|
||||
end
|
||||
end
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
Can you spot the issue? A malicious user could submit a formspec containing
|
||||
their own position values, allowing them to teleport to anywhere they wish to.
|
||||
@ -90,19 +90,19 @@ to the full Lua API.
|
||||
|
||||
Can you spot the vulnerability in the following?
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
local ie = minetest.request_insecure_environment()
|
||||
ie.os.execute(("path/to/prog %d"):format(3))
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
`String.format` is a function in the global shared table `String`.
|
||||
A malicious mod could override this function and pass stuff to os.execute:
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
String.format = function()
|
||||
return "xdg-open 'http://example.com'"
|
||||
end
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
The mod could pass something a lot more malicious than opening a website, such
|
||||
as giving a remote user control over the machine.
|
||||
|
@ -49,25 +49,25 @@ names ending in `_spec`, and then executes them in a standalone Lua environment.
|
||||
|
||||
### init.lua
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
mymod = {}
|
||||
|
||||
dofile(minetest.get_modpath("mymod") .. "/api.lua")
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
|
||||
|
||||
### api.lua
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
function mymod.add(x, y)
|
||||
return x + y
|
||||
end
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
### tests/api_spec.lua
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
-- Look for required things in
|
||||
package.path = "../?.lua;" .. package.path
|
||||
|
||||
@ -87,7 +87,7 @@ describe("add", function()
|
||||
assert.equals(-2, mymod.add(-1, -1))
|
||||
end)
|
||||
end)
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
You can now run the tests by opening a terminal in the mod's directory and
|
||||
running `busted .`
|
||||
@ -114,7 +114,7 @@ things not in your area however - for example, you'll have to mock the view when
|
||||
testing the controller/API. If you didn't follow the advice, then things are a
|
||||
little harder as you may have to mock the Minetest API.
|
||||
|
||||
{% highlight lua %}
|
||||
```lua
|
||||
-- As above, make a table
|
||||
_G.minetest = {}
|
||||
|
||||
@ -157,7 +157,7 @@ describe("list_areas", function()
|
||||
assert.same(expected, chat_send_all_calls)
|
||||
end)
|
||||
end)
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
|
||||
## Checking Commits with Travis
|
||||
@ -165,7 +165,7 @@ end)
|
||||
The Travis script from the [Error Checking](luacheck.html)
|
||||
chapter can be modified to also run Busted.
|
||||
|
||||
{% highlight yml %}
|
||||
```yml
|
||||
language: generic
|
||||
sudo: false
|
||||
addons:
|
||||
@ -179,7 +179,7 @@ script:
|
||||
- $HOME/.luarocks/bin/busted .
|
||||
notifications:
|
||||
email: false
|
||||
{% endhighlight %}
|
||||
```
|
||||
|
||||
|
||||
## Conclusion
|
||||
|
Loading…
Reference in New Issue
Block a user