Improve wording and grammar [3/3]
This commit is contained in:
parent
5cad89cacc
commit
9e7657621a
@ -24,7 +24,7 @@ creating mods.
|
|||||||
## What are Games and Mods?
|
## What are Games and Mods?
|
||||||
|
|
||||||
The power of Minetest is the ability to easily develop games without the need
|
The power of Minetest is the ability to easily develop games without the need
|
||||||
to create your own voxel graphics, voxel algorithms or fancy networking code.
|
to create your own voxel graphics, voxel algorithms, or fancy networking code.
|
||||||
|
|
||||||
In Minetest, a game is a collection of modules which work together to provide the
|
In Minetest, a game is a collection of modules which work together to provide the
|
||||||
content and behaviour of a game.
|
content and behaviour of a game.
|
||||||
|
@ -7,8 +7,8 @@ idx: 6.1
|
|||||||
|
|
||||||
## Introduction
|
## Introduction
|
||||||
|
|
||||||
The power of Minetest is the ability to easily create games without the need
|
The power of Minetest is the ability to easily develop games without the need
|
||||||
to write your own voxel graphics and algorithms or fancy networking.
|
to create your own voxel graphics, voxel algorithms, or fancy networking code.
|
||||||
|
|
||||||
* [What is a Game?](#what-is-a-game)
|
* [What is a Game?](#what-is-a-game)
|
||||||
* [Game Directory](#game-directory)
|
* [Game Directory](#game-directory)
|
||||||
@ -21,8 +21,8 @@ to write your own voxel graphics and algorithms or fancy networking.
|
|||||||
|
|
||||||
Games are a collection of mods which work together to make a cohesive game.
|
Games are a collection of mods which work together to make a cohesive game.
|
||||||
A good game has a consistent underlying theme and a direction, for example
|
A good game has a consistent underlying theme and a direction, for example
|
||||||
maybe it's a classic crafter miner with hard survival elements, or maybe
|
it could be a classic crafter miner with hard survival elements, or
|
||||||
it's a space simulation game with a steam punk automation ascetic.
|
it could be a space simulation game with a steam punk automation aesthetic.
|
||||||
|
|
||||||
Game design is a complex topic, and is actually a whole field of expertise.
|
Game design is a complex topic, and is actually a whole field of expertise.
|
||||||
It's beyond the scope of the book to more than briefly touch on it.
|
It's beyond the scope of the book to more than briefly touch on it.
|
||||||
@ -31,7 +31,7 @@ It's beyond the scope of the book to more than briefly touch on it.
|
|||||||
|
|
||||||
The structure and location of a game will seem rather familiar after working
|
The structure and location of a game will seem rather familiar after working
|
||||||
with mods.
|
with mods.
|
||||||
Games are found in a game location such as `minetest/games/<foo_game>`.
|
Games are found in a game location, such as `minetest/games/foo_game`.
|
||||||
|
|
||||||
foo_game
|
foo_game
|
||||||
├── game.conf
|
├── game.conf
|
||||||
@ -57,9 +57,9 @@ convenient, as it'll make porting mods to another game much easier.
|
|||||||
|
|
||||||
The best way to keep compatibility with another game is to keep API compatibility
|
The best way to keep compatibility with another game is to keep API compatibility
|
||||||
with any mods which have the same name.
|
with any mods which have the same name.
|
||||||
That is, if a mod uses the same name as another mod even if third party,
|
That is, if a mod uses the same name as another mod, even if third party,
|
||||||
then it should have a compatible API.
|
it should have a compatible API.
|
||||||
For example, if a game includes a mod called `doors` then it should have the
|
For example, if a game includes a mod called `doors`, then it should have the
|
||||||
same API as `doors` in Minetest Game.
|
same API as `doors` in Minetest Game.
|
||||||
|
|
||||||
API compatibility for a mod is the sum of the following things:
|
API compatibility for a mod is the sum of the following things:
|
||||||
@ -68,11 +68,11 @@ API compatibility for a mod is the sum of the following things:
|
|||||||
For example, `mobs.register_mob`.
|
For example, `mobs.register_mob`.
|
||||||
* Registered Nodes/Items - The presence of items.
|
* Registered Nodes/Items - The presence of items.
|
||||||
|
|
||||||
It's probably fine to have partial breakages as long as 90% of dependency
|
Small breakages aren't that bad, such as not having a random utility
|
||||||
usecases still works. For example, not having a random utility function that was
|
function that was only actually used internally, but bigger breakages
|
||||||
only actually used internally is ok, but not having `mobs.register_mobs` is bad.
|
related to core features are very bad.
|
||||||
|
|
||||||
It's difficult to maintain API compatibility with a disgusting God mega-mod like
|
It's difficult to maintain API compatibility with a disgusting mega God-mod like
|
||||||
*default* in Minetest Game, in which case the game shouldn't include a mod named
|
*default* in Minetest Game, in which case the game shouldn't include a mod named
|
||||||
default.
|
default.
|
||||||
|
|
||||||
@ -83,7 +83,7 @@ To check whether a mod name has been taken, search for it on
|
|||||||
|
|
||||||
### Groups and Aliases
|
### Groups and Aliases
|
||||||
|
|
||||||
Groups and Aliases are both massive tools in keeping compatibility between games,
|
Groups and Aliases are both useful tools in keeping compatibility between games,
|
||||||
as it allows item names to be different between different games. Common nodes
|
as it allows item names to be different between different games. Common nodes
|
||||||
like stone and wood should have groups to indicate the material. It's also a
|
like stone and wood should have groups to indicate the material. It's also a
|
||||||
good idea to provide aliases from default nodes to any direct replacements.
|
good idea to provide aliases from default nodes to any direct replacements.
|
||||||
|
@ -42,8 +42,8 @@ local vm = minetest.get_voxel_manip()
|
|||||||
local emin, emax = vm:read_from_map(pos1, pos2)
|
local emin, emax = vm:read_from_map(pos1, pos2)
|
||||||
```
|
```
|
||||||
|
|
||||||
For performance reasons, an LVM may not read the exact area you tell it to.
|
For performance reasons, an LVM will almost never read the exact area you tell it to.
|
||||||
Instead, it may read a larger area. The larger area is given by `emin` and `emax`,
|
Instead, it will likely read a larger area. The larger area is given by `emin` and `emax`,
|
||||||
which stand for *emerged min pos* and *emerged max pos*. An LVM will load the area
|
which stand for *emerged min pos* and *emerged max pos*. An LVM will load the area
|
||||||
it contains for you - whether that involves loading from memory, from disk, or
|
it contains for you - whether that involves loading from memory, from disk, or
|
||||||
calling the map generator.
|
calling the map generator.
|
||||||
@ -79,8 +79,9 @@ print(data[idx])
|
|||||||
|
|
||||||
When you run this, you'll notice that `data[vi]` is an integer. This is because
|
When you run this, you'll notice that `data[vi]` is an integer. This is because
|
||||||
the engine doesn't store nodes using their name string, as string comparison
|
the engine doesn't store nodes using their name string, as string comparison
|
||||||
is slow. Instead, the engine uses a content ID. You can find out the content
|
is slow. Instead, the engine uses an integer called a content ID.
|
||||||
ID for a particular type of node with `get_content_id()`. For example:
|
You can find out the content ID for a particular type of node with
|
||||||
|
`get_content_id()`. For example:
|
||||||
|
|
||||||
```lua
|
```lua
|
||||||
local c_stone = minetest.get_content_id("default:stone")
|
local c_stone = minetest.get_content_id("default:stone")
|
||||||
@ -117,13 +118,13 @@ end
|
|||||||
```
|
```
|
||||||
|
|
||||||
The reason for this touches on the topic of computer architecture. Reading from RAM is rather
|
The reason for this touches on the topic of 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 that a process requests
|
||||||
is in the cache, it can very quickly retrieve it. If the data is not in the cache,
|
is in the cache, it can very quickly retrieve it. If the data is not in the cache,
|
||||||
then a cache miss occurs and it will fetch the data it needs from RAM. Any data
|
then a cache miss occurs and it will fetch the data it needs from RAM. Any data
|
||||||
surrounding the requested data is also fetched and then replaces the data in the cache. This is
|
surrounding the requested data is also fetched and then replaces the data in the cache. This is
|
||||||
because it's quite likely that the process will ask for data near that location again. This means
|
because it's quite likely that the process will ask for data near that location again. This means
|
||||||
a good rule of optimisation is to iterate in a way that you read data one after
|
a good rule of optimisation is to iterate in a way that you read data one after
|
||||||
another, and avoid memory thrashing.
|
another, and avoid *cache thrashing*.
|
||||||
|
|
||||||
## Writing Nodes
|
## Writing Nodes
|
||||||
|
|
||||||
@ -194,10 +195,10 @@ end
|
|||||||
|
|
||||||
## Your Turn
|
## Your Turn
|
||||||
|
|
||||||
* Create `replace_in_area(from, to, pos1, pos2)` which replaces all instances of
|
* Create `replace_in_area(from, to, pos1, pos2)`, which replaces all instances of
|
||||||
`from` with `to` in the area given, where `from` and `to` are node names.
|
`from` with `to` in the area given, where `from` and `to` are node names.
|
||||||
* Make a function which rotates all chest nodes by 90°.
|
* Make a function which rotates all chest nodes by 90°.
|
||||||
* Make a function which uses an LVM to cause mossy cobble to spread to nearby
|
* Make a function which uses an LVM to cause mossy cobble to spread to nearby
|
||||||
stone and cobble nodes.
|
stone and cobble nodes.
|
||||||
Does your implementation cause mossy cobble to spread more than a distance of one each
|
Does your implementation cause mossy cobble to spread more than a distance of one node each
|
||||||
time? If so, how could you stop this?
|
time? If so, how could you stop this?
|
||||||
|
@ -95,7 +95,7 @@ print(meta:get_string("count")) --> "3"
|
|||||||
### Special Keys
|
### Special Keys
|
||||||
|
|
||||||
`infotext` is used in Node Metadata to show a tooltip when hovering the crosshair over a node.
|
`infotext` is used in Node Metadata to show a tooltip when hovering the crosshair over a node.
|
||||||
This is useful in order to show the owner of the node or the status.
|
This is useful when showing the ownership or status of a node.
|
||||||
|
|
||||||
`description` is used in ItemStack Metadata to override the description when
|
`description` is used in ItemStack Metadata to override the description when
|
||||||
hovering over the stack in an inventory.
|
hovering over the stack in an inventory.
|
||||||
@ -109,10 +109,10 @@ item or node.
|
|||||||
Tables must be converted to strings before they can be stored.
|
Tables must be converted to strings before they can be stored.
|
||||||
Minetest offers two formats for doing this: Lua and JSON.
|
Minetest offers two formats for doing this: Lua and JSON.
|
||||||
|
|
||||||
The Lua method tends to be a lot faster and exactly matches the format Lua
|
The Lua method tends to be a lot faster and matches the format Lua
|
||||||
uses for tables.
|
uses for tables, while JSON is a more standard format, is better
|
||||||
JSON is a more standard format and is better structured, and so is well suited
|
structured, and is well suited when you need to exchange information
|
||||||
when you need to exchange information with another program.
|
with another program.
|
||||||
|
|
||||||
```lua
|
```lua
|
||||||
local data = { username = "player1", score = 1234 }
|
local data = { username = "player1", score = 1234 }
|
||||||
@ -164,8 +164,6 @@ If the mod is likely to be used on a server and will store lots of data,
|
|||||||
it's a good idea to offer a database storage method.
|
it's a good idea to offer a database storage method.
|
||||||
You should make this optional by separating how the data is stored and where
|
You should make this optional by separating how the data is stored and where
|
||||||
it is used.
|
it is used.
|
||||||
Using a database such as sqlite requires using the insecure environment, and
|
|
||||||
can be painful for the user to set up.
|
|
||||||
|
|
||||||
```lua
|
```lua
|
||||||
local backend
|
local backend
|
||||||
@ -200,11 +198,17 @@ return backend
|
|||||||
|
|
||||||
The backend_sqlite would do a similar thing, but use the Lua *lsqlite3* library
|
The backend_sqlite would do a similar thing, but use the Lua *lsqlite3* library
|
||||||
instead of mod storage.
|
instead of mod storage.
|
||||||
You'll need to request an insecure environment and require the library:
|
|
||||||
|
Using a database such as SQLite requires using an insecure environment.
|
||||||
|
An insecure environment is a table that is only available to mods
|
||||||
|
explicitly whitelisted by the user, and it contains a less restricted
|
||||||
|
copy of the Lua API which could be abused if available to malicious mods.
|
||||||
|
Insecure environments will be covered in more detail in the
|
||||||
|
[Security](../quality/security.html) chapter.
|
||||||
|
|
||||||
```lua
|
```lua
|
||||||
local ie = minetest.request_insecure_environment()
|
local ie = minetest.request_insecure_environment()
|
||||||
assert(ie, "Please add mymod to secure.trusted_mods")
|
assert(ie, "Please add mymod to secure.trusted_mods in the settings")
|
||||||
|
|
||||||
local _sql = ie.require("lsqlite3")
|
local _sql = ie.require("lsqlite3")
|
||||||
-- Prevent other mods from using the global sqlite3 library
|
-- Prevent other mods from using the global sqlite3 library
|
||||||
@ -214,8 +218,6 @@ end
|
|||||||
```
|
```
|
||||||
|
|
||||||
Teaching about SQL or how to use the lsqlite3 library is out of scope for this book.
|
Teaching about SQL or how to use the lsqlite3 library is out of scope for this book.
|
||||||
Insecure environments will be covered in more detail in the
|
|
||||||
[security](../quality/security.html) chapter.
|
|
||||||
|
|
||||||
## Deciding Which to Use
|
## Deciding Which to Use
|
||||||
|
|
||||||
@ -224,17 +226,21 @@ how it is formatted, and how large it is.
|
|||||||
As a guideline, small data is up to 10K, medium data is up to 10MB, and large
|
As a guideline, small data is up to 10K, medium data is up to 10MB, and large
|
||||||
data is any size above that.
|
data is any size above that.
|
||||||
|
|
||||||
Node metadata is a good choice when the data is related to the node.
|
Node metadata is a good choice when you need to store node-related data.
|
||||||
Storing medium data is fairly efficient if you make it private.
|
Storing medium data is fairly efficient if you make it private.
|
||||||
|
|
||||||
Item metadata should not be used to store anything but small amounts of data as it is not
|
Item metadata should not be used to store anything but small amounts of data as it is not
|
||||||
possible to avoid sending it to the client.
|
possible to avoid sending it to the client.
|
||||||
The data will also be copied every time the stack is moved, or is accessed from Lua.
|
The data will also be copied every time the stack is moved, or accessed from Lua.
|
||||||
|
|
||||||
Mod storage is good for medium data but writing large data may be inefficient.
|
Mod storage is good for medium data but writing large data may be inefficient.
|
||||||
It's better to use a database for large data, to avoid having to write all the
|
It's better to use a database for large data to avoid having to write all the
|
||||||
data out on every save.
|
data out on every save.
|
||||||
|
|
||||||
|
Databases are only viable for servers due to the
|
||||||
|
need to whitelist the mod to access an insecure environment.
|
||||||
|
They're well suited for large data sets.
|
||||||
|
|
||||||
## Your Turn
|
## Your Turn
|
||||||
|
|
||||||
* Make a node which disappears after it has been punched five times.
|
* Make a node which disappears after it has been punched five times.
|
||||||
|
@ -8,12 +8,12 @@ redirect_from: /en/chapters/chat.html
|
|||||||
cmd_online:
|
cmd_online:
|
||||||
level: warning
|
level: warning
|
||||||
title: Offline players can run commands
|
title: Offline players can run commands
|
||||||
message: <p>A player name is passed instead of a player object, because mods
|
message: <p>A player name is passed instead of a player object because mods
|
||||||
can run commands on behalf of offline players. For example, the IRC
|
can run commands on behalf of offline players. For example, the IRC
|
||||||
bridge allows players to run commands without joining the game.</p>
|
bridge allows players to run commands without joining the game.</p>
|
||||||
|
|
||||||
<p>So make sure that you don't assume that the player is online.
|
<p>So make sure that you don't assume that the player is online.
|
||||||
You can check by seeing if minetest.get_player_by_name returns a player.</p>
|
You can check by seeing if <pre>minetest.get_player_by_name</pre> returns a player.</p>
|
||||||
|
|
||||||
cb_cmdsprivs:
|
cb_cmdsprivs:
|
||||||
level: warning
|
level: warning
|
||||||
@ -27,7 +27,7 @@ cb_cmdsprivs:
|
|||||||
## Introduction
|
## Introduction
|
||||||
|
|
||||||
Mods can interact with player chat, including
|
Mods can interact with player chat, including
|
||||||
sending messages, intercepting messages and registering chat commands.
|
sending messages, intercepting messages, and registering chat commands.
|
||||||
|
|
||||||
* [Sending Messages to All Players](#sending-messages-to-all-players)
|
* [Sending Messages to All Players](#sending-messages-to-all-players)
|
||||||
* [Sending Messages to Specific Players](#sending-messages-to-specific-players)
|
* [Sending Messages to Specific Players](#sending-messages-to-specific-players)
|
||||||
@ -64,40 +64,25 @@ only visible to the named player, in this case player1.
|
|||||||
|
|
||||||
## Chat Commands
|
## Chat Commands
|
||||||
|
|
||||||
To register a chat command, for example /foo, use register_chatcommand:
|
To register a chat command, for example `/foo`, use `register_chatcommand`:
|
||||||
|
|
||||||
```lua
|
```lua
|
||||||
minetest.register_chatcommand("foo", {
|
minetest.register_chatcommand("foo", {
|
||||||
privs = {
|
privs = {
|
||||||
interact = true
|
interact = true,
|
||||||
},
|
},
|
||||||
func = function(name, param)
|
func = function(name, param)
|
||||||
return true, "You said " .. param .. "!"
|
return true, "You said " .. param .. "!"
|
||||||
end
|
end,
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
Calling /foo bar will display `You said bar!` in the chat console.
|
In the above snippet, `interact` is listed as a required
|
||||||
|
[privilege](privileges.html) meaning that only players with the `interact` privilege can run the command.
|
||||||
|
|
||||||
You can restrict which players are able to run commands:
|
Chat commands can return up to two values,
|
||||||
|
the first being a Boolean indicating success, and the second being a
|
||||||
```lua
|
message to send to the user.
|
||||||
privs = {
|
|
||||||
interact = true
|
|
||||||
},
|
|
||||||
```
|
|
||||||
|
|
||||||
This means only players with the `interact` [privilege](privileges.html) can run the
|
|
||||||
command. Other players will see an error message informing them of which
|
|
||||||
privilege they're missing. If the player has the necessary privileges, the command
|
|
||||||
will run and the message will be sent:
|
|
||||||
|
|
||||||
```lua
|
|
||||||
return true, "You said " .. param .. "!"
|
|
||||||
```
|
|
||||||
|
|
||||||
This returns two values, a Boolean which shows the command succeeded
|
|
||||||
and the chat message to send to the player.
|
|
||||||
|
|
||||||
{% include notice.html notice=page.cmd_online %}
|
{% include notice.html notice=page.cmd_online %}
|
||||||
|
|
||||||
@ -117,30 +102,32 @@ Patterns are a way of extracting stuff from text using rules.
|
|||||||
local to, msg = string.match(param, "^([%a%d_-]+) (*+)$")
|
local to, msg = string.match(param, "^([%a%d_-]+) (*+)$")
|
||||||
```
|
```
|
||||||
|
|
||||||
The above implements `/msg <to> <message>`. Let's go through left to right:
|
The above code implements `/msg <to> <message>`. Let's go through left to right:
|
||||||
|
|
||||||
* `^` means match the start of the string.
|
* `^` means match the start of the string.
|
||||||
* `()` is a matching group - anything that matches stuff in here will be
|
* `()` is a matching group - anything that matches stuff in here will be
|
||||||
returned from string.match.
|
returned from string.match.
|
||||||
* `[]` means accept characters in this list.
|
* `[]` means accept characters in this list.
|
||||||
* `%a` means accept any letter and `%d` means any digit.
|
* `%a` means accept any letter and `%d` means accept any digit.
|
||||||
* `[%d%a_-]` means accept any letter or digit or `_` or `-`.
|
* `[%d%a_-]` means accept any letter or digit or `_` or `-`.
|
||||||
* `+` means match the last thing one or more times.
|
* `+` means match the thing before one or more times.
|
||||||
* `*` means match any character in this context.
|
* `*` means match any character in this context.
|
||||||
* `$` means match the end of the string.
|
* `$` means match the end of the string.
|
||||||
|
|
||||||
Put simply, this matches the name (a word with only letters/numbers/-/_),
|
Put simply, the pattern matches the name (a word with only letters/numbers/-/_),
|
||||||
then a space, then the message (one of more of any character). The name and
|
then a space, then the message (one or more of any character). The name and
|
||||||
message are returned, as they're surrounded in parentheses.
|
message are returned, because they're surrounded by parentheses.
|
||||||
|
|
||||||
That's how most mods implement complex chat commands. A better guide to Lua
|
That's how most mods implement complex chat commands. A better guide to Lua
|
||||||
Patterns would probably be the
|
Patterns would probably be the
|
||||||
[lua-users.org tutorial](http://lua-users.org/wiki/PatternsTutorial)
|
[lua-users.org tutorial](http://lua-users.org/wiki/PatternsTutorial)
|
||||||
or the [PIL documentation](https://www.lua.org/pil/20.2.html).
|
or the [PIL documentation](https://www.lua.org/pil/20.2.html).
|
||||||
|
|
||||||
|
<p class="book_hide">
|
||||||
There is also a library written by the author of this book which can be used
|
There is also a library written by the author of this book which can be used
|
||||||
to make complex chat commands without Patterns called
|
to make complex chat commands without patterns called
|
||||||
[ChatCmdBuilder](chat_complex.html).
|
<a href="chat_complex.html">Chat Command Builder</a>.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
|
||||||
## Intercepting Messages
|
## Intercepting Messages
|
||||||
@ -155,8 +142,8 @@ end)
|
|||||||
```
|
```
|
||||||
|
|
||||||
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
|
||||||
work the same.
|
work the same, because `nil` is returned implicitly and is treated like false.
|
||||||
|
|
||||||
{% include notice.html notice=page.cb_cmdsprivs %}
|
{% include notice.html notice=page.cb_cmdsprivs %}
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ redirect_from: /en/chapters/hud.html
|
|||||||
|
|
||||||
Heads Up Display (HUD) elements allow you to show text, images, and other graphical elements.
|
Heads Up Display (HUD) elements allow you to show text, images, and other graphical elements.
|
||||||
|
|
||||||
The HUD doesn't accept user input. For that, you should use a [Formspec](formspecs.html).
|
The HUD doesn't accept user input; for that, you should use a [formspec](formspecs.html).
|
||||||
|
|
||||||
* [Positioning](#positioning)
|
* [Positioning](#positioning)
|
||||||
* [Position and Offset](#position-and-offset)
|
* [Position and Offset](#position-and-offset)
|
||||||
@ -43,8 +43,8 @@ the HUD needs to work well on all screen types.
|
|||||||
Minetest's solution to this is to specify the location of an element using both
|
Minetest's solution to this is to specify the location of an element using both
|
||||||
a percentage position and an offset.
|
a percentage position and an offset.
|
||||||
The percentage position is relative to the screen size, so to place an element
|
The percentage position is relative to the screen size, so to place an element
|
||||||
in the center of the screen you would need to provide a percentage position of half
|
in the centre of the screen, you would need to provide a percentage position of half
|
||||||
the screen, eg (50%, 50%), and an offset of (0, 0).
|
the screen, e.g. (50%, 50%), and an offset of (0, 0).
|
||||||
|
|
||||||
The offset is then used to move an element relative to the percentage position.
|
The offset is then used to move an element relative to the percentage position.
|
||||||
|
|
||||||
@ -55,7 +55,7 @@ The offset is then used to move an element relative to the percentage position.
|
|||||||
Alignment is where the result of position and offset is on the element -
|
Alignment is where the result of position and offset is on the element -
|
||||||
for example, `{x = -1.0, y = 0.0}` will make the result of position and offset point
|
for example, `{x = -1.0, y = 0.0}` will make the result of position and offset point
|
||||||
to the left of the element's bounds. This is particularly useful when you want to
|
to the left of the element's bounds. This is particularly useful when you want to
|
||||||
make a text element left, center, or right justified.
|
make a text element aligned to the left, centre, or right.
|
||||||
|
|
||||||
<figure>
|
<figure>
|
||||||
<img
|
<img
|
||||||
@ -65,7 +65,7 @@ make a text element left, center, or right justified.
|
|||||||
</figure>
|
</figure>
|
||||||
|
|
||||||
The above diagram shows 3 windows (blue), each with a single HUD element (yellow)
|
The above diagram shows 3 windows (blue), each with a single HUD element (yellow)
|
||||||
with a different alignment each time. The arrow is the result of the position
|
and a different alignment each time. The arrow is the result of the position
|
||||||
and offset calculation.
|
and offset calculation.
|
||||||
|
|
||||||
### Scoreboard
|
### Scoreboard
|
||||||
@ -79,7 +79,7 @@ score panel like so:
|
|||||||
alt="screenshot of the HUD we're aiming for">
|
alt="screenshot of the HUD we're aiming for">
|
||||||
</figure>
|
</figure>
|
||||||
|
|
||||||
In the above screenshot all the elements have the same percentage position -
|
In the above screenshot, all the elements have the same percentage position
|
||||||
(100%, 50%) - but different offsets. This allows the whole thing to be anchored
|
(100%, 50%) - but different offsets. This allows the whole thing to be anchored
|
||||||
to the right of the window, but to resize without breaking.
|
to the right of the window, but to resize without breaking.
|
||||||
|
|
||||||
@ -107,16 +107,21 @@ or remove a HUD element.
|
|||||||
The element's type is given using the `hud_elem_type` property in the definition
|
The element's type is given using the `hud_elem_type` property in the definition
|
||||||
table. The meaning of other properties varies based on this type.
|
table. The meaning of other properties varies based on this type.
|
||||||
|
|
||||||
`scale` is the maximum bounds of text, text outside these bounds is cropped, eg: `{x=100, y=100}`.
|
`scale` is the maximum bounds of text; text outside these bounds is cropped, e.g.: `{x=100, y=100}`.
|
||||||
|
|
||||||
`number` is the text's colour, and is in [Hexadecimal form](http://www.colorpicker.com/), eg: `0xFF0000`.
|
`number` is the text's colour, and is in [hexadecimal form](http://www.colorpicker.com/), e.g.: `0xFF0000`.
|
||||||
|
|
||||||
|
|
||||||
### Our Example
|
### Our Example
|
||||||
|
|
||||||
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:
|
||||||
|
|
||||||
```lua
|
```lua
|
||||||
|
-- Get the dig and place count from storage, or default to 0
|
||||||
|
local meta = player:get_meta()
|
||||||
|
local digs_text = "Digs: " .. meta:get_int("score:digs")
|
||||||
|
local places_text = "Places: " .. meta:get_int("score:places")
|
||||||
|
|
||||||
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},
|
||||||
@ -127,12 +132,6 @@ player:hud_add({
|
|||||||
number = 0xFFFFFF,
|
number = 0xFFFFFF,
|
||||||
})
|
})
|
||||||
|
|
||||||
-- Get the dig and place count from storage, or default to 0
|
|
||||||
local digs = tonumber(player:get_attribute("score:digs") or 0)
|
|
||||||
local digs_text = "Digs: " .. digs
|
|
||||||
local places = tonumber(player:get_attribute("score:digs") or 0)
|
|
||||||
local places_text = "Places: " .. places
|
|
||||||
|
|
||||||
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},
|
||||||
@ -191,8 +190,8 @@ You will now have this:
|
|||||||
The `text` field is used to provide the image name.
|
The `text` field is used to provide the image name.
|
||||||
|
|
||||||
If a co-ordinate is positive, then it is a scale factor with 1 being the
|
If a co-ordinate is positive, then it is a scale factor with 1 being the
|
||||||
original image size, and 2 being double the size, and so on.
|
original image size, 2 being double the size, and so on.
|
||||||
However, if a co-ordinate is negative it is a percentage of the screensize.
|
However, if a co-ordinate is negative, it is a percentage of the screensize.
|
||||||
For example, `x=-100` is 100% of the width.
|
For example, `x=-100` is 100% of the width.
|
||||||
|
|
||||||
### Scale
|
### Scale
|
||||||
@ -200,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:
|
||||||
|
|
||||||
```lua
|
```lua
|
||||||
local percent = tonumber(player:get_attribute("score:score") or 0.2)
|
local percent = tonumber(meta:get("score:score") or 0.2)
|
||||||
|
|
||||||
player:hud_add({
|
player:hud_add({
|
||||||
hud_elem_type = "image",
|
hud_elem_type = "image",
|
||||||
@ -226,7 +225,7 @@ There is one problem however, it won't update when the stats change.
|
|||||||
|
|
||||||
## Changing an Element
|
## Changing an Element
|
||||||
|
|
||||||
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 it or remove it later.
|
||||||
|
|
||||||
```lua
|
```lua
|
||||||
local idx = player:hud_add({
|
local idx = player:hud_add({
|
||||||
@ -261,12 +260,11 @@ local saved_huds = {}
|
|||||||
function score.update_hud(player)
|
function score.update_hud(player)
|
||||||
local player_name = player:get_player_name()
|
local player_name = player:get_player_name()
|
||||||
|
|
||||||
local digs = tonumber(player:get_attribute("score:digs") or 0)
|
-- Get the dig and place count from storage, or default to 0
|
||||||
local digs_text = "Digs: " .. digs
|
local meta = player:get_meta()
|
||||||
local places = tonumber(player:get_attribute("score:digs") or 0)
|
local digs_text = "Digs: " .. meta:get_int("score:digs")
|
||||||
local places_text = "Places: " .. places
|
local places_text = "Places: " .. meta:get_int("score:places")
|
||||||
|
local percent = tonumber(meta:get("score:score") or 0.2)
|
||||||
local percent = tonumber(player:get_attribute("score:score") or 0.2)
|
|
||||||
|
|
||||||
local ids = saved_huds[player_name]
|
local ids = saved_huds[player_name]
|
||||||
if ids then
|
if ids then
|
||||||
|
@ -8,14 +8,16 @@ redirect_from: /en/chapters/player_physics.html
|
|||||||
|
|
||||||
## Introduction
|
## Introduction
|
||||||
|
|
||||||
Player physics can be modified using physics overrides. Physics overrides can set the
|
Player physics can be modified using physics overrides.
|
||||||
walking speed, jump speed and gravity constants. Physics overrides are set on a player
|
Physics overrides can set the walking speed, jump speed,
|
||||||
by player basis, and are multipliers. For example, a value of 2 for gravity would make
|
and gravity constants.
|
||||||
gravity twice as strong.
|
Physics overrides are set on a player-by-player basis,
|
||||||
|
and are multipliers.
|
||||||
|
For example, a value of 2 for gravity would make gravity twice as strong.
|
||||||
|
|
||||||
* [Basic Example](#basic_example)
|
* [Basic Example](#basic_example)
|
||||||
* [Available Overrides](#available_overrides)
|
* [Available Overrides](#available_overrides)
|
||||||
* [Mod Incompatibility ](#mod_incompatibility)
|
* [Mod Incompatibility](#mod_incompatibility)
|
||||||
* [Your Turn](#your_turn)
|
* [Your Turn](#your_turn)
|
||||||
|
|
||||||
## Basic Example
|
## Basic Example
|
||||||
@ -28,16 +30,16 @@ 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)
|
||||||
player:set_physics_override({
|
player:set_physics_override({
|
||||||
gravity = 0.1 -- set gravity to 10% of its original value
|
gravity = 0.1, -- set gravity to 10% of its original value
|
||||||
-- (0.1 * 9.81)
|
-- (0.1 * 9.81)
|
||||||
})
|
})
|
||||||
end
|
end,
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
## Available Overrides
|
## Available Overrides
|
||||||
|
|
||||||
player:set_physics_override() is given a table of overrides.\\
|
`player:set_physics_override()` is given a table of overrides.\\
|
||||||
According to [lua_api.txt]({{ page.root }}/lua_api.html#player-only-no-op-for-other-objects),
|
According to [lua_api.txt]({{ page.root }}/lua_api.html#player-only-no-op-for-other-objects),
|
||||||
these can be:
|
these can be:
|
||||||
|
|
||||||
@ -57,7 +59,7 @@ unintended, it has been preserved in overrides due to its use on many servers.
|
|||||||
Two overrides are needed to fully restore old movement behaviour:
|
Two overrides are needed to fully restore old movement behaviour:
|
||||||
|
|
||||||
* new_move: whether the player uses new movement (default: true)
|
* new_move: whether the player uses new movement (default: true)
|
||||||
* sneak_glitch: whether the player can use "sneak elevators" (default: false)
|
* sneak_glitch: whether the player can use 'sneak elevators' (default: false)
|
||||||
|
|
||||||
## Mod Incompatibility
|
## Mod Incompatibility
|
||||||
|
|
||||||
@ -69,5 +71,5 @@ player's speed, only the last one to run will be in effect.
|
|||||||
## Your Turn
|
## Your Turn
|
||||||
|
|
||||||
* **Sonic**: Set the speed multiplier to a high value (at least 6) when a player joins the game.
|
* **Sonic**: Set the speed multiplier to a high value (at least 6) when a player joins the game.
|
||||||
* **Super bounce**: Increase the jump value so that the player can jump 20 meters (1 meter is 1 node).
|
* **Super bounce**: Increase the jump value so that the player can jump 20 metres (1 metre is 1 node).
|
||||||
* **Space**: Make gravity decrease as the player gets higher.
|
* **Space**: Make gravity decrease as the player gets higher.
|
||||||
|
@ -12,10 +12,10 @@ Simple Fast Inventory (SFINV) is a mod found in Minetest Game that is used to
|
|||||||
create the player's inventory [formspec](formspecs.html). SFINV comes with
|
create the player's inventory [formspec](formspecs.html). SFINV comes with
|
||||||
an API that allows you to add and otherwise manage the pages shown.
|
an API that allows you to add and otherwise manage the pages shown.
|
||||||
|
|
||||||
Whilst SFINV by default shows pages as tabs, pages are called "pages" as
|
Whilst SFINV by default shows pages as tabs, pages are called pages
|
||||||
it's entirely possible that a mod or game decides to show them in
|
because it is entirely possible that a mod or game decides to show them in
|
||||||
some other format instead.
|
some other format instead.
|
||||||
For example, multiple pages could be shown on one view.
|
For example, multiple pages could be shown in one formspec.
|
||||||
|
|
||||||
* [Registering a Page](#registering-a-page)
|
* [Registering a Page](#registering-a-page)
|
||||||
* [Receiving events](#receiving-events)
|
* [Receiving events](#receiving-events)
|
||||||
@ -25,7 +25,7 @@ For example, multiple pages could be shown on one view.
|
|||||||
## Registering a Page
|
## Registering a Page
|
||||||
|
|
||||||
SFINV provides the aptly named `sfinv.register_page` function to create pages.
|
SFINV provides the aptly named `sfinv.register_page` function to create pages.
|
||||||
Simply call the function with the page's name, and its definition:
|
Simply call the function with the page's name and its definition:
|
||||||
|
|
||||||
```lua
|
```lua
|
||||||
sfinv.register_page("mymod:hello", {
|
sfinv.register_page("mymod:hello", {
|
||||||
@ -41,7 +41,7 @@ The `make_formspec` function surrounds your formspec with SFINV's formspec code.
|
|||||||
The fourth parameter, currently set as `true`, determines whether the
|
The fourth parameter, currently set as `true`, determines whether the
|
||||||
player's inventory is shown.
|
player's inventory is shown.
|
||||||
|
|
||||||
Let's make things more exciting. Here is the code for the formspec generation
|
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.
|
||||||
|
|
||||||
@ -83,14 +83,11 @@ sfinv.register_page("myadmin:myadmin", {
|
|||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
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
|
||||||
in previous chapters.
|
covered above and in previous chapters.
|
||||||
|
|
||||||
<figure>
|
<figure>
|
||||||
<img src="{{ page.root }}//static/sfinv_admin_fs.png" alt="Player Admin Page">
|
<img src="{{ page.root }}//static/sfinv_admin_fs.png" alt="Player Admin Page">
|
||||||
<figcaption>
|
|
||||||
The player admin page created above.
|
|
||||||
</figcaption>
|
|
||||||
</figure>
|
</figure>
|
||||||
|
|
||||||
## Receiving events
|
## Receiving events
|
||||||
@ -104,10 +101,10 @@ on_player_receive_fields = function(self, player, context, fields)
|
|||||||
end,
|
end,
|
||||||
```
|
```
|
||||||
|
|
||||||
Fields is the exact same as the fields given to the subscribers of
|
`on_player_receive_fields` works the same as
|
||||||
`minetest.register_on_player_receive_fields`. The return value of
|
`minetest.register_on_player_receive_fields`, except that `context` is
|
||||||
`on_player_receive_fields` is the same as a normal player receive fields.
|
given instead of `formname`.
|
||||||
Please note that sfinv will consume events relevant to itself, such as
|
Please note that SFINV will consume events relevant to itself, such as
|
||||||
navigation tab events, so you won't receive them in this callback.
|
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:
|
||||||
@ -166,7 +163,7 @@ If you only need to check one priv or want to perform an 'and', you should use
|
|||||||
|
|
||||||
Note that the `is_in_nav` is only called when the player's inventory formspec is
|
Note that the `is_in_nav` is only called when the player's inventory formspec is
|
||||||
generated. This happens when a player joins the game, switches tabs, or a mod
|
generated. This happens when a player joins the game, switches tabs, or a mod
|
||||||
requests it using SFINV's API.
|
requests for SFINV to regenerate.
|
||||||
|
|
||||||
This means that you need to manually request that SFINV regenerates the inventory
|
This means that you need to manually request that SFINV regenerates the inventory
|
||||||
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,
|
||||||
|
@ -33,7 +33,7 @@ code is thrown in together with no clear boundaries. This ultimately makes a
|
|||||||
project completely unmaintainable, ending in its abandonment.
|
project completely unmaintainable, ending in its abandonment.
|
||||||
|
|
||||||
The opposite of this is to design your project as a collection of interacting
|
The opposite of this is to design your project as a collection of interacting
|
||||||
smaller programs or areas of code.
|
smaller programs or areas of code. <!-- Weird wording? -->
|
||||||
|
|
||||||
> Inside every large program, there is a small program trying to get out.
|
> Inside every large program, there is a small program trying to get out.
|
||||||
>
|
>
|
||||||
@ -51,14 +51,13 @@ a low amount of coupling, as this means that changing the APIs of certain areas
|
|||||||
will be more feasible.
|
will be more feasible.
|
||||||
|
|
||||||
Note that these apply both when thinking about the relationship between mods,
|
Note that these apply both when thinking about the relationship between mods,
|
||||||
and the relationship between areas inside a mod. In both cases you should try
|
and the relationship between areas inside a mod.
|
||||||
to get high cohesion and low coupling.
|
|
||||||
|
|
||||||
|
|
||||||
## Model-View-Controller
|
## Model-View-Controller
|
||||||
|
|
||||||
In the next chapter we will discuss how to automatically test your code, and one
|
In the next chapter, we will discuss how to automatically test your
|
||||||
of the problems we will have is how to separate your logic
|
code and one of the problems we will have is how to separate your logic
|
||||||
(calculations, what should be done) from API calls (`minetest.*`, other mods)
|
(calculations, what should be done) from API calls (`minetest.*`, other mods)
|
||||||
as much as possible.
|
as much as possible.
|
||||||
|
|
||||||
@ -75,7 +74,7 @@ and any associated metadata. Actions you can take are `create`, `edit`, or
|
|||||||
receive fields. These are 3 areas that can usually be separated pretty well.
|
receive fields. These are 3 areas that can usually be separated pretty well.
|
||||||
|
|
||||||
In your tests, you will be able to make sure that an action when triggered does
|
In your tests, you will be able to make sure that an action when triggered does
|
||||||
the right thing to the data, but you won't need to test that an event calls an
|
the right thing to the data. You won't need to test that an event calls an
|
||||||
action (as this would require using the Minetest API, and this area of code
|
action (as this would require using the Minetest API, and this area of code
|
||||||
should be made as small as possible anyway.)
|
should be made as small as possible anyway.)
|
||||||
|
|
||||||
@ -98,8 +97,8 @@ function land.get_by_name(area_name)
|
|||||||
end
|
end
|
||||||
```
|
```
|
||||||
|
|
||||||
Your actions should also be pure, however calling other functions is more
|
Your actions should also be pure, but calling other functions is more
|
||||||
acceptable.
|
acceptable than in the above.
|
||||||
|
|
||||||
```lua
|
```lua
|
||||||
-- Controller
|
-- Controller
|
||||||
@ -155,7 +154,7 @@ most of the calculations are made.
|
|||||||
The controller should have no knowledge about the Minetest API - notice how
|
The controller should have no knowledge about the Minetest API - notice how
|
||||||
there are no Minetest calls or any view functions that resemble them.
|
there are no Minetest calls or any view functions that resemble them.
|
||||||
You should *NOT* have a function like `view.hud_add(player, def)`.
|
You should *NOT* have a function like `view.hud_add(player, def)`.
|
||||||
Instead, the view defines some actions the controller can tell the view to do,
|
Instead, the view defines some actions that the controller can tell the view to do,
|
||||||
like `view.add_hud(info)` where info is a value or table which doesn't relate
|
like `view.add_hud(info)` where info is a value or table which doesn't relate
|
||||||
to the Minetest API at all.
|
to the Minetest API at all.
|
||||||
|
|
||||||
@ -167,27 +166,27 @@ to the Minetest API at all.
|
|||||||
</figure>
|
</figure>
|
||||||
|
|
||||||
It is important that each area only communicates with its direct neighbours,
|
It is important that each area only communicates with its direct neighbours,
|
||||||
as shown above, in order to reduce how much you needs to change if you modify
|
as shown above, in order to reduce how much you need to change if you modify
|
||||||
an area's internals or externals. For example, to change the formspec you
|
an area's internals or externals. For example, to change the formspec you
|
||||||
would only need to edit the view. To change the view API, you would only need to
|
would only need to edit the view. To change the view API, you would only need to
|
||||||
change the view and the controller, but not the model at all.
|
change the view and the controller, but not the model at all.
|
||||||
|
|
||||||
In practice, this design is rarely used because of the increased complexity
|
In practice, this design is rarely used because of the increased complexity
|
||||||
and because it doesn't give many benefits for most types of mods. Instead,
|
and because it doesn't give many benefits for most types of mods. Instead,
|
||||||
you tend to see a lot more of a less formal and strict kind of design -
|
you will commonly see a less formal and strict kind of design -
|
||||||
varients of the API-View.
|
variants of the API-View.
|
||||||
|
|
||||||
|
|
||||||
### API-View
|
### API-View
|
||||||
|
|
||||||
In an ideal world, you'd have the above 3 areas perfectly separated with all
|
In an ideal world, you'd have the above 3 areas perfectly separated with all
|
||||||
events going into the controller before going back to the normal view. But
|
events going into the controller before going back to the normal view. But
|
||||||
this isn't the real world. A good half-way house is to reduce the mod into 2
|
this isn't the real world. A good compromise is to reduce the mod into two
|
||||||
parts:
|
parts:
|
||||||
|
|
||||||
* **API** - what was the model and controller. There should be no uses of
|
* **API** - This was the model and controller above. There should be no uses of
|
||||||
`minetest.` here.
|
`minetest.` here.
|
||||||
* **View** - the view as before. It's a good idea to structure this into separate
|
* **View** - This was also the view above. It's a good idea to structure this into separate
|
||||||
files for each type of event.
|
files for each type of event.
|
||||||
|
|
||||||
rubenwardy's [crafting mod](https://github.com/rubenwardy/crafting) roughly
|
rubenwardy's [crafting mod](https://github.com/rubenwardy/crafting) roughly
|
||||||
@ -204,40 +203,38 @@ as it doesn't use any Minetest APIs - as shown in the
|
|||||||
## Observer
|
## Observer
|
||||||
|
|
||||||
Reducing coupling may seem hard to do to begin with, but you'll make a lot of
|
Reducing coupling may seem hard to do to begin with, but you'll make a lot of
|
||||||
progress by splitting your code up well using a design like the one given above.
|
progress by splitting your code up using a design like the one given above.
|
||||||
It's not always possible to remove the need for one area to communicate with
|
It's not always possible to remove the need for one area to communicate with
|
||||||
another, but there are ways to decouple anyway - one such way being the Observer
|
another, but there are ways to decouple anyway - one example being the Observer
|
||||||
pattern.
|
pattern.
|
||||||
|
|
||||||
Let's take the example of unlocking an achievement when a player first kills a
|
Let's take the example of unlocking an achievement when a player first kills a
|
||||||
rare animal. The naive approach would be to have achievement code in the mob
|
rare animal. The naïve approach would be to have achievement code in the mob
|
||||||
kill function, checking the mob name and unlocking the award if it matches.
|
kill function, checking the mob name and unlocking the award if it matches.
|
||||||
This is a bad idea however, as it makes the mobs mod coupled to the achievements
|
This is a bad idea however, as it makes the mobs mod coupled to the achievements
|
||||||
code. If you kept on doing this - for example, adding XP to the mob death code -
|
code. If you kept on doing this - for example, adding XP to the mob death code -
|
||||||
you could end up with a lot of messy dependencies.
|
you could end up with a lot of messy dependencies.
|
||||||
|
|
||||||
Enter the Observer pattern. Instead of the mobs mod caring about awards, mobs
|
Enter the Observer pattern. Instead of the mymobs mod caring about awards,
|
||||||
exposes a way for other areas of code to register their interest in an event
|
the mymobs mod exposes a way for other areas of code to register their
|
||||||
and receive data about the event.
|
interest in an event and receive data about the event.
|
||||||
|
|
||||||
```lua
|
```lua
|
||||||
mobs.registered_on_death = {}
|
mymobs.registered_on_death = {}
|
||||||
function mobs.register_on_death(func)
|
function mymobs.register_on_death(func)
|
||||||
table.insert(mobs.registered_on_death, func)
|
table.insert(mymobs.registered_on_death, func)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- mob death code
|
-- mob death code
|
||||||
for i=1, #mobs.registered_on_death do
|
for i=1, #mymobs.registered_on_death do
|
||||||
mobs.registered_on_death[i](entity, reason)
|
mymobs.registered_on_death[i](entity, reason)
|
||||||
end
|
end
|
||||||
```
|
```
|
||||||
|
|
||||||
Then the other code registers its interest:
|
Then the other code registers its interest:
|
||||||
|
|
||||||
```lua
|
```lua
|
||||||
|
mymobs.register_on_death(function(mob, reason)
|
||||||
-- awards
|
|
||||||
mobs.register_on_death(function(mob, reason)
|
|
||||||
if reason.type == "punch" and reason.object and
|
if reason.type == "punch" and reason.object and
|
||||||
reason.object:is_player() then
|
reason.object:is_player() then
|
||||||
awards.notify_mob_kill(reason.object, mob.name)
|
awards.notify_mob_kill(reason.object, mob.name)
|
||||||
@ -246,12 +243,12 @@ end)
|
|||||||
```
|
```
|
||||||
|
|
||||||
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
|
||||||
what is listening to something.
|
what is listening to something.
|
||||||
|
|
||||||
## Conclusion
|
## Conclusion
|
||||||
|
|
||||||
Good code design is subjective, and depends on the project you're making. As a
|
Good code design is subjective, and highly depends on the project you're making. As a
|
||||||
general rule, try to keep cohesion high and coupling low. Phrased differently,
|
general rule, try to keep cohesion high and coupling low. Phrased differently,
|
||||||
keep related code together and unrelated code apart, and keep dependencies simple.
|
keep related code together and unrelated code apart, and keep dependencies simple.
|
||||||
|
|
||||||
|
@ -40,7 +40,7 @@ minetest.register_on_joinplayer(function(player)
|
|||||||
end)
|
end)
|
||||||
```
|
```
|
||||||
|
|
||||||
Do this:
|
Do this instead:
|
||||||
|
|
||||||
```lua
|
```lua
|
||||||
minetest.register_on_joinplayer(function(player)
|
minetest.register_on_joinplayer(function(player)
|
||||||
@ -62,10 +62,10 @@ end)
|
|||||||
|
|
||||||
## Don't Trust Formspec Submissions
|
## Don't Trust Formspec Submissions
|
||||||
|
|
||||||
Malicious clients can submit formspecs whenever they like with whatever content
|
Malicious clients can submit formspecs whenever they like, with
|
||||||
they like.
|
whatever content they like.
|
||||||
|
|
||||||
For example, the following code has a vulnerability which will allow players to
|
For example, the following code has a vulnerability which allows players to
|
||||||
give themselves moderator privileges:
|
give themselves moderator privileges:
|
||||||
|
|
||||||
```lua
|
```lua
|
||||||
@ -123,7 +123,7 @@ stack:get_meta():set_string("description", "Partially eaten")
|
|||||||
-- BAD! Modification will be lost
|
-- BAD! Modification will be lost
|
||||||
```
|
```
|
||||||
|
|
||||||
Do this:
|
Do this instead:
|
||||||
|
|
||||||
```lua
|
```lua
|
||||||
local inv = player:get_inventory()
|
local inv = player:get_inventory()
|
||||||
@ -137,8 +137,6 @@ The behaviour of callbacks is slightly more complicated. Modifying an `ItemStack
|
|||||||
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,
|
||||||
it will only be saved in the engine if the callback caller sets it.
|
it will only be saved in the engine if the callback caller sets it.
|
||||||
|
|
||||||
Avoid this:
|
|
||||||
|
|
||||||
```lua
|
```lua
|
||||||
minetest.register_on_item_eat(function(hp_change, replace_with_item,
|
minetest.register_on_item_eat(function(hp_change, replace_with_item,
|
||||||
itemstack, user, pointed_thing)
|
itemstack, user, pointed_thing)
|
||||||
@ -149,7 +147,7 @@ end)
|
|||||||
```
|
```
|
||||||
|
|
||||||
If no callbacks cancel this, the stack will be set and the description will be updated,
|
If no callbacks cancel this, the stack will be set and the description will be updated,
|
||||||
but if a callback cancels this, then the update may be lost.
|
but if a callback does cancel this, then the update may be lost.
|
||||||
|
|
||||||
It's better to do this instead:
|
It's better to do this instead:
|
||||||
|
|
||||||
|
@ -88,10 +88,10 @@ a look at the list below.
|
|||||||
### Troubleshooting
|
### Troubleshooting
|
||||||
|
|
||||||
* **accessing undefined variable foobar** - If `foobar` is meant to be a global,
|
* **accessing undefined variable foobar** - If `foobar` is meant to be a global,
|
||||||
then add it to `read_globals`. Otherwise, add any missing `local`s to the mod.
|
add it to `read_globals`. Otherwise, add any missing `local`s to the mod.
|
||||||
* **setting non-standard global variable foobar** - If `foobar` is meant to be a global,
|
* **setting non-standard global variable foobar** - If `foobar` is meant to be a global,
|
||||||
then add it to `globals`. Remove from `read_globals` if present there.
|
add it to `globals`. Remove from `read_globals` if present.
|
||||||
Otherwise add any missing `local`s to the mod.
|
Otherwise, add any missing `local`s to the mod.
|
||||||
* **mutating read-only global variable 'foobar'** - Move `foobar` from `read_globals` to
|
* **mutating read-only global variable 'foobar'** - Move `foobar` from `read_globals` to
|
||||||
`globals`.
|
`globals`.
|
||||||
|
|
||||||
@ -101,16 +101,16 @@ It is highly recommended that you find and install a plugin for your editor of c
|
|||||||
to show you errors without running a command. Most editors will likely have a plugin
|
to show you errors without running a command. Most editors will likely have a plugin
|
||||||
available.
|
available.
|
||||||
|
|
||||||
* **Atom** - `linter-luacheck`
|
* **Atom** - `linter-luacheck`.
|
||||||
* **Sublime** - Install using package-control:
|
* **Sublime** - Install using package-control:
|
||||||
[SublimeLinter](https://github.com/SublimeLinter/SublimeLinter),
|
[SublimeLinter](https://github.com/SublimeLinter/SublimeLinter),
|
||||||
[SublimeLinter-luacheck](https://github.com/SublimeLinter/SublimeLinter-luacheck)
|
[SublimeLinter-luacheck](https://github.com/SublimeLinter/SublimeLinter-luacheck).
|
||||||
|
|
||||||
## Checking Commits with Travis
|
## Checking Commits with Travis
|
||||||
|
|
||||||
If your project is public and is on Github, you can use TravisCI - a free service
|
If your project is public and is on Github, you can use TravisCI - a free service
|
||||||
to run jobs on commits to check them. This means that every commit you push will
|
to run jobs on commits to check them. This means that every commit you push will
|
||||||
be checked against LuaCheck, and a green tick or red cross displayed next to them
|
be checked against LuaCheck, and a green tick or red cross will be displayed next to them
|
||||||
depending on whether LuaCheck finds any mistakes. This is especially helpful for
|
depending on whether LuaCheck finds any mistakes. This is especially helpful for
|
||||||
when your project receives a pull request - you'll be able to see the LuaCheck output
|
when your project receives a pull request - you'll be able to see the LuaCheck output
|
||||||
without downloading the code.
|
without downloading the code.
|
||||||
@ -144,7 +144,7 @@ change the line after `script:` to:
|
|||||||
```
|
```
|
||||||
|
|
||||||
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.
|
||||||
After a while it should change either into a green tick or a red cross depending on the
|
After a while it should change either into a green tick or a red cross depending on the
|
||||||
outcome of LuaCheck. In either case, you can click the icon to see the build logs
|
outcome of LuaCheck. In either case, you can click the icon to see the build logs
|
||||||
and the output of LuaCheck.
|
and the output of LuaCheck.
|
||||||
|
@ -29,7 +29,7 @@ but can be suitable choices for artistic works such as images, text and meshes.
|
|||||||
|
|
||||||
You are allowed any license; however, mods which disallow derivatives are banned from the
|
You are allowed any license; however, mods which disallow derivatives are banned from the
|
||||||
official Minetest forum. (For a mod to be allowed on the forum, other developers must be
|
official Minetest forum. (For a mod to be allowed on the forum, other developers must be
|
||||||
able modify it and release the modified version.)
|
able to modify it and release the modified version.)
|
||||||
|
|
||||||
Please note that **public domain is not a valid licence**, because the definition varies
|
Please note that **public domain is not a valid licence**, because the definition varies
|
||||||
in different countries.
|
in different countries.
|
||||||
@ -37,17 +37,18 @@ in different countries.
|
|||||||
### LGPL and CC-BY-SA
|
### LGPL and CC-BY-SA
|
||||||
|
|
||||||
This is a common license combination in the Minetest community, and is what
|
This is a common license combination in the Minetest community, and is what
|
||||||
Minetest and minetest_game use.
|
Minetest and Minetest Game use.
|
||||||
You license your code under LGPL 2.1 and your art under CC-BY-SA. This means:
|
You license your code under LGPL 2.1 and your art under CC-BY-SA.
|
||||||
|
This means that:
|
||||||
|
|
||||||
* Anyone can modify, redistribute and sell modified or unmodified versions.
|
* Anyone can modify, redistribute and sell modified or unmodified versions.
|
||||||
* If someone modifies your mod, they must give their version the same license.
|
* If someone modifies your mod, they must give their version the same license.
|
||||||
* Your copyright notice must be kept.
|
* Your copyright notice must be kept.
|
||||||
|
|
||||||
### WTFPL and CC0
|
### CC0
|
||||||
|
|
||||||
These licenses allows anyone to do what they want with your mod.
|
These licenses allow anyone to do what they want with your mod,
|
||||||
This means they can modify, redistribute, sell, or leave out attribution.
|
which means they can modify, redistribute, sell, or leave-out attribution.
|
||||||
These licenses can be used for both code and art.
|
These licenses can be used for both code and art.
|
||||||
|
|
||||||
It is important to note that WTFPL is strongly discouraged and people may
|
It is important to note that WTFPL is strongly discouraged and people may
|
||||||
@ -61,19 +62,19 @@ in any copies of the mod or of substantial parts of the mod.
|
|||||||
|
|
||||||
## Packaging
|
## Packaging
|
||||||
|
|
||||||
There are some files it is recommended to include in your mod
|
There are some files that are recommended to include in your mod
|
||||||
when you release it.
|
before you release it.
|
||||||
|
|
||||||
### README.txt
|
### README.txt
|
||||||
|
|
||||||
The readme file should state:
|
The README file should state:
|
||||||
|
|
||||||
* What the mod does.
|
* What the mod does.
|
||||||
* What the license is.
|
* What the license is.
|
||||||
* Current version of mod.
|
* What dependencies there are.
|
||||||
* How to install the mod.
|
* How to install the mod.
|
||||||
* What dependencies there are / what the user needs to install.
|
* Current version of the mod.
|
||||||
* Where to report problems/bugs or get help.
|
* Optionally, the where to report problems or get help.
|
||||||
|
|
||||||
### description.txt
|
### description.txt
|
||||||
|
|
||||||
@ -85,7 +86,7 @@ Good example:
|
|||||||
|
|
||||||
Adds soup, cakes, bakes and juices.
|
Adds soup, cakes, bakes and juices.
|
||||||
|
|
||||||
Don't do this:
|
Avoid this:
|
||||||
|
|
||||||
(BAD) The food mod for Minetest.
|
(BAD) The food mod for Minetest.
|
||||||
|
|
||||||
@ -133,10 +134,7 @@ your mods. This can be done when creating a mod's forum topic (covered below).
|
|||||||
|
|
||||||
You need to zip the files for the mod into a single file. How to do this varies from
|
You need to zip the files for the mod into a single file. How to do this varies from
|
||||||
operating system to operating system.
|
operating system to operating system.
|
||||||
|
This is nearly always done using the right click menu after selecting all files.
|
||||||
If you use Windows, go to the mod's folder and select all the files.
|
|
||||||
Right click, Send To > Compressed (zipped) folder.
|
|
||||||
Rename the resulting zip file to the name of your mod.
|
|
||||||
|
|
||||||
When making a forum topic, on the "Create a Topic" page (see below), go to the
|
When making a forum topic, on the "Create a Topic" page (see below), go to the
|
||||||
"Upload Attachment" tab at the bottom.
|
"Upload Attachment" tab at the bottom.
|
||||||
@ -155,24 +153,13 @@ enter the version of your mod in the comment field.
|
|||||||
You can now create a forum topic. You should create it in
|
You can now create a forum topic. You should create it in
|
||||||
the ["WIP Mods"](https://forum.minetest.net/viewforum.php?f=9) (Work In Progress)
|
the ["WIP Mods"](https://forum.minetest.net/viewforum.php?f=9) (Work In Progress)
|
||||||
forum.\\
|
forum.\\
|
||||||
When you consider your mod no longer a work in progress, you can
|
When you no longer consider your mod a work in progress, you can
|
||||||
[request that it be moved](https://forum.minetest.net/viewtopic.php?f=11&t=10418)
|
[request that it be moved](https://forum.minetest.net/viewtopic.php?f=11&t=10418)
|
||||||
to "Mod Releases."
|
to "Mod Releases."
|
||||||
|
|
||||||
### Content
|
The forum topic should contain similar content to the README, but should
|
||||||
|
be more promotional and also include a link to download the mod.
|
||||||
The requirements of a forum topic are mostly the same as the recommendations for
|
It's a good idea to include screenshots of your mod in action, if possible.
|
||||||
a readme file. The topic should include:
|
|
||||||
|
|
||||||
* What the mod does.
|
|
||||||
* What the license is.
|
|
||||||
* Current version of mod.
|
|
||||||
* How to install the mod.
|
|
||||||
* What dependencies there are.
|
|
||||||
* Where to report problems/bugs or get help.
|
|
||||||
* Link to download, or an attachment.
|
|
||||||
|
|
||||||
You should also include screenshots of your mod in action, if relevant.
|
|
||||||
|
|
||||||
The Minetest forum uses bbcode for formatting. Here is an example for a
|
The Minetest forum uses bbcode for formatting. Here is an example for a
|
||||||
mod named superspecial:
|
mod named superspecial:
|
||||||
|
@ -20,12 +20,13 @@ owner to lose data or control.
|
|||||||
|
|
||||||
The most important concept in security is to **never trust the user**.
|
The most important concept in security is to **never trust the user**.
|
||||||
Anything the user submits should be treated as malicious.
|
Anything the user submits should be treated as malicious.
|
||||||
This means that you should always check that the user has the correct permissions,
|
This means that you should always check that the information they
|
||||||
that the give valid information, and they are otherwise allowed to do that action
|
enter is valid, that the user has the correct permissions,
|
||||||
(ie: in range or an owner)
|
and that they are otherwise allowed to do that action
|
||||||
|
(ie: in range or an owner).
|
||||||
|
|
||||||
A malicious action isn't necessarily the modification or destruction of data,
|
A malicious action isn't necessarily the modification or destruction of data,
|
||||||
but can be accessing data they're not supposed to such as password hashes or
|
but can be accessing sensitive data, such as password hashes or
|
||||||
private messages.
|
private messages.
|
||||||
This is especially bad if the server stores information such as emails or ages,
|
This is especially bad if the server stores information such as emails or ages,
|
||||||
which some may do for verification purposes.
|
which some may do for verification purposes.
|
||||||
@ -59,8 +60,8 @@ This could even be automated using client modifications to essentially replicate
|
|||||||
the `/teleport` command with no need for a privilege.
|
the `/teleport` command with no need for a privilege.
|
||||||
|
|
||||||
The solution for this kind of issue is to use a
|
The solution for this kind of issue is to use a
|
||||||
[Context](../players/formspecs.html#contexts), as shown in
|
[Context](../players/formspecs.html#contexts), as shown previously in
|
||||||
the formspecs chapter.
|
the Formspecs chapter.
|
||||||
|
|
||||||
### Time of Check isn't Time of Use
|
### Time of Check isn't Time of Use
|
||||||
|
|
||||||
@ -71,8 +72,8 @@ engine forbids it:
|
|||||||
* From 5.0 onward, named formspecs will be blocked if they haven't been shown yet.
|
* From 5.0 onward, named formspecs will be blocked if they haven't been shown yet.
|
||||||
|
|
||||||
This means that you should check in the handler that the user meets the
|
This means that you should check in the handler that the user meets the
|
||||||
conditions for showing the formspec in the first place, and any corresponding
|
conditions for showing the formspec in the first place, as well as any
|
||||||
actions.
|
corresponding actions.
|
||||||
|
|
||||||
The vulnerability caused by checking for permissions in the show formspec but not
|
The vulnerability caused by checking for permissions in the show formspec but not
|
||||||
in the handle formspec is called Time Of Check is not Time Of Use (TOCTOU).
|
in the handle formspec is called Time Of Check is not Time Of Use (TOCTOU).
|
||||||
@ -99,7 +100,7 @@ String.format = function()
|
|||||||
end
|
end
|
||||||
```
|
```
|
||||||
|
|
||||||
The mod could pass something a lot more malicious than opening a website, such
|
The mod could pass something much 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.
|
||||||
|
|
||||||
Some rules for using an insecure environment:
|
Some rules for using an insecure environment:
|
||||||
|
@ -9,7 +9,7 @@ idx: 7.5
|
|||||||
|
|
||||||
Unit tests are an essential tool in proving and reassuring yourself that your code
|
Unit tests are an essential tool in proving and reassuring yourself that your code
|
||||||
is correct. This chapter will show you how to write tests for Minetest mods and
|
is correct. This chapter will show you how to write tests for Minetest mods and
|
||||||
games using busted. Writing unit tests for functions where you call Minetest
|
games using Busted. Writing unit tests for functions where you call Minetest
|
||||||
functions is quite difficult, but luckily [in the previous chapter](clean_arch.html)
|
functions is quite difficult, but luckily [in the previous chapter](clean_arch.html)
|
||||||
we discussed how to make your code avoid this.
|
we discussed how to make your code avoid this.
|
||||||
|
|
||||||
@ -26,7 +26,7 @@ First you'll need to install LuaRocks.
|
|||||||
* Windows: Follow the [installation instructions on LuaRock's wiki](https://github.com/luarocks/luarocks/wiki/Installation-instructions-for-Windows).
|
* Windows: Follow the [installation instructions on LuaRock's wiki](https://github.com/luarocks/luarocks/wiki/Installation-instructions-for-Windows).
|
||||||
* Debian/Ubuntu Linux: `sudo apt install luarocks`
|
* Debian/Ubuntu Linux: `sudo apt install luarocks`
|
||||||
|
|
||||||
Next you should then install Busted globally:
|
Next you should install Busted globally:
|
||||||
|
|
||||||
sudo luarocks install busted
|
sudo luarocks install busted
|
||||||
|
|
||||||
@ -104,8 +104,8 @@ functions not inside of it. You tend to only write tests for a single file at on
|
|||||||
## Mocking: Using External Functions
|
## Mocking: Using External Functions
|
||||||
|
|
||||||
Mocking is the practice of replacing functions that the thing you're testing depends
|
Mocking is the practice of replacing functions that the thing you're testing depends
|
||||||
on. This can have two purposes - firstly, the function may not be available in the
|
on. This can have two purposes; one, the function may not be available in the
|
||||||
test environment. Secondly, you may want to capture calls to the function and any
|
test environment, and two, you may want to capture calls to the function and any
|
||||||
passed arguments.
|
passed arguments.
|
||||||
|
|
||||||
If you follow the advice in the [Clean Architectures](clean_arch.html) chapter,
|
If you follow the advice in the [Clean Architectures](clean_arch.html) chapter,
|
||||||
@ -163,7 +163,7 @@ end)
|
|||||||
|
|
||||||
## Checking Commits with Travis
|
## Checking Commits with Travis
|
||||||
|
|
||||||
The Travis script from the [Error Checking](luacheck.html)
|
The Travis script from the [Automatic Error Checking](luacheck.html)
|
||||||
chapter can be modified to also run Busted.
|
chapter can be modified to also run Busted.
|
||||||
|
|
||||||
```yml
|
```yml
|
||||||
|
Loading…
Reference in New Issue
Block a user