diff --git a/README.md b/README.md index e503b9f..ab00c2b 100644 --- a/README.md +++ b/README.md @@ -67,9 +67,9 @@ Explain why/how these concepts are useful in modding Paragraphs -{% highlight lua %} +```lua code -{% endhighlight %} +``` ## Parts in diff --git a/_en/basics/getting_started.md b/_en/basics/getting_started.md index 79d4315..9ce71ea 100644 --- a/_en/basics/getting_started.md +++ b/_en/basics/getting_started.md @@ -113,7 +113,7 @@ Are you confused? Don't worry, here is an example which puts all of this togethe default ### init.lua -{% highlight lua %} +```lua print("This file will be run at load time!") minetest.register_node("mymod:node", { @@ -128,7 +128,7 @@ minetest.register_node("mymod:node", { }, groups = {cracky = 1} }) -{% endhighlight %} +``` ### mod.conf name = mymod diff --git a/_en/basics/lua.md b/_en/basics/lua.md index 6ee1f46..b4ad3da 100644 --- a/_en/basics/lua.md +++ b/_en/basics/lua.md @@ -30,7 +30,7 @@ A code editor with code highlighting is sufficient for writing scripts in Lua. Code highlighting gives different colours to different words and characters depending on what they mean. This allows you to spot mistakes. -{% highlight lua %} +```lua function ctf.post(team,msg) if not ctf.team(team) then return false @@ -44,7 +44,7 @@ function ctf.post(team,msg) return true end -{% endhighlight %} +``` For example, keywords in the above snippet are highlighted such as if, then, end, return. table.insert is a function which comes with Lua by default. @@ -88,13 +88,13 @@ There are three main types of flow: So, what do statements in Lua look like? -{% highlight lua %} +```lua local a = 2 -- Set 'a' to 2 local b = 2 -- Set 'b' to 2 local result = a + b -- Set 'result' to a + b, which is 4 a = a + 10 print("Sum is "..result) -{% endhighlight %} +``` Whoa, what happened there? @@ -140,14 +140,14 @@ Not an exhaustive list. Doesn't contain every possible operator. The most basic selection is the if statement. It looks like this: -{% highlight lua %} +```lua local random_number = math.random(1, 100) -- Between 1 and 100. if random_number > 50 then print("Woohoo!") else print("No!") end -{% endhighlight %} +``` That example generates a random number between 1 and 100. It then prints "Woohoo!" if that number is bigger than 50, otherwise it prints "No!". @@ -169,24 +169,24 @@ What else can you get apart from '>'? That doesn't contain every possible operator, and you can combine operators like this: -{% highlight lua %} +```lua if not A and B then print("Yay!") end -{% endhighlight %} +``` Which prints "Yay!" if A is false and B is true. Logical and arithmetic operators work exactly the same, they both accept inputs and return a value which can be stored. -{% highlight lua %} +```lua local A = 5 local is_equal = (A == 5) if is_equal then print("Is equal!") end -{% endhighlight %} +``` ## Programming @@ -208,7 +208,7 @@ however, the following websites are quite useful in developing this: Whether a variable is local or global determines where it can be written to or read to. A local variable is only accessible from where it is defined. Here are some examples: -{% highlight lua %} +```lua -- Accessible from within this script file local one = 1 @@ -221,11 +221,11 @@ function myfunc() local three = one + two end end -{% endhighlight %} +``` Whereas global variables can be accessed from anywhere in the script file, and from any other mod. -{% highlight lua %} +```lua my_global_variable = "blah" function one() @@ -235,7 +235,7 @@ end print(my_global_variable) -- Output: "blah" one() print(my_global_variable) -- Output: "three" -{% endhighlight %} +``` ### Locals should be used as much as possible @@ -243,7 +243,7 @@ print(my_global_variable) -- Output: "three" Lua is global by default (unlike most other programming languages). Local variables must be identified as such. -{% highlight lua %} +```lua function one() foo = "bar" end @@ -254,7 +254,7 @@ end one() two() -{% endhighlight %} +``` dump() is a function that can turn any variable into a string so the programmer can see what it is. The foo variable will be printed as "bar", including the quotes @@ -266,7 +266,7 @@ This is sloppy coding, and Minetest will in fact warn about this: To correct this, use "local": -{% highlight lua %} +```lua function one() local foo = "bar" end @@ -277,7 +277,7 @@ end one() two() -{% endhighlight %} +``` Remember that nil means **not initialised**. The variable hasn't been assigned a value yet, @@ -286,15 +286,15 @@ doesn't exist, or has been uninitialised (ie: set to nil). The same goes for functions. Functions are variables of a special type, and should be made local, as other mods could have functions of the same name. -{% highlight lua %} +```lua local function foo(bar) return bar * 2 end -{% endhighlight %} +``` API tables should be used to allow other mods to call the functions, like so: -{% highlight lua %} +```lua mymod = {} function mymod.foo(bar) @@ -303,27 +303,27 @@ end -- In another mod, or script: mymod.foo("foobar") -{% endhighlight %} +``` ## Including other Lua Scripts The recommended way to include other Lua scripts in a mod is to use *dofile*. -{% highlight lua %} +```lua dofile(minetest.get_modpath("modname") .. "/script.lua") -{% endhighlight %} +``` "local" variables declared outside of any functions in a script file will be local to that script. A script can return a value, which is useful for sharing private locals: -{% highlight lua %} +```lua -- script.lua return "Hello world!" -- init.lua local ret = dofile(minetest.get_modpath("modname") .. "/script.lua") print(ret) -- Hello world! -{% endhighlight %} +``` Later chapters will discuss how to split up the code of a mod in a lot of detail. However, the simplistic approach for now is to have different files for different diff --git a/_en/inventories/inventories.md b/_en/inventories/inventories.md index 03ab2a9..28c24a5 100644 --- a/_en/inventories/inventories.md +++ b/_en/inventories/inventories.md @@ -69,9 +69,9 @@ Press i in game to see your player inventory. Use a player's name to get their inventory: -{% highlight lua %} +```lua local inv = minetest.get_inventory({type="player", name="celeron55"}) -{% endhighlight %} +``` ### Node Inventories @@ -80,9 +80,9 @@ The node must be loaded, because it is stored in [node metadata](node_metadata.h Use its position to get a node inventory: -{% highlight lua %} +```lua local inv = minetest.get_inventory({type="node", pos={x=, y=, z=}}) -{% endhighlight %} +``` ### Detached Inventories @@ -93,15 +93,15 @@ A detached inventory would also allow multiple chests to share the same inventor Use the inventory name to get a detached inventory: -{% highlight lua %} +```lua local inv = minetest.get_inventory({type="detached", name="inventory_name"}) -{% endhighlight %} +``` You can create your own detached inventories: -{% highlight lua %} +```lua minetest.create_detached_inventory("inventory_name", callbacks) -{% endhighlight %} +``` This creates a detached inventory or, if the inventory already exists, it is cleared. You can also supply a [table of callbacks]({{ page.root }}/lua_api.html#detached-inventory-callbacks). @@ -112,9 +112,9 @@ You can also supply a [table of callbacks]({{ page.root }}/lua_api.html#detached You can check where an inventory is located: -{% highlight lua %} +```lua local location = inv:get_location() -{% endhighlight %} +``` This will return a table like the one passed to `minetest.get_inventory()`. @@ -125,7 +125,7 @@ If the location is unknown, `{type="undefined"}` is returned. Inventory lists have a size, for example `main` has size of 32 slots by default. They also have a width, which is used to divide them into a grid. -{% highlight lua %} +```lua if inv:set_size("main", 32) then inv:set_width("main", 8) print("size: " .. inv.get_size("main")) @@ -133,7 +133,7 @@ if inv:set_size("main", 32) then else print("Error!") end -{% endhighlight %} +``` You can read from the map once you have a position: -{% highlight lua %} +```lua local node = minetest.get_node({ x = 1, y = 3, z = 4 }) print(dump(node)) --> { name=.., param1=.., param2=.. } -{% endhighlight %} +``` If the position is a decimal, it will be rounded to the containing node. The function will always return a table containing the node information: @@ -82,30 +82,30 @@ For example, say we wanted to make a certain type of plant that grows better near mese. You would need to search for any nearby mese nodes, and adapt the growth rate accordingly. -{% highlight lua %} +```lua local grow_speed = 1 local node_pos = minetest.find_node_near(pos, 5, { "default:mese" }) if node_pos then minetest.chat_send_all("Node found at: " .. dump(node_pos)) grow_speed = 2 end -{% endhighlight %} +``` Let's say, for example, that the growth rate increases the more mese there is nearby. You should then use a function which can find multiple nodes in area: -{% highlight lua %} +```lua local pos1 = vector.subtract(pos, { x = 5, y = 5, z = 5 }) local pos2 = vector.add(pos, { x = 5, y = 5, z = 5 }) local pos_list = minetest.find_nodes_in_area(pos1, pos2, { "default:mese" }) local grow_speed = 1 + #pos_list -{% endhighlight %} +``` The above code doesn't quite do what we want, as it checks based on area, whereas `find_node_near` checks based on range. In order to fix this we will, unfortunately, need to manually check the range ourselves. -{% highlight lua %} +```lua local pos1 = vector.subtract(pos, { x = 5, y = 5, z = 5 }) local pos2 = vector.add(pos, { x = 5, y = 5, z = 5 }) local pos_list = minetest.find_nodes_in_area(pos1, pos2, { "default:mese" }) @@ -116,7 +116,7 @@ for i=1, #pos_list do grow_speed = grow_speed + 1 end end -{% endhighlight %} +``` Now your code will correctly increase `grow_speed` based on mese nodes in range. Note how we compared the squared distance from the position, rather than square @@ -135,12 +135,12 @@ You can use `set_node` to write to the map. Each call to set_node will cause lighting to be recalculated, which means that set_node is fairly slow for large numbers of nodes. -{% highlight lua %} +```lua minetest.set_node({ x = 1, y = 3, z = 4 }, { name = "default:mese" }) local node = minetest.get_node({ x = 1, y = 3, z = 4 }) print(node.name) --> default:mese -{% endhighlight %} +``` set_node will remove any associated metadata or inventory from that position. This isn't desirable in all circumstances, especially if you're using multiple @@ -150,9 +150,9 @@ two. You can set a node without deleting metadata or the inventory like so: -{% highlight lua %} +```lua minetest.swap_node({ x = 1, y = 3, z = 4 }, { name = "default:mese" }) -{% endhighlight %} +``` ### Removing Nodes @@ -160,10 +160,10 @@ A node must always be present. To remove a node, you set the position to `air`. The following two lines will both remove a node, and are both identical: -{% highlight lua %} +```lua minetest.remove_node(pos) minetest.set_node(pos, { name = "air" }) -{% endhighlight %} +``` In fact, remove_node will call set_node with name being air. @@ -173,7 +173,7 @@ You can use `minetest.emerge_area` to load map blocks. Emerge area is asynchrono meaning the blocks won't be loaded instantly. Instead, they will be loaded soon in the future, and the callback will be called each time. -{% highlight lua %} +```lua -- Load a 20x20x20 area local halfsize = { x = 10, y = 10, z = 10 } local pos1 = vector.subtract(pos, halfsize) @@ -181,12 +181,12 @@ local pos2 = vector.add (pos, halfsize) local context = {} -- persist data between callback calls minetest.emerge_area(pos1, pos2, emerge_callback, context) -{% endhighlight %} +``` Minetest will call `emerge_callback` whenever it loads a block, with some progress information. -{% highlight lua %} +```lua local function emerge_callback(pos, action, num_calls_remaining, context) -- On first call, record number of blocks if not context.total_blocks then @@ -206,7 +206,7 @@ local function emerge_callback(pos, action, num_calls_remaining, context) context.loaded_blocks, context.total_blocks, perc) end end -{% endhighlight %} +``` This is not the only way of loading blocks; Using a LVM will also cause the encompassed blocks to be loaded synchronously. @@ -215,14 +215,14 @@ encompassed blocks to be loaded synchronously. You can use delete_blocks to delete a range of map blocks: -{% highlight lua %} +```lua -- Delete a 20x20x20 area local halfsize = { x = 10, y = 10, z = 10 } local pos1 = vector.subtract(pos, halfsize) local pos2 = vector.add (pos, halfsize) minetest.delete_area(pos1, pos2) -{% endhighlight %} +``` This will delete all map blocks in that area, *inclusive*. This means that some nodes will be deleted outside the area as they will be on a mapblock which overlaps diff --git a/_en/map/lvm.md b/_en/map/lvm.md index f1cbf88..0c51986 100644 --- a/_en/map/lvm.md +++ b/_en/map/lvm.md @@ -36,10 +36,10 @@ You can only load a cubic area into an LVM, so you need to work out the minimum and maximum positions that you need to modify. Then you can create and read into an LVM like so: -{% highlight lua %} +```lua local vm = minetest.get_voxel_manip() local emin, emax = vm:read_from_map(pos1, pos2) -{% endhighlight %} +``` An LVM may not read exactly the area you tell it to, for performance reasons. Instead, it may read a larger area. The larger area is given by `emin` and `emax`, @@ -53,9 +53,9 @@ To read the types of nodes at particular positions, you'll need to use `get_data `get_data()` returns a flat array where each entry represents the type of a particular node. -{% highlight lua %} +```lua local data = vm:get_data() -{% endhighlight %} +``` You can get param2 and lighting data using the methods `get_light_data()` and `get_param2_data()`. @@ -63,7 +63,7 @@ You'll need to use `emin` and `emax` to work out where a node is in the flat arr given by the above methods. There's a helper class called `VoxelArea` which handles the calculation for you: -{% highlight lua %} +```lua local a = VoxelArea:new{ MinEdge = emin, MaxEdge = emax @@ -74,25 +74,25 @@ local idx = a:index(x, y, z) -- Read node print(data[idx]) -{% endhighlight %} +``` If you run that, you'll notice that `data[vi]` is an integer. This is because the engine doesn't store nodes using their name string, as string comparision is slow. Instead, the engine uses a content ID. You can find out the content ID for a particular type of node like so: -{% highlight lua %} +```lua local c_stone = minetest.get_content_id("default:stone") -{% endhighlight %} +``` and then you can check whether a node is stone like so: -{% highlight lua %} +```lua local idx = a:index(x, y, z) if data[idx] == c_stone then print("is stone!") end -{% endhighlight %} +``` It is recommended that you find out and store the content IDs of nodes types using load time, as the IDs of a node type will never change. Make sure to store @@ -101,7 +101,7 @@ the IDs in a local for performance reasons. Nodes in an LVM data are stored in reverse co-ordinate order, so you should always iterate in the order of `z, y, x` like so: -{% highlight lua %} +```lua for z = min.z, max.z do for y = min.y, max.y do for x = min.x, max.x do @@ -113,7 +113,7 @@ for z = min.z, max.z do end end end -{% endhighlight %} +``` The reason for this touches computer architecture. Reading from RAM is rather costly, so CPUs have multiple levels of caching. If the data a process requests @@ -128,7 +128,7 @@ another, and avoid *memory thrashing*. First you need to set the new content ID in the data array: -{% highlight lua %} +```lua for z = min.z, max.z do for y = min.y, max.y do for x = min.x, max.x do @@ -139,15 +139,15 @@ for z = min.z, max.z do end end end -{% endhighlight %} +``` When you finished setting nodes in the LVM, you then need to upload the data array to the engine: -{% highlight lua %} +```lua vm:set_data(data) vm:write_to_map(true) -{% endhighlight %} +``` For setting lighting and param2 data, there are the appropriately named `set_light_data()` and `set_param2_data()` methods. @@ -158,7 +158,7 @@ date using `minetest.fix_light`. ## Example -{% highlight lua %} +```lua -- Get content IDs during load time, and store into a local local c_dirt = minetest.get_content_id("default:dirt") local c_grass = minetest.get_content_id("default:dirt_with_grass") @@ -189,7 +189,7 @@ local function grass_to_dirt(pos1, pos2) vm:set_data(data) vm:write_to_map(true) end -{% endhighlight %} +``` ## Your Turn diff --git a/_en/map/node_metadata.md b/_en/map/node_metadata.md index a22e995..7203963 100644 --- a/_en/map/node_metadata.md +++ b/_en/map/node_metadata.md @@ -44,16 +44,16 @@ Metadata is stored in a key value relationship. For example: If you know the position of a node, you can retrieve its metadata: -{% highlight lua %} +```lua local meta = minetest.get_meta(pos) -- where pos = { x = 1, y = 5, z = 7 } -{% endhighlight %} +``` ## Reading Metadata After retrieving the metadata, you can read its values: -{% highlight lua %} +```lua local value = meta:get_string("key") if value then @@ -63,7 +63,7 @@ else -- metadata of key "key" does not exist print(value) end -{% endhighlight %} +``` The functions available include: @@ -74,7 +74,7 @@ The functions available include: To get a Boolean, you should use `get_string` and `minetest.is_yes`: -{% highlight lua %} +```lua local value = minetest.is_yes(meta:get_string("key")) if value then @@ -82,18 +82,18 @@ if value then else print("is no") end -{% endhighlight %} +``` ## Setting Metadata You can set node metadata. For example: -{% highlight lua %} +```lua local value = "one" meta:set_string("key", value) meta:set_string("foo", "bar") -{% endhighlight %} +``` This can be done using the following functions: @@ -105,11 +105,11 @@ This can be done using the following functions: You can convert to and from Lua tables using `to_table` and `from_table`: -{% highlight lua %} +```lua local tmp = meta:to_table() tmp.foo = "bar" meta:from_table(tmp) -{% endhighlight %} +``` ## Infotext @@ -117,9 +117,9 @@ The Minetest engine reads the field `infotext` to make text appear on mouse-over. This is used by furnaces to show progress and by signs to show their text. For example: -{% highlight lua %} +```lua meta:set_string("infotext", "Here is some text that will appear on mouse-over!") -{% endhighlight %} +``` ## Your Turn diff --git a/_en/map/objects.md b/_en/map/objects.md index 883a643..c0dcbe0 100644 --- a/_en/map/objects.md +++ b/_en/map/objects.md @@ -38,19 +38,19 @@ Don't be fooled though, all entities are Lua entities. `get_pos` and `set_pos` exist to allow you to get and set an entity's position. -{% highlight lua %} +```lua local object = minetest.get_player_by_name("bob") local pos = object:get_pos() object:set_pos({ x = pos.x, y = pos.y + 1, z = pos.z }) -{% endhighlight %} +``` `set_pos` immediately sets the position, with no animation. If you'd like to smoothly animate an object to the new position, you should use `move_to`. This, unfortunately, only works for entities. -{% highlight lua %} +```lua object:move_to({ x = pos.x, y = pos.y + 1, z = pos.z }) -{% endhighlight %} +``` An important thing to think about when dealing with entities is network latency. In an ideal world, messages about entity movements would arrive immediately, @@ -69,14 +69,14 @@ Unlike nodes, objects have a dynamic rather than set appearance. You can change how an object looks, among other things, at any time by updating its properties. -{% highlight lua %} +```lua object:set_properties({ visual = "mesh", mesh = "character.b3d", textures = {"character_texture.png"}, visual_size = {x=1, y=1}, }) -{% endhighlight %} +``` The updated properties will be sent to all players in range. This is very useful to get a large amount of variety very cheaply, such as having @@ -93,7 +93,7 @@ joined players. An Entity has a type table much like an item does. This table can contain callback methods, default object properties, and custom elements. -{% highlight lua %} +```lua local MyEntity = { initial_properties = { hp_max = 1, @@ -113,7 +113,7 @@ local MyEntity = { function MyEntity:set_message(msg) self.message = msg end -{% endhighlight %} +``` When an entity is emerged, a table is created for it by copying everything from its type table. @@ -121,16 +121,16 @@ This table can be used to store variables for that particular entity. Both an ObjectRef and an entity table provide ways to get the counterpart: -{% highlight lua %} +```lua local entity = object:get_luaentity() local object = entity.object print("entity is at " .. minetest.pos_to_string(object:get_pos())) -{% endhighlight %} +``` There are a number of available callbacks for use with entities. A complete list can be found in [lua_api.txt]({{ page.root }}/lua_api.html#registered-entities) -{% highlight lua %} +```lua function MyEntity:on_step(dtime) local pos = self.object:get_pos() @@ -151,7 +151,7 @@ end function MyEntity:on_punch(hitter) minetest.chat_send_player(hitter:get_player_name(), self.message) end -{% endhighlight %} +``` Now, if you were to spawn and use this entity, you'd notice that the message would be forgotten when the entity becomes inactive then active again. @@ -161,7 +161,7 @@ how to save things. Staticdata is a string which contains all of the custom information that needs to stored. -{% highlight lua %} +```lua function MyEntity:get_staticdata() return minetest.write_json({ message = self.message, @@ -174,7 +174,7 @@ function MyEntity:on_activate(staticdata, dtime_s) self:set_message(data.message) end end -{% endhighlight %} +``` Minetest may call `get_staticdata()` as many times as it once and at any time. This is because Minetest doesn't wait for a MapBlock to become inactive to save @@ -187,9 +187,9 @@ This means that staticdata could be empty. Finally, you need to register the type table using the aptly named `register_entity`. -{% highlight lua %} +```lua minetest.register_entity("9_entities:myentity", MyEntity) -{% endhighlight %} +``` ## Attachments @@ -198,9 +198,9 @@ Attached objects will move when the parent - the object they are attached to - is moved. An attached object is said to be a child of the parent. An object can have an unlimited number of children, but at most one parent. -{% highlight lua %} +```lua child:set_attach(parent, bone, position, rotation) -{% endhighlight %} +``` An Object's `get_pos()` will always return the global position of the object, no matter whether it is attached or not. diff --git a/_en/players/chat.md b/_en/players/chat.md index 54f8f20..84d3063 100644 --- a/_en/players/chat.md +++ b/_en/players/chat.md @@ -39,9 +39,9 @@ sending messages, intercepting messages and registering chat commands. To send a message to every player in the game, call the chat_send_all function. -{% highlight lua %} +```lua minetest.chat_send_all("This is a chat message to all players") -{% endhighlight %} +``` Here is an example of how this appears in-game: @@ -55,9 +55,9 @@ The message appears on a separate line to distinguish it from in-game player cha To send a message to a specific player, call the chat_send_player function: -{% highlight lua %} +```lua minetest.chat_send_player("player1", "This is a chat message for player1") -{% endhighlight %} +``` This message displays in the same manner as messages to all players, but is only visible to the named player, in this case player1. @@ -66,7 +66,7 @@ only visible to the named player, in this case player1. To register a chat command, for example /foo, use register_chatcommand: -{% highlight lua %} +```lua minetest.register_chatcommand("foo", { privs = { interact = true @@ -75,26 +75,26 @@ minetest.register_chatcommand("foo", { return true, "You said " .. param .. "!" end }) -{% endhighlight %} +``` Calling /foo bar will display `You said bar!` in the chat console. You can restrict which players are able to run commands: -{% highlight lua %} +```lua privs = { interact = true }, -{% endhighlight %} +``` This means only players with the `interact` [privilege](privileges.html) can run the command. Other players will see an error message informing them of which privilege they're missing. If the player has the necessary privileges, the command will run and the message will be sent: -{% highlight lua %} +```lua return true, "You said " .. param .. "!" -{% endhighlight %} +``` This returns two values, a Boolean which shows the command succeeded and the chat message to send to the player. @@ -113,9 +113,9 @@ It is often required to make complex chat commands, such as: This is usually done using [Lua patterns](https://www.lua.org/pil/20.2.html). Patterns are a way of extracting stuff from text using rules. -{% highlight lua %} +```lua local to, msg = string.match(param, "^([%a%d_-]+) (*+)$") -{% endhighlight %} +``` The above implements `/msg `. Let's go through left to right: @@ -147,12 +147,12 @@ to make complex chat commands without Patterns called To intercept a message, use register_on_chat_message: -{% highlight lua %} +```lua minetest.register_on_chat_message(function(name, message) print(name .. " said " .. message) return false end) -{% endhighlight %} +``` By returning false, you allow the chat message to be sent by the default handler. You can actually remove the line `return false`, and it would still @@ -163,7 +163,7 @@ work the same. You should make sure you take into account that it may be a chat command, or the user may not have `shout`. -{% highlight lua %} +```lua minetest.register_on_chat_message(function(name, message) if message:sub(1, 1) == "/" then print(name .. " ran chat command") @@ -175,4 +175,4 @@ minetest.register_on_chat_message(function(name, message) return false end) -{% endhighlight %} +``` diff --git a/_en/players/chat_complex.md b/_en/players/chat_complex.md index 12df301..62a2a99 100644 --- a/_en/players/chat_complex.md +++ b/_en/players/chat_complex.md @@ -26,14 +26,14 @@ modders tend to use the method outlined in the Traditionally mods implemented these complex commands using Lua patterns. -{% highlight lua %} +```lua local name = string.match(param, "^join ([%a%d_-]+)") -{% endhighlight %} +``` I however find Lua patterns annoying to write and unreadable. Because of this, I created a library to do this for you. -{% highlight lua %} +```lua ChatCmdBuilder.new("sethp", function(cmd) cmd:sub(":target :hp:int", function(name, target, hp) local player = minetest.get_player_by_name(target) @@ -51,7 +51,7 @@ end, { -- ^ probably better to register a custom priv } }) -{% endhighlight %} +``` `ChatCmdBuilder.new(name, setup_func, def)` creates a new chat command called `name`. It then calls the function passed to it (`setup_func`), which then creates @@ -99,11 +99,11 @@ In `:name :hp:int`, there are two variables there: The first argument is the caller's name. The variables are then passed to the function in order. -{% highlight lua %} +```lua cmd:sub(":target :hp:int", function(name, target, hp) -- subcommand function end) -{% endhighlight %} +``` ## Installing ChatCmdBuilder @@ -125,7 +125,7 @@ Here is an example that creates a chat command that allows us to do this: * `/admin log ` - show report log * `/admin log ` - log to report log -{% highlight lua %} +```lua local admin_log local function load() admin_log = {} @@ -179,4 +179,4 @@ end, { ban = true } }) -{% endhighlight %} +``` diff --git a/_en/players/formspecs.md b/_en/players/formspecs.md index 1d6843e..d73bb7f 100644 --- a/_en/players/formspecs.md +++ b/_en/players/formspecs.md @@ -110,7 +110,7 @@ This example shows a formspec to a player when they use the /formspec command. -{% highlight lua %} +```lua -- Show form when the /formspec command is used. minetest.register_chatcommand("formspec", { func = function(name, param) @@ -121,20 +121,20 @@ minetest.register_chatcommand("formspec", { "button_exit[1,2;2,1;exit;Save]") end }) -{% endhighlight %} +``` Note: the .. is used to join two strings together. The following two lines are equivalent: -{% highlight lua %} +```lua "foobar" "foo" .. "bar" -{% endhighlight %} +``` ## Callbacks It's possible to expand the previous example with a callback: -{% highlight lua %} +```lua -- Show form when the /formspec command is used. minetest.register_chatcommand("formspec", { func = function(name, param) @@ -161,7 +161,7 @@ minetest.register_on_player_receive_fields(function(player, formname, fields) -- from receiving this submission. return true end) -{% endhighlight %} +``` The function given in minetest.register_on_player_receive_fields is called every time a user submits a form. Most callbacks will check the formname given @@ -185,14 +185,14 @@ Some elements can submit the form without the user clicking a button, such as a check box. You can detect these cases by looking for a clicked button. -{% highlight lua %} +```lua -- An example of what fields could contain, -- using the above code { name = "Foo Bar", exit = true } -{% endhighlight %} +``` ## Contexts @@ -202,7 +202,7 @@ what a chat command was called with, or what the dialog is about. For example, you might make a form to handle land protection information: -{% highlight lua %} +```lua -- -- Step 1) set context when player requests the formspec -- @@ -256,7 +256,7 @@ minetest.register_on_player_receive_fields(function(player, formname, fields) "Something went wrong, try again.") end end) -{% endhighlight %} +``` ## Node Meta Formspecs @@ -265,7 +265,7 @@ add formspecs to a [node's meta data](node_metadata.html). For example, this is used with chests to allow for faster opening times - you don't need to wait for the server to send the player the chest formspec. -{% highlight lua %} +```lua minetest.register_node("mymod:rightclick", { description = "Rightclick me!", tiles = {"mymod_rightclick.png"}, @@ -286,7 +286,7 @@ minetest.register_node("mymod:rightclick", { print(fields.x) end }) -{% endhighlight %} +``` Formspecs set this way do not trigger the same callback. In order to receive form input for meta formspecs, you must include an diff --git a/_en/players/hud.md b/_en/players/hud.md index 7ceb23c..621a204 100644 --- a/_en/players/hud.md +++ b/_en/players/hud.md @@ -87,7 +87,7 @@ to the right of the window, but to resize without breaking. You can create a HUD element once you have a copy of the player object: -{% highlight lua %} +```lua local player = minetest.get_player_by_name("username") local idx = player:hud_add({ hud_elem_type = "text", @@ -97,7 +97,7 @@ local idx = player:hud_add({ alignment = {x = 0, y = 0}, -- center aligned scale = {x = 100, y = 100}, -- covered later }) -{% endhighlight %} +``` The `hud_add` function returns an element ID - this can be used later to modify or remove a HUD element. @@ -116,7 +116,7 @@ table. The meaning of other properties varies based on this type. Let's go ahead, and place all the text in our score panel: -{% highlight lua %} +```lua player:hud_add({ hud_elem_type = "text", position = {x = 1, y = 0.5}, @@ -152,7 +152,7 @@ player:hud_add({ scale = { x = 50, y = 10}, number = 0xFFFFFF, }) -{% endhighlight %} +``` This results in the following: @@ -167,7 +167,7 @@ This results in the following: Image elements are created in a very similar way to text elements: -{% highlight lua %} +```lua player:hud_add({ hud_elem_type = "image", position = {x = 1, y = 0.5}, @@ -176,7 +176,7 @@ player:hud_add({ scale = { x = 1, y = 1}, alignment = { x = 1, y = 0 }, }) -{% endhighlight %} +``` You will now have this: @@ -199,7 +199,7 @@ For example, `x=-100` is 100% of the width. Let's make the progress bar for our score panel as an example of scale: -{% highlight lua %} +```lua local percent = tonumber(player:get_attribute("scoreboard:score") or 0.2) player:hud_add({ @@ -219,7 +219,7 @@ player:hud_add({ scale = { x = percent, y = 1}, alignment = { x = 1, y = 0 }, }) -{% endhighlight %} +``` We now have a HUD that looks like the one in the first post! There is one problem however, it won't update when the stats change. @@ -228,7 +228,7 @@ There is one problem however, it won't update when the stats change. You can use the ID returned by the hud_add method to update or remove it later. -{% highlight lua %} +```lua local idx = player:hud_add({ hud_elem_type = "text", text = "Hello world!", @@ -237,7 +237,7 @@ local idx = player:hud_add({ player:hud_change(idx, "text", "New Text") player:hud_remove(idx) -{% endhighlight %} +``` The `hud_change` method takes the element ID, the property to change, and the new value. The above call changes the `text` property from "Hello World" to "Test". @@ -245,16 +245,16 @@ value. The above call changes the `text` property from "Hello World" to "Test". This means that doing the `hud_change` immediately after the `hud_add` is functionally equivalent to the following, in a rather inefficient way: -{% highlight lua %} +```lua local idx = player:hud_add({ hud_elem_type = "text", text = "New Text", }) -{% endhighlight %} +``` ## Storing IDs -{% highlight lua %} +```lua scoreboard = {} local saved_huds = {} @@ -287,7 +287,7 @@ minetest.register_on_joinplayer(scoreboard.update_hud) minetest.register_on_leaveplayer(function(player) saved_huds[player:get_player_name()] = nil end) -{% endhighlight %} +``` ## Other Elements diff --git a/_en/players/player_physics.md b/_en/players/player_physics.md index 4f8478d..0ed62f4 100644 --- a/_en/players/player_physics.md +++ b/_en/players/player_physics.md @@ -23,7 +23,7 @@ gravity twice as strong. Here is an example of how to add an antigravity command, which puts the caller in low G: -{% highlight lua %} +```lua minetest.register_chatcommand("antigravity", { func = function(name, param) local player = minetest.get_player_by_name(name) @@ -33,7 +33,7 @@ minetest.register_chatcommand("antigravity", { }) end }) -{% endhighlight %} +``` ## Available Overrides diff --git a/_en/players/privileges.md b/_en/players/privileges.md index c87e3cf..a658797 100644 --- a/_en/players/privileges.md +++ b/_en/players/privileges.md @@ -50,37 +50,37 @@ minetest.conf file) is automatically given all available privileges. Use `register_privilege` to declare a new privilege: -{% highlight lua %} +```lua minetest.register_privilege("vote", { description = "Can vote on issues", give_to_singleplayer = true }) -{% endhighlight %} +``` If `give_to_singleplayer` is true, you can remove it, because true is the default when it is not specified. This simplifies the privilege registration to: -{% highlight lua %} +```lua minetest.register_privilege("vote", { description = "Can vote on issues" }) -{% endhighlight %} +``` ## Checking for Privileges To quickly check whether a player has all the required privileges: -{% highlight lua %} +```lua local has, missing = minetest.check_player_privs(player_or_name, { interact = true, vote = true }) -{% endhighlight %} +``` In this example, `has` is true if the player has all the privileges needed.\\ If `has` is false, then `missing` will contain a dictionary of missing privileges. -{% highlight lua %} +```lua if minetest.check_player_privs(name, {interact=true, vote=true}) then print("Player has all privs!") else @@ -95,43 +95,43 @@ if has then else print("Player is missing privs: " .. dump(missing)) end -{% endhighlight %} +``` ## Getting and Setting Privileges To get a table containing a player's privileges, regardless of whether the player is logged in, use `minetest.get_player_privs`: -{% highlight lua %} +```lua local privs = minetest.get_player_privs(name) print(dump(privs)) -{% endhighlight %} +``` This example may give: -{% highlight lua %} +```lua { fly = true, interact = true, shout = true } -{% endhighlight %} +``` To set a player's privileges, use `minetest.set_player_privs`: -{% highlight lua %} +```lua minetest.set_player_privs(name, { interact = true, shout = true }) -{% endhighlight %} +``` To grant a player privileges, use a combination of the above two functions: -{% highlight lua %} +```lua local privs = minetest.get_player_privs(name) privs.vote = true minetest.set_player_privs(name, privs) -{% endhighlight %} +``` ## Adding Privileges to basic_privs diff --git a/_en/players/sfinv.md b/_en/players/sfinv.md index 5ccf590..5750e21 100644 --- a/_en/players/sfinv.md +++ b/_en/players/sfinv.md @@ -27,14 +27,14 @@ some other format instead. So, to register a page you need to call the aptly named `sfinv.register_page` function with the page's name, and its definition. Here is a minimal example: -{% highlight lua %} +```lua sfinv.register_page("mymod:hello", { title = "Hello!", get = function(self, player, context) -- TODO: implement this end }) -{% endhighlight %} +``` You can also override an existing page using `sfinv.override_page`. @@ -42,7 +42,7 @@ If you ran the above code and clicked the page's tab, it would probably crash as sfinv is expecting a response from the `get` method. So let's add a response to fix that: -{% highlight lua %} +```lua sfinv.register_page("mymod:hello", { title = "Hello!", get = function(self, player, context) @@ -50,7 +50,7 @@ sfinv.register_page("mymod:hello", { "label[0.1,0.1;Hello world!]", true) end }) -{% endhighlight %} +``` The `make_formspec` function surrounds your formspec with sfinv's formspec code. The fourth parameter, currently set as `true`, determines whether or not the @@ -69,7 +69,7 @@ Let's make things more exciting. Here is the code for the formspec generation part of a player admin tab. This tab will allow admins to kick or ban players by selecting them in a list and clicking a button. -{% highlight lua %} +```lua sfinv.register_page("myadmin:myadmin", { title = "Tab", get = function(self, player, context) @@ -103,7 +103,7 @@ sfinv.register_page("myadmin:myadmin", { table.concat(formspec, ""), false) end, }) -{% endhighlight %} +``` There's nothing new about the above code, all the concepts are covered above and in previous chapters. @@ -120,11 +120,11 @@ in previous chapters. You can receive formspec events by adding a `on_player_receive_fields` function to a sfinv definition. -{% highlight lua %} +```lua on_player_receive_fields = function(self, player, context, fields) -- TODO: implement this end, -{% endhighlight %} +``` Fields is the exact same as the fields given to the subscribers of `minetest.register_on_player_receive_fields`. The return value of @@ -134,7 +134,7 @@ navigation tab events, so you won't receive them in this callback. Now let's implement the `on_player_receive_fields` for our admin mod: -{% highlight lua %} +```lua on_player_receive_fields = function(self, player, context, fields) -- text list event, check event type and set index if selection changed if fields.playerlist then @@ -163,7 +163,7 @@ on_player_receive_fields = function(self, player, context, fields) end end end, -{% endhighlight %} +``` There's a rather large problem with this, however. Anyone can kick or ban players! You need a way to only show this to players with the kick or ban privileges. @@ -174,12 +174,12 @@ Luckily SFINV allows you to do this! You can add an `is_in_nav` function to your page's definition if you'd like to control when the page is shown: -{% highlight lua %} +```lua is_in_nav = function(self, player, context) local privs = minetest.get_player_privs(player:get_player_name()) return privs.kick or privs.ban end, -{% endhighlight %} +``` If you only need to check one priv or want to perform an and, you should use `minetest.check_player_privs()` instead of `get_player_privs`. @@ -192,7 +192,7 @@ This means that you need to manually request that SFINV regenerates the inventor formspec on any events that may change `is_in_nav`'s result. In our case, we need to do that whenever kick or ban is granted or revoked to a player: -{% highlight lua %} +```lua local function on_grant_revoke(grantee, granter, priv) if priv == "kick" or priv == "ban" then local player = minetest.get_player_by_name(grantee) @@ -207,7 +207,7 @@ if minetest.register_on_priv_grant then minetest.register_on_priv_grant(on_grant_revoke) minetest.register_on_priv_revoke(on_grant_revoke) end -{% endhighlight %} +``` ## on_enter and on_leave callbacks @@ -221,7 +221,7 @@ Also, note that the inventory may not be visible at the time these callbacks are called. For example, on_enter is called for the home page when a player joins the game even before they open their inventory! -{% highlight lua %} +```lua on_enter = function(self, player, context) end, @@ -229,7 +229,7 @@ end, on_leave = function(self, player, context) end, -{% endhighlight %} +``` ## Adding to an existing page diff --git a/_en/quality/clean_arch.md b/_en/quality/clean_arch.md index 9339322..377e674 100644 --- a/_en/quality/clean_arch.md +++ b/_en/quality/clean_arch.md @@ -83,7 +83,7 @@ You should write your data representation using Pure Lua. "Pure" in this context means that the functions could run outside of Minetest - none of the engine's functions are called. -{% highlight lua %} +```lua -- Data function land.create(name, area_name) land.lands[area_name] = { @@ -96,12 +96,12 @@ end function land.get_by_name(area_name) return land.lands[area_name] end -{% endhighlight %} +``` Your actions should also be pure, however calling other functions is more acceptable. -{% highlight lua %} +```lua -- Controller function land.handle_create_submit(name, area_name) -- process stuff (ie: check for overlaps, check quotas, check permissions) @@ -113,13 +113,13 @@ function land.handle_creation_request(name) -- This is a bad example, as explained later land.show_create_formspec(name) end -{% endhighlight %} +``` Your event handlers will have to interact with the Minetest API. You should keep the amount of calculations to a minimum, as you won't be able to test this area very easily. -{% highlight lua %} +```lua -- View function land.show_create_formspec(name) -- Note how there's no complex calculations here! @@ -141,7 +141,7 @@ minetest.register_chatcommand("/land", { minetest.register_on_player_receive_fields(function(player, formname, fields) land.handle_create_submit(player:get_player_name(), fields.area_name) end) -{% endhighlight %} +``` The above is the Model-View-Controller pattern. The model is a collection of data with minimal functions. The view is a collection of functions which listen to @@ -217,7 +217,7 @@ Enter the Observer pattern. Instead of the mobs mod caring about awards, mobs exposes a way for other areas of code to register their interest in an event and receive data about the event. -{% highlight lua %} +```lua mobs.registered_on_death = {} function mobs.register_on_death(func) table.insert(mobs.registered_on_death, func) @@ -227,11 +227,11 @@ end for i=1, #mobs.registered_on_death do mobs.registered_on_death[i](entity, reason) end -{% endhighlight %} +``` Then the other code registers its interest: -{% highlight lua %} +```lua -- awards mobs.register_on_death(function(mob, reason) @@ -239,7 +239,7 @@ mobs.register_on_death(function(mob, reason) awards.notify_mob_kill(reason.object, mob.name) end end) -{% endhighlight %} +``` You may be thinking - wait a second, this looks awfully familiar. And you're right! The Minetest API is heavily Observer based to stop the engine having to care about diff --git a/_en/quality/common_mistakes.md b/_en/quality/common_mistakes.md index e0af8e1..40f9144 100644 --- a/_en/quality/common_mistakes.md +++ b/_en/quality/common_mistakes.md @@ -21,7 +21,7 @@ offline or the entity is unloaded - then calling any methods will result in a cr Don't do this: -{% highlight lua %} +```lua minetest.register_on_joinplayer(function(player) local function func() local pos = player:get_pos() -- BAD! @@ -36,11 +36,11 @@ minetest.register_on_joinplayer(function(player) -- It's recommended to just not do this -- use minetest.get_connected_players() and minetest.get_player_by_name() instead. end) -{% endhighlight %} +``` instead, do this: -{% highlight lua %} +```lua minetest.register_on_joinplayer(function(player) local function func(name) -- Attempt to get the ref again @@ -56,7 +56,7 @@ minetest.register_on_joinplayer(function(player) -- Pass the name into the function minetest.after(1, func, player:get_player_name()) end) -{% endhighlight %} +``` ## Don't Trust Formspec Submissions @@ -66,7 +66,7 @@ they like. The following code has a vulnerability where any player can give themselves moderator privileges: -{% highlight lua %} +```lua local function show_formspec(name) if not minetest.check_player_privs(name, { privs = true }) then return false @@ -89,11 +89,11 @@ minetest.register_on_player_receive_fields(function(player, formname, fields) minetest.set_player_privs(fields.target, privs) return true end) -{% endhighlight %} +``` Instead, do this: -{% highlight lua %} +```lua minetest.register_on_player_receive_fields(function(player, formname, fields) if not minetest.check_player_privs(name, { privs = true }) then return false @@ -101,7 +101,7 @@ minetest.register_on_player_receive_fields(function(player, formname, fields) -- code end) -{% endhighlight %} +``` ## Set ItemStacks After Changing Them @@ -112,22 +112,22 @@ This means that modifying a stack won't actually modify that stack in the invent Don't do this: -{% highlight lua %} +```lua local inv = player:get_inventory() local stack = inv:get_stack("main", 1) stack:get_meta():set_string("description", "Partially eaten") -- BAD! Modification will be lost -{% endhighlight %} +``` Do this: -{% highlight lua %} +```lua local inv = player:get_inventory() local stack = inv:get_stack("main", 1) stack:get_meta():set_string("description", "Partially eaten") inv:set_stack("main", 1, stack) -- Correct! Item stack is set -{% endhighlight %} +``` The behaviour of callbacks is slightly more complicated. Modifying an itemstack you are given will change it for the caller too, and any subsequent callbacks. However, @@ -135,23 +135,23 @@ it will only be saved in the engine if the callback caller sets it. Avoid this: -{% highlight lua %} +```lua minetest.register_on_item_eat(function(hp_change, replace_with_item, itemstack, user, pointed_thing) itemstack:get_meta():set_string("description", "Partially eaten") -- Almost correct! Data will be lost if another callback cancels the behaviour end) -{% endhighlight %} +``` If no callbacks cancel, then the stack will be set and the description will be updated. If a callback cancels, then the update may be lost. It's better to do this instead: -{% highlight lua %} +```lua minetest.register_on_item_eat(function(hp_change, replace_with_item, itemstack, user, pointed_thing) itemstack:get_meta():set_string("description", "Partially eaten") user:get_inventory():set_stack("main", user:get_wield_index(), itemstack) -- Correct, description will always be set! end) -{% endhighlight %} +``` If the callbacks cancel or the callback runner doesn't set the stack, then our update will still be set. diff --git a/_en/quality/luacheck.md b/_en/quality/luacheck.md index a7428af..dfdeba8 100644 --- a/_en/quality/luacheck.md +++ b/_en/quality/luacheck.md @@ -59,7 +59,7 @@ root of your game, modpack, or mod. Put the following contents in it: -{% highlight lua %} +```lua unused_args = false allow_defined_top = true @@ -78,7 +78,7 @@ read_globals = { -- MTG "default", "sfinv", "creative", } -{% endhighlight %} +``` Next you'll need to test that it works by running LuaCheck. You should get a lot less errors this time. Starting at the first error you get, either modify the @@ -121,7 +121,7 @@ and enable Travis by flipping the switch. Next, create a file called .travis.yml with the following content: -{% highlight yml %} +```yml language: generic sudo: false addons: @@ -134,14 +134,14 @@ script: - $HOME/.luarocks/bin/luacheck --no-color . notifications: email: false -{% endhighlight %} +``` If your project is a game rather than a mod or mod pack, change the line after `script:` to: -{% highlight yml %} +```yml - $HOME/.luarocks/bin/luacheck --no-color mods/ -{% endhighlight %} +``` Now commit and push to Github. Go to your project's page on Github, and click commits. You should see an orange disc next to the commit you just made. diff --git a/_en/quality/security.md b/_en/quality/security.md index 685a0b6..2b9bb52 100644 --- a/_en/quality/security.md +++ b/_en/quality/security.md @@ -40,7 +40,7 @@ Any users can submit almost any formspec with any values at any time. Here's some real code found in a mod: -{% highlight lua %} +```lua minetest.register_on_player_receive_fields(function(player, formname, fields) -- Todo: fix security issue here local name = player:get_player_name() @@ -56,7 +56,7 @@ minetest.register_on_player_receive_fields(function(player, formname, fields) end end end -{% endhighlight %} +``` Can you spot the issue? A malicious user could submit a formspec containing their own position values, allowing them to teleport to anywhere they wish to. @@ -90,19 +90,19 @@ to the full Lua API. Can you spot the vulnerability in the following? -{% highlight lua %} +```lua local ie = minetest.request_insecure_environment() ie.os.execute(("path/to/prog %d"):format(3)) -{% endhighlight %} +``` `String.format` is a function in the global shared table `String`. A malicious mod could override this function and pass stuff to os.execute: -{% highlight lua %} +```lua String.format = function() return "xdg-open 'http://example.com'" end -{% endhighlight %} +``` The mod could pass something a lot more malicious than opening a website, such as giving a remote user control over the machine. diff --git a/_en/quality/unit_testing.md b/_en/quality/unit_testing.md index 63d32ca..6cf9836 100644 --- a/_en/quality/unit_testing.md +++ b/_en/quality/unit_testing.md @@ -49,25 +49,25 @@ names ending in `_spec`, and then executes them in a standalone Lua environment. ### init.lua -{% highlight lua %} +```lua mymod = {} dofile(minetest.get_modpath("mymod") .. "/api.lua") -{% endhighlight %} +``` ### api.lua -{% highlight lua %} +```lua function mymod.add(x, y) return x + y end -{% endhighlight %} +``` ### tests/api_spec.lua -{% highlight lua %} +```lua -- Look for required things in package.path = "../?.lua;" .. package.path @@ -87,7 +87,7 @@ describe("add", function() assert.equals(-2, mymod.add(-1, -1)) end) end) -{% endhighlight %} +``` You can now run the tests by opening a terminal in the mod's directory and running `busted .` @@ -114,7 +114,7 @@ things not in your area however - for example, you'll have to mock the view when testing the controller/API. If you didn't follow the advice, then things are a little harder as you may have to mock the Minetest API. -{% highlight lua %} +```lua -- As above, make a table _G.minetest = {} @@ -157,7 +157,7 @@ describe("list_areas", function() assert.same(expected, chat_send_all_calls) end) end) -{% endhighlight %} +``` ## Checking Commits with Travis @@ -165,7 +165,7 @@ end) The Travis script from the [Error Checking](luacheck.html) chapter can be modified to also run Busted. -{% highlight yml %} +```yml language: generic sudo: false addons: @@ -179,7 +179,7 @@ script: - $HOME/.luarocks/bin/busted . notifications: email: false -{% endhighlight %} +``` ## Conclusion