Improve wording and grammar [3/3]

master
rubenwardy 2018-10-27 03:10:37 +01:00 committed by GitHub
parent 5cad89cacc
commit 9e7657621a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 189 additions and 215 deletions

View File

@ -24,7 +24,7 @@ creating mods.
## What are Games and Mods?
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
content and behaviour of a game.

View File

@ -7,8 +7,8 @@ idx: 6.1
## Introduction
The power of Minetest is the ability to easily create games without the need
to write your own voxel graphics and algorithms or fancy networking.
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.
* [What is a Game?](#what-is-a-game)
* [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.
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's a space simulation game with a steam punk automation ascetic.
it could be a classic crafter miner with hard survival elements, or
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.
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
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
├── 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
with any mods which have the same name.
That is, if a mod uses the same name as another mod even if third party,
then it should have a compatible API.
For example, if a game includes a mod called `doors` then it should have the
That is, if a mod uses the same name as another mod, even if third party,
it should have a compatible API.
For example, if a game includes a mod called `doors`, then it should have the
same API as `doors` in Minetest Game.
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`.
* Registered Nodes/Items - The presence of items.
It's probably fine to have partial breakages as long as 90% of dependency
usecases still works. For example, not having a random utility function that was
only actually used internally is ok, but not having `mobs.register_mobs` is bad.
Small breakages aren't that bad, such as not having a random utility
function that was only actually used internally, but bigger breakages
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.
@ -83,7 +83,7 @@ To check whether a mod name has been taken, search for it on
### 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
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.

View File

@ -42,8 +42,8 @@ local vm = minetest.get_voxel_manip()
local emin, emax = vm:read_from_map(pos1, pos2)
```
For performance reasons, an LVM may not read the exact area you tell it to.
Instead, it may read a larger area. The larger area is given by `emin` and `emax`,
For performance reasons, an LVM will almost never read the exact area you tell it to.
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
it contains for you - whether that involves loading from memory, from disk, or
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
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
ID for a particular type of node with `get_content_id()`. For example:
is slow. Instead, the engine uses an integer called a content ID.
You can find out the content ID for a particular type of node with
`get_content_id()`. For example:
```lua
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
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,
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
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
another, and avoid memory thrashing.
another, and avoid *cache thrashing*.
## Writing Nodes
@ -194,10 +195,10 @@ end
## 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.
* Make a function which rotates all chest nodes by 90&deg;.
* Make a function which uses an LVM to cause mossy cobble to spread to nearby
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?

View File

@ -95,7 +95,7 @@ print(meta:get_string("count")) --> "3"
### Special Keys
`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
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.
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
uses for tables.
JSON is a more standard format and is better structured, and so is well suited
when you need to exchange information with another program.
The Lua method tends to be a lot faster and matches the format Lua
uses for tables, while JSON is a more standard format, is better
structured, and is well suited when you need to exchange information
with another program.
```lua
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.
You should make this optional by separating how the data is stored and where
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
local backend
@ -200,11 +198,17 @@ return backend
The backend_sqlite would do a similar thing, but use the Lua *lsqlite3* library
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
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")
-- 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.
Insecure environments will be covered in more detail in the
[security](../quality/security.html) chapter.
## 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
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.
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.
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.
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.
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
* Make a node which disappears after it has been punched five times.

View File

@ -8,12 +8,12 @@ redirect_from: /en/chapters/chat.html
cmd_online:
level: warning
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
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.
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:
level: warning
@ -27,7 +27,7 @@ cb_cmdsprivs:
## Introduction
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 Specific Players](#sending-messages-to-specific-players)
@ -64,40 +64,25 @@ only visible to the named player, in this case player1.
## 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
minetest.register_chatcommand("foo", {
privs = {
interact = true
interact = true,
},
func = function(name, 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:
```lua
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.
Chat commands can return up to two values,
the first being a Boolean indicating success, and the second being a
message to send to the user.
{% 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_-]+) (*+)$")
```
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.
* `()` is a matching group - anything that matches stuff in here will be
returned from string.match.
* `[]` 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 `-`.
* `+` 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 the end of the string.
Put simply, this matches the name (a word with only letters/numbers/-/_),
then a space, then the message (one of more of any character). The name and
message are returned, as they're surrounded in parentheses.
Put simply, the pattern matches the name (a word with only letters/numbers/-/_),
then a space, then the message (one or more of any character). The name and
message are returned, because they're surrounded by parentheses.
That's how most mods implement complex chat commands. A better guide to Lua
Patterns would probably be the
[lua-users.org tutorial](http://lua-users.org/wiki/PatternsTutorial)
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
to make complex chat commands without Patterns called
[ChatCmdBuilder](chat_complex.html).
to make complex chat commands without patterns called
<a href="chat_complex.html">Chat Command Builder</a>.
</p>
## Intercepting Messages
@ -155,8 +142,8 @@ end)
```
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
work the same.
handler. You can actually remove the line `return false` and it would still
work the same, because `nil` is returned implicitly and is treated like false.
{% include notice.html notice=page.cb_cmdsprivs %}

View File

@ -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.
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)
* [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
a percentage position and an offset.
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
the screen, eg (50%, 50%), and an offset of (0, 0).
in the centre of the screen, you would need to provide a percentage position of half
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.
@ -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 -
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
make a text element left, center, or right justified.
make a text element aligned to the left, centre, or right.
<figure>
<img
@ -65,7 +65,7 @@ make a text element left, center, or right justified.
</figure>
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.
### Scoreboard
@ -79,7 +79,7 @@ score panel like so:
alt="screenshot of the HUD we're aiming for">
</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
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
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
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
-- 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({
hud_elem_type = "text",
position = {x = 1, y = 0.5},
@ -127,12 +132,6 @@ player:hud_add({
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({
hud_elem_type = "text",
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.
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.
However, if a co-ordinate is negative it is a percentage of the screensize.
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.
For example, `x=-100` is 100% of the width.
### 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:
```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({
hud_elem_type = "image",
@ -226,7 +225,7 @@ There is one problem however, it won't update when the stats change.
## 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
local idx = player:hud_add({
@ -261,12 +260,11 @@ local saved_huds = {}
function score.update_hud(player)
local player_name = player:get_player_name()
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
local percent = tonumber(player:get_attribute("score:score") or 0.2)
-- 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")
local percent = tonumber(meta:get("score:score") or 0.2)
local ids = saved_huds[player_name]
if ids then

View File

@ -8,14 +8,16 @@ redirect_from: /en/chapters/player_physics.html
## Introduction
Player physics can be modified using physics overrides. Physics overrides can set the
walking speed, jump speed and gravity constants. 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.
Player physics can be modified using physics overrides.
Physics overrides can set the walking speed, jump speed,
and gravity constants.
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)
* [Available Overrides](#available_overrides)
* [Mod Incompatibility ](#mod_incompatibility)
* [Mod Incompatibility](#mod_incompatibility)
* [Your Turn](#your_turn)
## Basic Example
@ -28,16 +30,16 @@ minetest.register_chatcommand("antigravity", {
func = function(name, param)
local player = minetest.get_player_by_name(name)
player:set_physics_override({
gravity = 0.1 -- set gravity to 10% of its original value
-- (0.1 * 9.81)
gravity = 0.1, -- set gravity to 10% of its original value
-- (0.1 * 9.81)
})
end
end,
})
```
## 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),
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:
* 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
@ -69,5 +71,5 @@ player's speed, only the last one to run will be in effect.
## Your Turn
* **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.

View File

@ -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
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
it's entirely possible that a mod or game decides to show them in
Whilst SFINV by default shows pages as tabs, pages are called pages
because it is entirely possible that a mod or game decides to show them in
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)
* [Receiving events](#receiving-events)
@ -25,7 +25,7 @@ For example, multiple pages could be shown on one view.
## Registering a Page
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
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
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
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
in previous chapters.
There's nothing new about the above code; all the concepts are
covered above and in previous chapters.
<figure>
<img src="{{ page.root }}//static/sfinv_admin_fs.png" alt="Player Admin Page">
<figcaption>
The player admin page created above.
</figcaption>
</figure>
## Receiving events
@ -104,10 +101,10 @@ on_player_receive_fields = function(self, player, context, fields)
end,
```
Fields is the exact same as the fields given to the subscribers of
`minetest.register_on_player_receive_fields`. The return value of
`on_player_receive_fields` is the same as a normal player receive fields.
Please note that sfinv will consume events relevant to itself, such as
`on_player_receive_fields` works the same as
`minetest.register_on_player_receive_fields`, except that `context` is
given instead of `formname`.
Please note that SFINV will consume events relevant to itself, such as
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:
@ -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
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
formspec on any events that may change `is_in_nav`'s result. In our case,

View File

@ -33,7 +33,7 @@ code is thrown in together with no clear boundaries. This ultimately makes a
project completely unmaintainable, ending in its abandonment.
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.
>
@ -51,14 +51,13 @@ a low amount of coupling, as this means that changing the APIs of certain areas
will be more feasible.
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
to get high cohesion and low coupling.
and the relationship between areas inside a mod.
## Model-View-Controller
In the next chapter we will discuss how to automatically test your code, and one
of the problems we will have is how to separate your logic
In the next chapter, we will discuss how to automatically test your
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)
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.
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
should be made as small as possible anyway.)
@ -98,8 +97,8 @@ function land.get_by_name(area_name)
end
```
Your actions should also be pure, however calling other functions is more
acceptable.
Your actions should also be pure, but calling other functions is more
acceptable than in the above.
```lua
-- Controller
@ -155,7 +154,7 @@ most of the calculations are made.
The controller should have no knowledge about the Minetest API - notice how
there are no Minetest calls or any view functions that resemble them.
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
to the Minetest API at all.
@ -167,27 +166,27 @@ to the Minetest API at all.
</figure>
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
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.
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,
you tend to see a lot more of a less formal and strict kind of design -
varients of the API-View.
you will commonly see a less formal and strict kind of design -
variants of the API-View.
### API-View
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
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:
* **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.
* **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.
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
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
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.
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.
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 -
you could end up with a lot of messy dependencies.
Enter the Observer pattern. Instead of the mobs mod caring about awards, mobs
exposes a way for other areas of code to register their interest in an event
and receive data about the event.
Enter the Observer pattern. Instead of the mymobs mod caring about awards,
the mymobs mod exposes a way for other areas of code to register their
interest in an event and receive data about the event.
```lua
mobs.registered_on_death = {}
function mobs.register_on_death(func)
table.insert(mobs.registered_on_death, func)
mymobs.registered_on_death = {}
function mymobs.register_on_death(func)
table.insert(mymobs.registered_on_death, func)
end
-- mob death code
for i=1, #mobs.registered_on_death do
mobs.registered_on_death[i](entity, reason)
for i=1, #mymobs.registered_on_death do
mymobs.registered_on_death[i](entity, reason)
end
```
Then the other code registers its interest:
```lua
-- awards
mobs.register_on_death(function(mob, reason)
mymobs.register_on_death(function(mob, reason)
if reason.type == "punch" and reason.object and
reason.object:is_player() then
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!
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.
## 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,
keep related code together and unrelated code apart, and keep dependencies simple.

View File

@ -40,7 +40,7 @@ minetest.register_on_joinplayer(function(player)
end)
```
Do this:
Do this instead:
```lua
minetest.register_on_joinplayer(function(player)
@ -62,10 +62,10 @@ end)
## Don't Trust Formspec Submissions
Malicious clients can submit formspecs whenever they like with whatever content
they like.
Malicious clients can submit formspecs whenever they like, with
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:
```lua
@ -123,7 +123,7 @@ stack:get_meta():set_string("description", "Partially eaten")
-- BAD! Modification will be lost
```
Do this:
Do this instead:
```lua
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,
it will only be saved in the engine if the callback caller sets it.
Avoid this:
```lua
minetest.register_on_item_eat(function(hp_change, replace_with_item,
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,
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:

View File

@ -88,10 +88,10 @@ a look at the list below.
### Troubleshooting
* **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,
then add it to `globals`. Remove from `read_globals` if present there.
Otherwise add any missing `local`s to the mod.
add it to `globals`. Remove from `read_globals` if present.
Otherwise, add any missing `local`s to the mod.
* **mutating read-only global variable 'foobar'** - Move `foobar` from `read_globals` to
`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
available.
* **Atom** - `linter-luacheck`
* **Atom** - `linter-luacheck`.
* **Sublime** - Install using package-control:
[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
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
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
when your project receives a pull request - you'll be able to see the LuaCheck output
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
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
outcome of LuaCheck. In either case, you can click the icon to see the build logs
and the output of LuaCheck.

View File

@ -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
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
in different countries.
@ -37,17 +37,18 @@ in different countries.
### LGPL and CC-BY-SA
This is a common license combination in the Minetest community, and is what
Minetest and minetest_game use.
You license your code under LGPL 2.1 and your art under CC-BY-SA. This means:
Minetest and Minetest Game use.
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.
* If someone modifies your mod, they must give their version the same license.
* Your copyright notice must be kept.
### WTFPL and CC0
### CC0
These licenses allows anyone to do what they want with your mod.
This means they can modify, redistribute, sell, or leave out attribution.
These licenses allow anyone to do what they want with your mod,
which means they can modify, redistribute, sell, or leave-out attribution.
These licenses can be used for both code and art.
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
There are some files it is recommended to include in your mod
when you release it.
There are some files that are recommended to include in your mod
before you release it.
### README.txt
The readme file should state:
The README file should state:
* What the mod does.
* What the license is.
* Current version of mod.
* What dependencies there are.
* How to install the mod.
* What dependencies there are / what the user needs to install.
* Where to report problems/bugs or get help.
* Current version of the mod.
* Optionally, the where to report problems or get help.
### description.txt
@ -85,7 +86,7 @@ Good example:
Adds soup, cakes, bakes and juices.
Don't do this:
Avoid this:
(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
operating system to operating system.
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.
This is nearly always done using the right click menu after selecting all files.
When making a forum topic, on the "Create a Topic" page (see below), go to the
"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
the ["WIP Mods"](https://forum.minetest.net/viewforum.php?f=9) (Work In Progress)
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)
to "Mod Releases."
### Content
The requirements of a forum topic are mostly the same as the recommendations for
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 forum topic should contain similar content to the README, but should
be more promotional and also include a link to download the mod.
It's a good idea to include screenshots of your mod in action, if possible.
The Minetest forum uses bbcode for formatting. Here is an example for a
mod named superspecial:

View File

@ -20,12 +20,13 @@ owner to lose data or control.
The most important concept in security is to **never trust the user**.
Anything the user submits should be treated as malicious.
This means that you should always check that the user has the correct permissions,
that the give valid information, and they are otherwise allowed to do that action
(ie: in range or an owner)
This means that you should always check that the information they
enter is valid, that the user has the correct permissions,
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,
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.
This is especially bad if the server stores information such as emails or ages,
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 solution for this kind of issue is to use a
[Context](../players/formspecs.html#contexts), as shown in
the formspecs chapter.
[Context](../players/formspecs.html#contexts), as shown previously in
the Formspecs chapter.
### 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.
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
actions.
conditions for showing the formspec in the first place, as well as any
corresponding actions.
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).
@ -99,7 +100,7 @@ String.format = function()
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.
Some rules for using an insecure environment:

View File

@ -9,7 +9,7 @@ idx: 7.5
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
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)
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).
* Debian/Ubuntu Linux: `sudo apt install luarocks`
Next you should then install Busted globally:
Next you should install Busted globally:
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 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
test environment. Secondly, you may want to capture calls to the function and any
on. This can have two purposes; one, the function may not be available in the
test environment, and two, you may want to capture calls to the function and any
passed arguments.
If you follow the advice in the [Clean Architectures](clean_arch.html) chapter,
@ -163,7 +163,7 @@ end)
## 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.
```yml