Now, fences aren't all that exciting. Minecraft's had them for almost two years now. No, the cool bit is how they were scripted.
The 3-D model
Unlike items and mobs, custom block models cannot be imported from just any Blender or Collada file. To keep things manageable, they are essentially a 16x16x16 chunk scaled down to the size of a block. You define them as a list of boxes, where all coordinates are integers, limited from 0 to 16. Every face of every box can be textured individually, the same way a normal block can.
For example, here's a snippet of the Lua script that defines the materials:
define_material({ name = "fence.0000",
on_place = place_fence, on_remove = remove_fence,
custom_model = { { 7, 7, 0, 9, 9, 15, "fence" } } })
define_material({ name = "fence.000E",
on_remove = remove_fence,
custom_model = { { 7, 7, 0, 9, 9, 15, "fence" },
{ 10, 8, 10, 15, 8, 12, "bark" } } })
A fence can connect to its neighbors in any combination of the four cardinal directions, so we need 16 different models in total. You're free to pick any name you like, but I've used a naming system here that shows in which directions the fence extends. "Fence.0000" is just a post, a single box with the 'fence' texture. "Fence.000E" extends to the east, and consists of the same post, and another box with the much darker 'bark' texture. We also need "Fence.00W0", "Fence.00WE", "Fence.0N00", and so on.
(Ordering the models by binary counting will come in handy later.)
Examples of fence models
All this info is sent to the client on login. So if you join a server, there's no need to install anything, you automatically get to see whatever cool custom blocks they designed for their map.
This is nice for static decoration, but if you want to give the player the opportunity to build his or her own fences, there's a little more scripting involved.
(Note: the idea for custom block models isn't quite new, Minetest recently added "nodeboxes" that can do the same thing.)
Scripting the behavior
Every fence material gets a number. We'll say 'fence.0000' is material number 100, and the others are numbered incrementally. If the player places a fence, the value in the chunk's array gets changed from 0 (air) to 100, and a fence post appears. So far so good, but the second fence placed right next to it also looks like a post. When this happens, we want to connect the two. So the first one should change from 100 to 104 ('fence.0N00'), and the other to 108 ('fence.S000').
local function is_fence(a, b)
return b >= a and b < (a + 16)
end
local function place_fence(p, id)
local e = p + vec(1, 0, 0)
local n = p + vec(0, 1, 0)
local w = p + vec(-1, 0, 0)
local s = p + vec(0, -1, 0)
if (is_fence(id, get_block(e))) then
id = id + 1
change_block(e, get_block(e) + 2)
end
if (is_fence(id, get_block(w))) then
id = id + 2
change_block(w, get_block(w) + 1)
end
if (is_fence(id, get_block(n))) then
id = id + 4
change_block(n, get_block(n) + 8)
end
if (is_fence(id, get_block(s))) then
id = id + 8
change_block(s, get_block(s) + 4)
end
change_block(p, id)
end
'place_fence' only gets called for "fence.0000". The parameters are the position, and the material ID (in this case, 100). We use this ID to check the four neighbors (e, w, s, and n), to see if they are fences as well. This check is rather simple; we've registered 16 materials, so they should be somewhere in the range 100 to 116.
For every fence we find, we adjust the model of that neighbor block so it connects to the one we're about to place. And of course we adjust 'id' as well, we need to connect them both ways.
Removing a fence is very similar, but instead of adding a given value for each direction, we subtract it:
local function remove_fence(p, id)
local e = p + vec(1, 0, 0)
local n = p + vec(0, 1, 0)
local w = p + vec(-1, 0, 0)
local s = p + vec(0, -1, 0)
if (is_fence(id, get_block(e))) then
change_block(e, get_block(e) - 2)
end
if (is_fence(id, get_block(w))) then
change_block(w, get_block(w) - 1)
end
if (is_fence(id, get_block(n))) then
change_block(n, get_block(n) - 8)
end
if (is_fence(id, get_block(s))) then
change_block(s, get_block(s) - 4)
end
change_block(p, 0)
end
No need to adjust 'id', at the end we're going to place an air block there anyway.
More to come
Of course, you can build much more with this API than just fences. Doors, stairs, window shutters, pipes, ladders, wooden beams, rails, you name it. How about fences that connect to walls as well? With gates? I'll leave all that to your imagination for now, and in the next few posts I'd like to show other parts of the API, such as the user interface and items.