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
|
Paragraphs
|
||||||
|
|
||||||
{% highlight lua %}
|
```lua
|
||||||
code
|
code
|
||||||
{% endhighlight %}
|
```
|
||||||
|
|
||||||
## Parts in
|
## Parts in
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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 %}
|
```
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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 %}
|
```
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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.
|
||||||
|
@ -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 %}
|
```
|
||||||
|
@ -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 %}
|
```
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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.
|
||||||
|
@ -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.
|
||||||
|
@ -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.
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user