Episode 22 of a series about building an MMO from scratch, with the focus on the engineering around the game. Each world gets a second land, the Ashlands, next to the island we had, which becomes the Meadows, with a gate between them, the way MMOs have zones. First, the look of the island goes into one place: the sky, the fog, the sun and the ambient light become an atmosphere that the shaders read each frame, and the settings window tries presets on the fly, which turns up a fog bug and settles a new light for the Meadows. Then the world server runs both lands in one process, each with its own island, players and monsters, a gate takes players from one to the other, a Flyway migration keeps the land a player is in, and the metrics and the bots learn about lands too.
The code
Each step on screen is a tag in the game's repository. The code at the end of the episode is ep022/end.
- 4:35
ep022/step-1The look in one placeBrowseChanges - 15:00
ep022/step-2Settling the MeadowsBrowseChanges - 18:06
ep022/step-3Two landsBrowseChanges - 30:35
ep022/step-4Through the gateBrowseChanges - 33:53
ep022/step-5Keeping the landBrowseChanges - 38:06
ep022/step-6Lands in the metricsBrowseChanges - 41:58
ep022/step-7DocsBrowseChanges
Transcript
Intro
0:00 Hello and welcome back to episode twenty-two, where we're going to give each world a second land, with a gate that takes you there, and give each land a look of its own. Let's begin with a quick look at where we're going to be by the end of the episode.
0:14 From where we start in the Meadows, I walk north, to a ring of standing stones, which is the gate to the other land. On the other side are the Ashlands, a land of their own, with ash and charred trees around a sea of lava, under a red haze.
0:29 On the website of the series you can find more information about the project. The code for the project is open source on GitHub, tagged at each step of each episode so that you can follow along.
Last time
0:41 Now, before we get to today's work, let's quickly go over what we did last time. We ran Consul, the service registry from HashiCorp, next to our servers. Each world server registers with it, and sends it a heartbeat each two seconds from its tick loop, so that a world that stops drops out of the list. The login server asks Consul for the worlds that are up, and the client shows them before you log in, so that you pick a world. A migration gave each world its own players, and a third world joined the list and our dashboard with a single command.
Today
1:13 Today, we're going to put the look of our island in one place, its colours, its fog and its light, so that a land's look is data, and we can try a few looks on the fly. With that, we settle on a new light for the island we have, which becomes the Meadows. Then each world gets a second land, the Ashlands, which the world server runs next to the Meadows, with a gate between them. The players table keeps the land you're in, and our metrics and our bots learn about lands too. All right, let's get started with what a land is, next to the worlds from last time.
Lands
1:46 Last time's worlds are copies of the game, each with its own players, and your character in one world has nothing to do with your character in another. A land is a different idea: a place inside one world, with its own island, its own monsters and its own look. Your character goes from one land to the other and back, and takes its level, its experience and its health along. MMOs call these zones, maps or continents, and most have a lot of them, from the area where you start to the dungeons. The island we have becomes the Meadows, where players start, and next to it, each world gets the Ashlands, an island from another seed, with ash and charred trees around a sea of lava.
2:24 There are two ways to run lands, and many MMOs run a server for each land, or for each zone, and a trip to another land hands your character over, from one server to the other. The first server saves you and gives you a token, like our login server does, and your client connects to the second one, usually behind a loading screen. That lets a busy land have a machine of its own, while each trip costs a save, a token and a new connection. The other way is a single server that runs all the lands of its world, so that going through a gate is a move from one map to another, within a tick, with nothing to hand over. My proposal is the second, at least for now, as a world server keeps up with about twelve hundred players on my machine, and a second land spreads a world's players over two islands, without adding any. Both lands share one tick loop, so a slow land slows the other, and a crash takes both of them down. Each land is going to be an object of its own on the server, so if a land needs a machine of its own one day, the work is the handoff, while the lands stay as they are.
The look in one place
3:25 Right now, the look of our island is spread over the client. The sky is the renderer's background colour, which the fog fades into, and the fog's two distances are constants in the renderer. The sun's direction is written into the code of our two vertex shaders, and so are the point five five and the point four five from episode two, which light each face. The ground's four colours are constants in ground dot c c, and the sea's colour is in ground dot h. A second land with a look of its own would mean a second copy of each of those, so we're going to gather them in one struct, and have the renderer take a land's look as data.
3:59 In episode two, each face got a base of point five five, and up to point four five more as it turned towards the sun, from the dot product of its normal and the sun's direction. We're going to keep the formula, and give its two numbers names. Ambient is the light a face gets even when it's turned away from the sun, which outside is the light from the sky all around. And sunlight is what the sun adds on top, for a face that's turned towards it. Each of them becomes a colour, red, green and blue, so that a warm sun can light the faces turned to it, while the faces in the shade get a cooler light, the way they do outside.
4:35 The look gets a header of its own in the client, atmosphere dot h. It includes raylib, for its colour type, and array, for the colours that we keep as three floats, red, green and blue, from zero to one, which is how the shaders take them. Atmosphere is the part of the look that the shaders read each frame, and its comment says so: the sky and the light. Sky is the background colour, which the fog fades into, as in episode fifteen. Fog near and fog far are where the fog starts and where it hides everything, in metres from the camera, with the comment that sat over the renderer's constants. Sun is the direction towards the sun, which the shaders are going to normalise, so its length doesn't matter. Sunlight is what the sun adds to a face that's turned to it, and ambient is what a face gets even when it's turned away, as we just saw. A look is a land's colours: its atmosphere, and the colours that go into its meshes. Sand, grass, rock and snow are the ground's four colours, by height and slope, as raylib colours from zero to two fifty-five, as that's how the vertices keep them. Sea is the sea's colour, with its alpha, and trees multiplies the colours of the trees' faces, so that a land can tint its trees without a second model. Then the look we have, as a constexpr, so that it's built at compile time, with designated initialisers, which name each field. We're going to call the island we have the Meadows, as it gets a neighbour later today. Its values are the ones we had in the renderer and the shaders: the sky blue, fog from fifty to a hundred and forty metres, and the sun's direction. Then point four five of sunlight and point five five of ambient light on each channel, so that the light stays white, like before, and then the ground's four colours, from ground dot c c. The sea keeps its alpha of two hundred and ten, so that the island still shows through it near the shore. White trees leave the trees' colours as they are, so for now, nothing changes on screen.
6:32 In renderer dot h, we include our new header, and the fog's two constants go, as each land is going to have fog of its own. Set background stays, as the tests use it, and now it sets the sky's colour. Mutable atmosphere takes the place of mutable background, for the settings window, which edits the whole atmosphere in place. Set terrain takes a look, and its comment says that it builds the terrain's meshes in the look's colours, and takes on the look's atmosphere, so that going to another land is a single call. And the renderer keeps an atmosphere, which starts as the Meadows'. In renderer dot c c, our shaders are written at the top of the file, as strings, and they're where the light gets worked out for each corner. First, the vertex shader for the ground, the trees and the cubes. It gets three more uniforms, next to the fog's: the sun's direction, the sunlight and the ambient light, each a vec three. The sun used to be a constant in the shader's code. Light becomes a vec three too, a colour instead of a brightness, so that each channel gets light of its own. Light is the formula we just saw: the ambient light, plus the sunlight times the dot product of the normal and the sun, where it's above zero. Normalize turns the sun's direction into a vector of length one, so that a land can write it with any length, and the dot product stays between minus one and one. The skinned shader, for the characters, gets that change too, so that the characters and the ground are lit alike, and the fragment shader takes light in as a vec three, and multiplies the colour by it, as before. Set fog becomes set atmosphere, which sets all six uniforms on a shader, from an atmosphere. The fog's colour comes from the sky, and its comment moves inside, over that call: into the sky's colour, so that far away things fade into the sky. Fog near and fog far come from the atmosphere, instead of the constants. Then three more calls of set shader value, one for each of the new uniforms. Get shader location finds each uniform by its name, so the names have to match the shaders'. The sun, the sunlight and the ambient light go in as vec threes, from their arrays, so set shader value sends three floats for each of them, from the pointer that data returns. Further down, set terrain takes the look as well, like its declaration in the header. It takes on the look's atmosphere first, and then hands the look to load chunks, which bakes the look's colours into the chunks' meshes, and the sea takes its colour from the look, instead of k sea. Draw sets the atmosphere on both shaders, each frame, so that a change in the settings window shows up straight away, and the culling stops at the atmosphere's far fog, as nothing past it shows. The character screen's preview has its own background, and now it takes the Meadows' atmosphere with that background. The comment says why: lit like the Meadows, wherever we are, so that a character's preview doesn't take on the red light of the Ashlands. Last, clear clears the screen to the sky's colour, which is the colour behind everything we draw.
9:32 The look's colours have to reach the meshes, and that starts in ground dot h, which includes atmosphere dot h. Add ground takes the look, after the terrain, and its comment says that the triangles are in the look's colours, by height and slope. K sea goes, as the sea's colour is in the look now. In ground dot c c, the four colour constants go too, while the heights where the sand turns to grass, and the grass to rock and snow, stay, as they belong to the island's shape. Ground colour takes the look, and returns its sand on the beaches, its snow on the peaks, its rock on the steep slopes and the high ground, and its grass everywhere else, by the rules we had. The heights and the order of the checks are as they were, so with the colours we had, the Meadows come out as before. Add face takes the look along, to hand it to ground colour. And add ground takes the look too, and hands it to add face, for both triangles of each cell, where the hash still varies each face a little. For the trees, add faces gets a tint, and its comment says that the faces' colours are multiplied by the tint. In trees dot c c, raylib's color tint does the multiplying, channel by channel, so that white leaves a colour as it was, and a dark tint darkens it. Chunks dot h includes our header, and load chunks takes the look, which it hands to add ground for the ground of each chunk, while each tree gets the look's tint.
10:55 Now the settings window, which had a colour picker for the background, and five presets of a colour each. In menu dot h, atmosphere dot h comes in, in place of array. Build takes the atmosphere to edit, by reference, and the land's own atmosphere, as a const reference, to go back to. Its comment says that the settings window edits the atmosphere, and reset puts back the land's own. In menu dot c c, array, as menu dot c c uses it directly now. A preset was a name and a colour, and it becomes a name and an atmosphere, so that a preset can change the fog and the light too. Dusk has a pinkish sky, the fog a little nearer, and a low sun from the west, so its x is minus point eight, while its y is low. The sunlight is orange, with all of the red, and the ambient light is bluish, so that the faces turned away from the sun go cool. Night is a dark blue sky, with the fog much closer, from twenty metres to a hundred. The sunlight is a dim blue, like light from the moon, and there's very little ambient light, so the shade is almost black. Mist is a grey sky, with fog from five metres to sixty, and a weak sun with a lot of ambient light, as in a mist, most of the light comes from all around. And ash is a red haze, with the fog from thirty-five metres, a low sun with an orange light, and a little red in the ambient light. The Ashlands' look is going to start from this one. In build, the window gets a control for each field. The sky is a colour editor, like the background was. The fog is ImGui's drag float range two: two numbers that you drag left and right, from zero to four hundred metres, which keeps the first under the second. The sun is three sliders, x, y and z, from minus one to one, and the sunlight and the ambient light are colour editors too, as they're colours now. A button for each preset copies its atmosphere into the renderer's, and reset, with an arrow that goes round to the left, copies the land's own back. In the client's main, the island is built in the Meadows' look, and the menu gets the renderer's atmosphere to edit, and the Meadows' atmosphere to reset to. The benchmark builds the island in the Meadows' look too, and in the renderer's tests, the terrain test sets its sky after the terrain, as setting the terrain takes on the look's sky.
13:11 Let's build it and run it, with the login server and our world, Ember, next to it. I create an account, run west towards a lake, and zoom out, so that we see more of the island. The Meadows look as they did, as their atmosphere starts with the values we had. In the view menu, the settings window has the atmosphere's fields now, with the presets under them. Dusk turns the sky pink, and the low sun from the west lights one side of each tree in orange, while the shade goes blue. Night is dark, with a dim blue moonlight on the hills, and the fog closing in around us. With mist, the fog starts five metres from the camera, and the island fades into grey, except for the lake, which stays as blue as it was, through the fog. Keep that in mind, as we're going to come back to it. Ash is the red haze that the Ashlands are going to have, on the grass of the Meadows for now, and reset puts the Meadows' own atmosphere back.
Settling the Meadows
14:06 So, the lake stayed blue because of how we work out the fog. In episode fifteen, the vertex shader worked out the fog at each corner of a triangle, and let the GPU blend it across the triangle. I said then that this is enough, as fog changes slowly across a triangle two metres wide, and for the ground, that's still true. The sea, though, is a single square, four times as wide as the island, so about two kilometres across. Its corners are far out past the fog, or behind the camera, where the depth is below zero and the fog comes out as none. So what gets blended across the lakes has little to do with how far away they are. With fog that ends at a hundred and forty metres, the lakes came out about right, and with the mist's near fog, the problem shows. So we're going to work out the fog for each pixel, from its depth, which costs a few more instructions for each pixel, and gives each one the fog of its own distance.
15:01 In renderer dot c c, the vertex shader works out the fog from w, the depth after the projection, at each corner. So the fog's two uniforms leave the vertex shaders, and the fog they handed on to the fragment shader becomes the depth. Depth is gl position dot w, as it is, and the GPU blends the depth across the triangle, which is right for a triangle of any size, as the depth changes evenly across it. The skinned shader gets that change too, so that the characters fade like the ground. The fragment shader takes the fog's two uniforms in, and works out the fog for its own pixel, with the formula we had, from the depth that it gets. Then it mixes the colour towards the sky by that fog, as before, so the sea's far corners no longer matter.
15:47 With the fog fixed, let's settle on a look for the Meadows, which I picked by trying values in the settings window, and comparing stills of the island from a few places. The sky keeps its blue, a shade deeper, so that it stands apart from the warmer ground. The fog starts at forty-five metres instead of fifty, and ends at a hundred and fifty, so the hills fade over a longer stretch, which gives the island some depth. The sun was high, and a little behind the camera as you play, so most of the faces we see were lit about alike, and the hills looked flat. It moves lower, and to the side, so that the faces of a slope get different light, and the low-poly shapes stand out. The sunlight gets stronger and warm, with more red than blue, while the ambient light goes down, and cooler, with more blue. So the faces in the shade are darker than they were, and a little blue, as shade is outside. With more sunlight and less ambient light, the ground in the sun comes out about as bright as it was, and the shade is what changes. And the grass goes a little more yellow, as the cool shade made it look a little cold.
16:53 Let's run it again, and go back to the lake, and zoom out. The Meadows have their new look, with the sun from the side picking out the faces of the slopes and the trees, and the shade going a little blue. In the settings window, the mist fades the lake into the fog now, like the rest of the island, and reset brings back the Meadows' new atmosphere.
Two lands
17:13 Now for the lands, where the server, the client and later the bots have to agree on which lands there are. So the lands go in a table in terrain, the library that both sides link, next to the island's code. Each land's island comes from the world's seed plus a number of the land's own, so Ember's Ashlands differ from Frost's, while Ember's Meadows stay the island we know. Each land says how deep you can wade into its sea, which in the Ashlands is not at all, as their sea is lava. And each land has a gate, ten metres north of its middle, which takes you to the other land. The server tells a client which land it's in by the land's place in the table, so the table's order matters to both sides. On the server, each land is going to be a World, the class that runs our island's players and monsters, and on the client, each land gets a look, with the Ashlands' look next to the Meadows'.
18:06 In terrain, a new header, lands dot h, which the server, the client and the bots are going to share. After the header guard, array, for the list of lands, c s t d int, for the seed's fixed-size integer, and terrain dot h, for the depth that players wade to. A place is an x and a z, in metres from the middle of a land, as each land is an island of its own, with its own middle. A land has a name, as a const char pointer, as it's text written into the code, which a std string would copy for nothing. Seed is added to the world's seed, so that each land has an island of its own. Wading is how deep into its sea players can wade, in metres, with the knee-deep wading we've had as the default. The gate is where the land's gate stands, and to is the land it takes you to, by its place in the list. K lands is the list, a constexpr array of two lands. Its comment says why the order matters: the server tells a client which land it's in by its place in the list, so the server and the client list them in one order. The Meadows come first, with nothing added to the seed, so that they're the island we've had, with the wading we've had. Their gate is ten metres north of the middle, where north is minus z, and takes you to land one. The Ashlands add a thousand to the seed, more than any world's seed we're going to use, and a wading of zero, as their sea is lava, which nobody should wade into. Their gate is ten metres north of their middle too, which takes you back to land zero. Ten metres from the middle is inside the clearing where no trees grow, on ground that stays above the sea on the seeds we use, so each island has room for its gate. K gate radius is how close to a gate's middle takes you through it, a metre, and its comment says that it's in metres from the middle of a gate. In gate says whether an x and a z are inside a land's gate, and that's all the header needs for now. In lands dot c c, our header first, then c math, for std hypot. In gate takes the gap between the place and the gate's middle, along x and along z, and hypot turns the two into the length of the gap, which has to be under the radius. And in terrain's CMakeLists, lands dot c c joins the library, next to the noise and the terrain, so that the server and the client both get it. And the wading has to reach the terrain, as walkable is where the depth counts. In terrain dot h, the island's constructor takes how deep players can wade, with k wading as the default, so that the client, which moves nobody, stays as it is. Its comment says that players can wade into its sea as deep as wading, in metres. The terrain keeps the depth in wading, and in terrain dot c c, the constructor keeps it, and walkable compares the ground's height with the sea level less that wading, instead of k wading.
20:57 The client has to know which land it's in, so in nanommo dot proto, the world message gets land, field ten, after the seed, as a uint thirty-two, the land's place in the list. Like the seed, it's one of each client's own fields, which the server adds to each world that it sends that client. On the client, parts puts a world back together from its datagrams, and keeps the client's own fields from the newest part. So parts dot h gets a land, under the seed, and in parts dot c c, the comment about the room we keep for those fields in each datagram lists the land too, next to the seed. Add keeps the land from each part, like the seed, and world hands it on, in the world that the client draws. In the parts test, the test that splits a world into datagrams checks that each datagram stays under twelve hundred bytes, with the client's own fields at their largest. So it sets the land to four billion as well, which no land is, as a number that big takes the most bytes on the wire.
21:56 On to the server, where server dot h includes c s t d def, for size t, and vector. Players returns how many players the world has, which Consul's heartbeat sends, and its comment says that it counts them in all its lands now, so it moves to the dot c c, as it's a loop now. Land of finds the land a player is in, by their ID, as a pointer to its World, or a null pointer when they're in none. Travel moves the players who walked into a gate, and broadcast land sends a land's units to its players, by the land's index, and whether this tick sends the names. And where the server had a World, it has lands, a vector with a World for each land. In server dot c c, our lands header, next to the terrain's. The constructor used to make the World in its initialiser list, and now its body makes the lands, as that takes a loop. There's a World for each land in the list, each with a random seed for its monsters, and a terrain from the world's seed plus the land's, with the land's wading. Emplace back builds each World in place, at the end of the vector, as a World is big, with its island in it. Players adds up the players of each land, as an int, which is what the registration takes, with a static cast, as size returns an unsigned size t. In start, each land gets its skeletons, the three near the middle and the camps, which come from its own terrain, so each island has its camps in places of its own, on its own grass. Land of goes through the lands, and returns the one whose players have the ID, or null. With two lands, a loop over them is simpler than keeping a map from each player to their land up to date, and it can't fall out of step with where the players are. An input from a client goes to the land the player is in, once we know there is one, and a hello is refused when the player is already in a land, as it was with a single world. A player who joins goes into the first land, the Meadows, for now, and the metrics get the players of all the lands, while the log line takes the name from the player we loaded. Leave finds the player's land, to log their name, and takes them out of it, and the metrics get the players of all the lands again. Each tick steps each land, and then travel moves the players who walked into a gate, before we save and broadcast, so that a player's save and the world they're sent are both from after their trip. Travel goes through the lands by an index called from, as it needs a land's place in the list to find where its gate leads. A reference called here keeps the land we're in, from the list. For each land, we note who's in its gate first, in travellers, and the comment says why: leaving changes the map we go through, and changing a map while we loop over it breaks the loop. In gate checks each player's position against this land's gate, and the ones in it go on the list. Then for each traveller, we copy the player out of the land, and leave takes them out of it. The gate leads to the land we call there, and the player comes out at that land's gate, at its x and its z. The log says who went where, and join puts them into the other land, with std move, as the copy is ours to give away. They keep their name, their experience and their health, as the copy has all of those. Save changed goes through the players of each land, so that it saves them wherever they are. Broadcast still works out whether this tick sends the names and the levels, once for all the lands, and then broadcasts each land, by its index. Broadcast land is the broadcast we had, for a single land, which it takes from the vector by its index. Its players' and monsters' pieces, and the interest cells, come from that land's players and monsters, so that a player sees the units of their own land, and no others. The experience in a player's own fields comes from the land they're in too. And each client's own fields get the land's seed, which is the world's plus the land's, and the land, by its index, which the client uses to pick the land's look.
25:46 On the client, the Ashlands get their look, in atmosphere dot h, which includes our lands header now. The atmosphere comes from the ash preset: a red haze for the sky, and fog from thirty-five to a hundred and thirty metres. The sun is low in the north-east, which you face as you walk out of the gate, so that the trees stand dark against the light, with an orange sunlight and a little red in the ambient light. Where the Meadows have sand, the Ashlands have black sand, by the lava. Where the grass would be, it's ash, a warm grey, and the rock is dark, while the peaks, where the snow was, glow a deep orange, like cooling lava. The sea is lava, a yellow orange, with an alpha of two fifty-five, so that you can't see through it, and the light darkens it towards orange. And the trees' tint is a dark brown, which leaves them charred, from the model we have. K looks lists the looks, and its comment says that they're in the order of the lands, so that a land's index finds its look. Static assert checks at compile time that there's a look for each land, so a third land with no look stops the build, with the message that each land needs its look.
26:54 A gate has to be something players can see from a distance. The renderer has drawn cubes since episode two, so a ring of standing stones is a few cubes, and needs no model to download. In gate dot h, after the header guard, vector, as a gate is a list of cubes, and our headers, for the cube and the terrain. Gate cubes returns the cubes of a land's gate, standing on that land's terrain, for the land at an index in the list. Its comment says what they are: a ring of standing stones around the land's gate, and a slab in the middle in the colour of the sky where it takes you. In gate dot c c, which is a new file in the client, our header first. Then array, for the sky's colour, c math, for cos and sin, numbers, for pi, and vector, and our atmosphere and lands headers, for the looks and the gates. In the unnamed namespace, k stones is six of them, and k ring is how far they stand from the middle of the gate. K stone is a stone's size, sixty centimetres wide and two point six metres tall, as a raylib vector, so that the stones stand taller than a player, and the gate shows from a distance. Gate cubes takes the land's gate from the list, and starts with an empty vector of cubes. For each stone, an angle around the ring, a sixth of the way round each time, and cos and sin put the stone on the circle around the gate's middle. Each stone is a cube at the ground's height there, with its size and the land's rock colour. A cube's position is its middle, so a stone goes up by half its height, as the comment says, to stand on the ground. Then the slab, in the colour of the other land's sky, which we find through the land's to. It's two metres across, as wide as the gate, and twenty centimetres thick, on the ground in the middle, so that half of it is under the ground. Color from normalized turns the sky's floats into a raylib colour, so that the Meadows' gate shows the Ashlands' red, and the Ashlands' gate shows the Meadows' blue. And gate dot c c joins the client's library in CMake, next to the chunks.
28:54 In the client's main, the gate's header and the lands', and land, the index of the land we're in, which starts with the Meadows. The client built the island again whenever the seed changed, and with a seed of its own for each land, a new seed is a new land, as the comment over it says. Land comes from the world, when it's a land the client knows, and the comment says what happens otherwise: a land from a newer server looks like the Meadows. An index past the end of the list would read memory that isn't the list's. The renderer builds the land in its look, the land's gate goes in as its cubes, and the log says which land we're in. The character screen gets the land's name, and reset in the settings goes back to the land's own atmosphere. In character screen dot h, build takes the land's name, and in the dot c c, the position line says which land we're in, and then the position in it.
Through the gate
29:47 Let's build it and run it, with the world server's log along the bottom. I'm logged in, and the gate is right ahead of where we start, a ring of grey stones around a slab in the Ashlands' red. I walk into it, and I stop in the middle of the gate, walking on the spot. And the server's log fills up with me going to the Ashlands and back to the Meadows, a line each way on each tick. A player who arrives in the middle of a gate is inside that gate, so the next land's turn in travel sends them straight back. Travel goes through the Meadows and then the Ashlands, so it all happens within a tick, and the client stays in the Meadows the whole time, while I can't walk out of the gate. The middle of the other gate is the obvious place to put a player, and it's easy to forget that the gate they arrive at sends players on like any other.
30:35 In travel, a player comes out at the other land's gate, in its middle, which is inside that gate. So in lands dot h, k arrival is how far from the middle of a gate a player comes out: three metres, past the gate's edge at one metre. Its comment says why: so that players don't arrive inside it. Arrival works out where a player who goes through a gate comes out, at the other land's gate, from the way they're facing. Its comment says that they come out past the gate's middle, the way they were facing, so that they walk on out of it. In lands dot c c, arrival takes the gate's middle, and goes three metres along the player's yaw, with sin for x and cos for z, which is how our yaw works, as zero faces along z. And in travel, where the player's x and z came from the other land's gate, arrival works them out from the other land and the player's yaw. The player keeps the yaw from their last step, and comes out there, instead of in the middle of the gate.
31:35 Let's run it again, with the arrival in, and the log along the bottom. I walk into the gate, and come out past the Ashlands' gate, still walking north, while the log says that I went to the Ashlands. The client builds the Ashlands from their seed, in their look, which takes a moment, and then there's ash where the grass would be, with charred trees, under the red haze. I turn around, and the slab in the gate is blue, the colour of the Meadows' sky, and walking back through it takes me home, out past the Meadows' gate, with a second line in the log.
32:09 Now that we've seen it work, a test to pin it down, in the terrain tests, which include our lands header. Has room at each gate goes through the first three worlds' seeds, Ember's, Frost's and Gale's, and through each land of each world. On each island, which it makes with its land's wading, the gate has to stand on ground that a player can walk on, or nobody could reach it. Then for a yaw each half a radian, all the way around, and the comment says why: whichever way a player comes through, they land on the island, out of the gate. So the arrival has to be walkable, and outside the gate, which is the mistake we just made.
32:46 Compose builds the tests, and runs the terrain tests on their own, without the databases, as they need none, and the four of them pass.
Keeping the land
32:55 So far, the server keeps where a player stands, and not which land they stand in. Let's see what that means: I go through the gate to the Ashlands, and the character screen says that I'm in the Ashlands, a few metres past their gate. I log out from the game menu, and log back in, and I'm in the Meadows, at the spot where I stood in the Ashlands. The server saved my position, and loaded it into the land that everyone joins, so a player saved in the Ashlands comes back in the Meadows, and on another island, the spot could even be in the sea.
33:26 So a migration adds the land to the players table, by its name, as a name still means the right land when we add lands or change their order, while an index into the list would point at another land. Its default is the Meadows, which is where all our players are, as it's the land everyone has joined so far. In Redis, each player's hash gets the land too, so that the saver can write it to MySQL, and the server puts a player who joins in the land they left.
33:54 The migration is V four, players land, and it adds the land column, of up to sixteen characters, like the world's, which can't be null, with the Meadows as its default. That default fills in the rows we have, and it keeps the servers from before working while a deploy migrates, as they save their players without a land, and those players go in as the Meadows, where they were. Algorithm instant makes MySQL add the column by changing the table's definition, without rewriting the table, or refuse, like the health column in episode eighteen.
34:25 The database keeps a land by its name, while the server works with its index, so lands dot h gets string view, and land named, which returns a land's place in the list from its name. Its comment says what it does with a name that isn't there: it returns the first land's, so that a player from a land we've taken out starts in the Meadows. It takes a string view, which looks at characters without copying them, as it reads the name and keeps nothing. In lands dot c c, c s t d def, for size t, and string view. Land named goes through the list by index, compares each land's name with the one it's given, and returns the index of the land that has it, or zero, the Meadows, when none does. In world dot h, the player gets land, with a comment: which of the world's lands, by its place in k lands. A World keeps the players of a single land, so it has no use for this field, and the server keeps it up to date, so that saving a player says where they are. In players dot h, a player's progress, which is what we save, gets the land, so that a trip to the other land counts as a change, and gets saved. In players dot c c, our lands header, for the names of the lands. Load selects the land, with the Meadows for an account that has no row in this world yet, and reads the land from the player's hash in Redis too, as the hash is newer. Land named turns the name into the land's index, and a hash saved before this episode has no land, which land named reads as the Meadows. Save writes the player's hash with H set, one command for each player who changed. Now that the land goes on the end, clang format lays the whole command out again, as it no longer fits the way it was. The land goes in by its name, from the list, next to the rest of the progress. Flush reads the land from each hash, the ones with worlds and the ones from before worlds, and the insert gets a land column, after the health, with a value from each hash. Null if turns an empty land into null, and coalesce turns that into the Meadows. The comment over the values says why: a hash with no land was saved before lands, so it's in the Meadows. And a row that's there already gets its land updated, next to the rest. In server dot c c, progress of keeps the land, for the dead too, as they come back in the middle of the land they died in. A player who joins goes into the land they were saved in, and travel sets the land that a player goes to, so that the next save writes it.
36:48 In the players tests, loads where the player was saved in that world saves a player in the Ashlands, land one, and checks that load brings them back there. Flushes the players to MySQL saves the player in Frost in the Ashlands too, and its row has to say Ashlands. The player from before worlds, whose hash has no land, goes in as the Meadows, so each of the rows we expect gets its land.
37:14 Let's see the migration on a database from before this episode, with a player in it, which I set up off camera. Starting the world runs Flyway first, and its log says that it migrated the schema to version four, players land. The player who was there before is in the Meadows, which is where they were, with their position as it was.
37:36 Then the players tests, which compose builds and runs next to MySQL, Redis and Consul, after Flyway's migrations, and the three of them pass.
37:53 And let's go through the gate to the Ashlands again, with the migration in, log out, and log back in. This time, I'm in the Ashlands, at the spot where I left, as the character screen says.
Lands in the metrics
38:07 Our dashboard counts the players of each world, and with two lands, we'd like to see how a world's players spread over them. In metrics dot h, prometheus-cpp's family header comes in, and set players takes the land's name now, before the count. Players becomes a family of gauges, which is a metric with a label, where each value of the label gets a gauge of its own. In metrics dot c c, the help text says that it's players by land, and register returns the family, without adding a gauge with no labels, as each land adds its own. Set players takes the land too, and adds the land's gauge, with the land as its label, and sets it to the count. The comment says that add returns the land's gauge if it has one already, so calling it each time is fine, and costs a lookup in a map. In server dot c c, joining and leaving don't set the players any more, and instead, each tick sets the players of each land, after travel, by the land's name. A trip changes the counts of two lands without anyone joining or leaving, and two gauges a tick cost next to nothing. In the dashboard, the players panel names each line by its world and its land. And load dot s h, which reads Ember's players, adds up its lands with sum, as the query returns a value for each land now.
39:23 Our bots don't know about the gate, and walk into it by chance, now and then, so the Ashlands would stay almost empty under load. Like players, the bots are going to go there on purpose, on some of their trips. In wander dot h, our lands header, for the gate's place. A trip gets there, how close to its end counts as there, in metres, with three as before. Random trip takes the gate of the bot's land now, after the random numbers. Its comment says that the trip is a place within a hundred and seventy metres of the middle, as before. Or on one trip in five, it's the middle of the gate, which takes the bot to the other land, as players go there too. In wander dot c c, k there goes, as each trip has its own, and k through the gate is a fifth, the share of trips that go into the gate. In random trip, on one trip in five, the trip goes to the gate's middle, and its there is half the gate's radius. The comment says why: all the way to its middle, as three metres short, the bot would stop outside. And travel stops at the trip's own there, instead of at three metres. In bot dot h, the bot keeps the land it's in, by its index, and in bot dot c c, with our lands header, receive takes the land from each world it gets. When the land changes, the bot has been through a gate, so its trip, which was for the land it left, goes, as the comment says. Without that, a bot would turn around and walk back into the gate, to finish its trip. The check against the list's size keeps a land we don't know out of the index. And a new trip gets the gate of the bot's land. The bots' library links terrain now, for the lands and their gates. In the wander tests, our lands header, and a new test, goes into the gate on a trip in five. It starts the random numbers from a seed of one, so that each run of the test makes the trips it made before, and puts a gate ten metres north of the middle. It makes a thousand trips, and counts the ones that end at the gate's middle. Each of those has to stop inside the gate, under its radius, as a trip that stopped three metres short wouldn't go through. With a fifth of the trips, about two hundred go to the gate, so the count has to be between a hundred and fifty and two hundred and fifty, which leaves room for chance.
41:37 Let's start the world with Prometheus, Grafana and a hundred bots, which go to random places on the island, and into the gate on one trip in five. After a couple of minutes, the players panel shows Ember's lands apart, with most of the bots in the Meadows, where they start. A share of them are in the Ashlands, where a bot stays until one of its trips takes it back through the gate.
Docs
41:58 Last, the README, where the section on the island says that each land's island comes from the world's seed plus the land's, and what a land's look and atmosphere are, with the Meadows' values. It says that the fog is worked out for each pixel, and how to change the atmosphere in the settings window. A new section on lands has a table of the two lands, their seeds and their gates, and it says how deep players can wade into each land's sea, how a gate works and where players come out. It says that the client builds a land when its seed changes, which takes a moment. And it says that the players table and Redis keep the land by its name, and that the metric of players online and the bots go by land. It ends on why the lands run in one process, and what a server for each land would need. The table of a world's settings says that the seed is for the lands' islands, saving players lists the land in the hash, and monitoring says that the players online are counted in each land.
Wrap-up
42:54 So, today the look of a land became data: the colours of its meshes, and an atmosphere for the shaders, which we can change as we play. The Meadows got a lower, warmer sun, once the fog was worked out for each pixel. Each world runs two lands in its world server, each a World of its own, with a gate between them that puts you out past the other gate, after a first try that sent us straight back. The players table keeps the land you're in, and our metrics and our bots know about lands, so that the dashboard shows how the players spread over them.
Playing it
44:14 Let me know in the comments what you'd like to see next. And if you want to follow along with the series, subscribe to the channel. I hope you find this useful, and thanks for watching.