]> git.lizzy.rs Git - dragonfireclient.git/blob - src/dungeongen.cpp
Implement urlencode and urldecode
[dragonfireclient.git] / src / dungeongen.cpp
1 /*
2 Minetest
3 Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU Lesser General Public License as published by
7 the Free Software Foundation; either version 2.1 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 GNU Lesser General Public License for more details.
14
15 You should have received a copy of the GNU Lesser General Public License along
16 with this program; if not, write to the Free Software Foundation, Inc.,
17 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19
20 #include "dungeongen.h"
21 #include "mapgen.h"
22 #include "voxel.h"
23 #include "noise.h"
24 #include "mapblock.h"
25 #include "mapnode.h"
26 #include "map.h"
27 #include "nodedef.h"
28 #include "profiler.h"
29 #include "settings.h" // For g_settings
30 #include "main.h" // For g_profiler
31
32 //#define DGEN_USE_TORCHES
33
34 NoiseParams nparams_dungeon_rarity = 
35         {0.0, 1.0, v3f(500.0, 500.0, 500.0), 0, 2, 0.8};
36 NoiseParams nparams_dungeon_wetness =
37         {0.0, 1.0, v3f(40.0, 40.0, 40.0), 32474, 4, 1.1};
38 NoiseParams nparams_dungeon_density =
39         {0.0, 1.0, v3f(2.5, 2.5, 2.5), 0, 2, 1.4};
40
41
42 ///////////////////////////////////////////////////////////////////////////////
43
44
45 DungeonGen::DungeonGen(INodeDefManager *ndef, u64 seed, s16 waterlevel,
46                                                 DungeonParams *dparams) {
47         this->ndef        = ndef;
48         this->mapseed     = seed;
49         this->water_level = waterlevel;
50
51 #ifdef DGEN_USE_TORCHES
52         c_torch  = ndef->getId("default:torch");
53 #endif
54         
55         if (dparams) {
56                 memcpy(&dp, dparams, sizeof(dp));
57         } else {
58                 dp.c_water  = ndef->getId("mapgen_water_source");
59                 dp.c_cobble = ndef->getId("mapgen_cobble");
60                 dp.c_moss   = ndef->getId("mapgen_mossycobble");
61                 dp.c_stair  = ndef->getId("mapgen_stair_cobble");
62
63                 dp.diagonal_dirs = false;
64                 dp.mossratio = 3.0;
65                 dp.holesize  = v3s16(1, 2, 1);
66                 dp.roomsize  = v3s16(0,0,0);
67
68                 dp.np_rarity  = nparams_dungeon_rarity;
69                 dp.np_wetness = nparams_dungeon_wetness;
70                 dp.np_density = nparams_dungeon_density;
71         }
72 }
73
74
75 void DungeonGen::generate(ManualMapVoxelManipulator *vm, u32 bseed,
76                                                 v3s16 nmin, v3s16 nmax) {
77         //TimeTaker t("gen dungeons");
78         int approx_groundlevel = 10 + water_level;
79
80         if ((nmin.Y + nmax.Y) / 2 >= approx_groundlevel ||
81                 NoisePerlin3D(&dp.np_rarity, nmin.X, nmin.Y, nmin.Z, mapseed) < 0.2)
82                 return;
83                 
84         this->vmanip    = vm;
85         this->blockseed = bseed;
86         random.seed(bseed + 2);
87
88         // Dungeon generator doesn't modify places which have this set
89         vmanip->clearFlag(VMANIP_FLAG_DUNGEON_INSIDE | VMANIP_FLAG_DUNGEON_PRESERVE);
90
91         // Set all air and water to be untouchable to make dungeons open
92         // to caves and open air
93         for (s16 z = nmin.Z; z <= nmax.Z; z++) {
94                 for (s16 y = nmin.Y; y <= nmax.Y; y++) {
95                         u32 i = vmanip->m_area.index(nmin.X, y, z);
96                         for (s16 x = nmin.X; x <= nmax.X; x++) {
97                                 content_t c = vmanip->m_data[i].getContent();
98                                 if (c == CONTENT_AIR || c == dp.c_water)
99                                         vmanip->m_flags[i] |= VMANIP_FLAG_DUNGEON_PRESERVE;
100                                 i++;
101                         }
102                 }
103         }
104         
105         // Add it
106         makeDungeon(v3s16(1,1,1) * MAP_BLOCKSIZE);
107
108         // Convert some cobble to mossy cobble
109         if (dp.mossratio != 0.0) {
110                 for (s16 z = nmin.Z; z <= nmax.Z; z++)
111                 for (s16 y = nmin.Y; y <= nmax.Y; y++) {
112                         u32 i = vmanip->m_area.index(nmin.X, y, z);
113                         for (s16 x = nmin.X; x <= nmax.X; x++) {
114                                 if (vmanip->m_data[i].getContent() == dp.c_cobble) {
115                                         float wetness = NoisePerlin3D(&dp.np_wetness, x, y, z, mapseed);
116                                         float density = NoisePerlin3D(&dp.np_density, x, y, z, blockseed);
117                                         if (density < wetness / dp.mossratio)
118                                                 vmanip->m_data[i].setContent(dp.c_moss);
119                                 }
120                                 i++;
121                         }
122                 }
123         }
124         
125         //printf("== gen dungeons: %dms\n", t.stop());
126 }
127
128
129 void DungeonGen::makeDungeon(v3s16 start_padding)
130 {
131         v3s16 areasize = vmanip->m_area.getExtent();
132         v3s16 roomsize;
133         v3s16 roomplace;
134
135         /*
136                 Find place for first room
137         */
138         bool fits = false;
139         for (u32 i = 0; i < 100; i++)
140         {
141                 bool is_large_room = ((random.next() & 3) == 1);
142                 roomsize = is_large_room ?
143                         v3s16(random.range(8, 16),random.range(8, 16),random.range(8, 16)) :
144                         v3s16(random.range(4,  8),random.range(4,  6),random.range(4, 8));
145                 roomsize += dp.roomsize;
146
147                 // start_padding is used to disallow starting the generation of
148                 // a dungeon in a neighboring generation chunk
149                 roomplace = vmanip->m_area.MinEdge + start_padding + v3s16(
150                         random.range(0,areasize.X-roomsize.X-1-start_padding.X),
151                         random.range(0,areasize.Y-roomsize.Y-1-start_padding.Y),
152                         random.range(0,areasize.Z-roomsize.Z-1-start_padding.Z));
153                         
154                 /*
155                         Check that we're not putting the room to an unknown place,
156                         otherwise it might end up floating in the air
157                 */
158                 fits = true;
159                 for (s16 z = 1; z < roomsize.Z - 1; z++)
160                 for (s16 y = 1; y < roomsize.Y - 1; y++)
161                 for (s16 x = 1; x < roomsize.X - 1; x++)
162                 {
163                         v3s16 p = roomplace + v3s16(x, y, z);
164                         u32 vi = vmanip->m_area.index(p);
165                         if (vmanip->m_flags[vi] & VMANIP_FLAG_DUNGEON_INSIDE)
166                         {
167                                 fits = false;
168                                 break;
169                         }
170                         if (vmanip->m_data[vi].getContent() == CONTENT_IGNORE)
171                         {
172                                 fits = false;
173                                 break;
174                         }
175                 }
176                 if (fits)
177                         break;
178         }
179         // No place found
180         if (fits == false)
181                 return;
182
183         /*
184                 Stores the center position of the last room made, so that
185                 a new corridor can be started from the last room instead of
186                 the new room, if chosen so.
187         */
188         v3s16 last_room_center = roomplace + v3s16(roomsize.X / 2, 1, roomsize.Z / 2);
189
190         u32 room_count = random.range(2, 16);
191         for (u32 i = 0; i < room_count; i++)
192         {
193                 // Make a room to the determined place
194                 makeRoom(roomsize, roomplace);
195
196                 v3s16 room_center = roomplace + v3s16(roomsize.X / 2, 1, roomsize.Z / 2);
197
198 #ifdef DGEN_USE_TORCHES
199                 // Place torch at room center (for testing)
200                 vmanip->m_data[vmanip->m_area.index(room_center)] = MapNode(c_torch);
201 #endif
202
203                 // Quit if last room
204                 if (i == room_count - 1)
205                         break;
206
207                 // Determine walker start position
208
209                 bool start_in_last_room = (random.range(0, 2) != 0);
210
211                 v3s16 walker_start_place;
212
213                 if(start_in_last_room)
214                 {
215                         walker_start_place = last_room_center;
216                 }
217                 else
218                 {
219                         walker_start_place = room_center;
220                         // Store center of current room as the last one
221                         last_room_center = room_center;
222                 }
223
224                 // Create walker and find a place for a door
225                 v3s16 doorplace;
226                 v3s16 doordir;
227                 
228                 m_pos = walker_start_place;
229                 bool r = findPlaceForDoor(doorplace, doordir);
230                 if (r == false)
231                         return;
232
233                 if (random.range(0,1) == 0)
234                         // Make the door
235                         makeDoor(doorplace, doordir);
236                 else
237                         // Don't actually make a door
238                         doorplace -= doordir;
239
240                 // Make a random corridor starting from the door
241                 v3s16 corridor_end;
242                 v3s16 corridor_end_dir;
243                 makeCorridor(doorplace, doordir, corridor_end, corridor_end_dir);
244
245                 // Find a place for a random sized room
246                 roomsize = v3s16(random.range(4,8),random.range(4,6),random.range(4,8));
247                 roomsize += dp.roomsize;
248
249                 m_pos = corridor_end;
250                 m_dir = corridor_end_dir;
251                 r = findPlaceForRoomDoor(roomsize, doorplace, doordir, roomplace);
252                 if (r == false)
253                         return;
254
255                 if (random.range(0,1) == 0)
256                         // Make the door
257                         makeDoor(doorplace, doordir);
258                 else
259                         // Don't actually make a door
260                         roomplace -= doordir;
261
262         }
263 }
264
265
266 void DungeonGen::makeRoom(v3s16 roomsize, v3s16 roomplace)
267 {
268         MapNode n_cobble(dp.c_cobble);
269         MapNode n_air(CONTENT_AIR);
270         
271         // Make +-X walls
272         for (s16 z = 0; z < roomsize.Z; z++)
273         for (s16 y = 0; y < roomsize.Y; y++)
274         {
275                 {
276                         v3s16 p = roomplace + v3s16(0, y, z);
277                         if (vmanip->m_area.contains(p) == false)
278                                 continue;
279                         u32 vi = vmanip->m_area.index(p);
280                         if (vmanip->m_flags[vi] & VMANIP_FLAG_DUNGEON_UNTOUCHABLE)
281                                 continue;
282                         vmanip->m_data[vi] = n_cobble;
283                 }
284                 {
285                         v3s16 p = roomplace + v3s16(roomsize.X - 1, y, z);
286                         if (vmanip->m_area.contains(p) == false)
287                                 continue;
288                         u32 vi = vmanip->m_area.index(p);
289                         if (vmanip->m_flags[vi] & VMANIP_FLAG_DUNGEON_UNTOUCHABLE)
290                                 continue;
291                         vmanip->m_data[vi] = n_cobble;
292                 }
293         }
294
295         // Make +-Z walls
296         for (s16 x = 0; x < roomsize.X; x++)
297         for (s16 y = 0; y < roomsize.Y; y++)
298         {
299                 {
300                         v3s16 p = roomplace + v3s16(x, y, 0);
301                         if (vmanip->m_area.contains(p) == false)
302                                 continue;
303                         u32 vi = vmanip->m_area.index(p);
304                         if (vmanip->m_flags[vi] & VMANIP_FLAG_DUNGEON_UNTOUCHABLE)
305                                 continue;
306                         vmanip->m_data[vi] = n_cobble;
307                 }
308                 {
309                         v3s16 p = roomplace + v3s16(x, y, roomsize.Z - 1);
310                         if (vmanip->m_area.contains(p) == false)
311                                 continue;
312                         u32 vi = vmanip->m_area.index(p);
313                         if (vmanip->m_flags[vi] & VMANIP_FLAG_DUNGEON_UNTOUCHABLE)
314                                 continue;
315                         vmanip->m_data[vi] = n_cobble;
316                 }
317         }
318
319         // Make +-Y walls (floor and ceiling)
320         for (s16 z = 0; z < roomsize.Z; z++)
321         for (s16 x = 0; x < roomsize.X; x++)
322         {
323                 {
324                         v3s16 p = roomplace + v3s16(x, 0, z);
325                         if (vmanip->m_area.contains(p) == false)
326                                 continue;
327                         u32 vi = vmanip->m_area.index(p);
328                         if (vmanip->m_flags[vi] & VMANIP_FLAG_DUNGEON_UNTOUCHABLE)
329                                 continue;
330                         vmanip->m_data[vi] = n_cobble;
331                 }
332                 {
333                         v3s16 p = roomplace + v3s16(x,roomsize. Y - 1, z);
334                         if (vmanip->m_area.contains(p) == false)
335                                 continue;
336                         u32 vi = vmanip->m_area.index(p);
337                         if (vmanip->m_flags[vi] & VMANIP_FLAG_DUNGEON_UNTOUCHABLE)
338                                 continue;
339                         vmanip->m_data[vi] = n_cobble;
340                 }
341         }
342
343         // Fill with air
344         for (s16 z = 1; z < roomsize.Z - 1; z++)
345         for (s16 y = 1; y < roomsize.Y - 1; y++)
346         for (s16 x = 1; x < roomsize.X - 1; x++)
347         {
348                 v3s16 p = roomplace + v3s16(x, y, z);
349                 if (vmanip->m_area.contains(p) == false)
350                         continue;
351                 u32 vi = vmanip->m_area.index(p);
352                 vmanip->m_flags[vi] |= VMANIP_FLAG_DUNGEON_UNTOUCHABLE;
353                 vmanip->m_data[vi]   = n_air;
354         }
355 }
356
357
358 void DungeonGen::makeFill(v3s16 place, v3s16 size,
359                 u8 avoid_flags, MapNode n, u8 or_flags)
360 {
361         for (s16 z = 0; z < size.Z; z++)
362         for (s16 y = 0; y < size.Y; y++)
363         for (s16 x = 0; x < size.X; x++)
364         {
365                 v3s16 p = place + v3s16(x, y, z);
366                 if (vmanip->m_area.contains(p) == false)
367                         continue;
368                 u32 vi = vmanip->m_area.index(p);
369                 if (vmanip->m_flags[vi] & avoid_flags)
370                         continue;
371                 vmanip->m_flags[vi] |= or_flags;
372                 vmanip->m_data[vi]   = n;
373         }
374 }
375
376
377 void DungeonGen::makeHole(v3s16 place)
378 {
379         makeFill(place, dp.holesize, 0,
380                 MapNode(CONTENT_AIR), VMANIP_FLAG_DUNGEON_INSIDE);
381 }
382
383
384 void DungeonGen::makeDoor(v3s16 doorplace, v3s16 doordir)
385 {
386         makeHole(doorplace);
387
388 #ifdef DGEN_USE_TORCHES
389         // Place torch (for testing)
390         vmanip->m_data[vmanip->m_area.index(doorplace)] = MapNode(c_torch);
391 #endif
392 }
393
394
395 void DungeonGen::makeCorridor(v3s16 doorplace,
396                 v3s16 doordir, v3s16 &result_place, v3s16 &result_dir)
397 {
398         makeHole(doorplace);
399         v3s16 p0 = doorplace;
400         v3s16 dir = doordir;
401         u32 length;
402         /*if (random.next() % 2)
403                 length = random.range(1, 13);
404         else
405                 length = random.range(1, 6);*/
406         length = random.range(1, 13);
407         u32 partlength = random.range(1, 13);
408         u32 partcount = 0;
409         s16 make_stairs = 0;
410         
411         if (random.next() % 2 == 0 && partlength >= 3)
412                 make_stairs = random.next() % 2 ? 1 : -1;
413         
414         for (u32 i = 0; i < length; i++) {
415                 v3s16 p = p0 + dir;
416                 if (partcount != 0)
417                         p.Y += make_stairs;
418
419                 if (vmanip->m_area.contains(p) == true &&
420                         vmanip->m_area.contains(p + v3s16(0, 1, 0)) == true) {
421                         if (make_stairs) {
422                                 makeFill(p + v3s16(-1, -1, -1), dp.holesize + v3s16(2, 3, 2),
423                                                 VMANIP_FLAG_DUNGEON_UNTOUCHABLE, MapNode(dp.c_cobble), 0);
424                                 makeHole(p);
425                                 makeHole(p - dir);
426                                 
427                                 // TODO: fix stairs code so it works 100% (quite difficult)
428
429                                 // exclude stairs from the bottom step
430                                 // exclude stairs from diagonal steps
431                                 if (((dir.X ^ dir.Z) & 1) &&
432                                         (((make_stairs ==  1) && i != 0) ||
433                                         ((make_stairs == -1) && i != length - 1))) {
434                                         // rotate face 180 deg if making stairs backwards
435                                         int facedir = dir_to_facedir(dir * make_stairs);
436                                         
437                                         u32 vi = vmanip->m_area.index(p.X - dir.X, p.Y - 1, p.Z - dir.Z);
438                                         if (vmanip->m_data[vi].getContent() == dp.c_cobble)
439                                                 vmanip->m_data[vi] = MapNode(dp.c_stair, 0, facedir);
440                                         
441                                         vi = vmanip->m_area.index(p.X, p.Y, p.Z);
442                                         if (vmanip->m_data[vi].getContent() == dp.c_cobble)
443                                                 vmanip->m_data[vi] = MapNode(dp.c_stair, 0, facedir);
444                                 }
445                         } else {
446                                 makeFill(p + v3s16(-1, -1, -1), dp.holesize + v3s16(2, 2, 2),
447                                                 VMANIP_FLAG_DUNGEON_UNTOUCHABLE, MapNode(dp.c_cobble), 0);
448                                 makeHole(p);
449                         }
450
451                         p0 = p;
452                 } else {
453                         // Can't go here, turn away
454                         dir = turn_xz(dir, random.range(0, 1));
455                         make_stairs = -make_stairs;
456                         partcount = 0;
457                         partlength = random.range(1, length);
458                         continue;
459                 }
460
461                 partcount++;
462                 if (partcount >= partlength) {
463                         partcount = 0;
464
465                         dir = random_turn(random, dir);
466
467                         partlength = random.range(1,length);
468
469                         make_stairs = 0;
470                         if (random.next() % 2 == 0 && partlength >= 3)
471                                 make_stairs = random.next() % 2 ? 1 : -1;
472                 }
473         }
474         result_place = p0;
475         result_dir = dir;
476 }
477
478
479 bool DungeonGen::findPlaceForDoor(v3s16 &result_place, v3s16 &result_dir)
480 {
481         for (u32 i = 0; i < 100; i++)
482         {
483                 v3s16 p = m_pos + m_dir;
484                 v3s16 p1 = p + v3s16(0, 1, 0);
485                 if (vmanip->m_area.contains(p) == false
486                  || vmanip->m_area.contains(p1) == false
487                  || i % 4 == 0)
488                 {
489                         randomizeDir();
490                         continue;
491                 }
492                 if (vmanip->getNodeNoExNoEmerge(p).getContent()  == dp.c_cobble
493                  && vmanip->getNodeNoExNoEmerge(p1).getContent() == dp.c_cobble)
494                 {
495                         // Found wall, this is a good place!
496                         result_place = p;
497                         result_dir = m_dir;
498                         // Randomize next direction
499                         randomizeDir();
500                         return true;
501                 }
502                 /*
503                         Determine where to move next
504                 */
505                 // Jump one up if the actual space is there
506                 if (vmanip->getNodeNoExNoEmerge(p+v3s16(0,0,0)).getContent() == dp.c_cobble
507                  && vmanip->getNodeNoExNoEmerge(p+v3s16(0,1,0)).getContent() == CONTENT_AIR
508                  && vmanip->getNodeNoExNoEmerge(p+v3s16(0,2,0)).getContent() == CONTENT_AIR)
509                         p += v3s16(0,1,0);
510                 // Jump one down if the actual space is there
511                 if (vmanip->getNodeNoExNoEmerge(p+v3s16(0,1,0)).getContent() == dp.c_cobble
512                  && vmanip->getNodeNoExNoEmerge(p+v3s16(0,0,0)).getContent() == CONTENT_AIR
513                  && vmanip->getNodeNoExNoEmerge(p+v3s16(0,-1,0)).getContent() == CONTENT_AIR)
514                         p += v3s16(0,-1,0);
515                 // Check if walking is now possible
516                 if (vmanip->getNodeNoExNoEmerge(p).getContent() != CONTENT_AIR
517                  || vmanip->getNodeNoExNoEmerge(p+v3s16(0,1,0)).getContent() != CONTENT_AIR)
518                 {
519                         // Cannot continue walking here
520                         randomizeDir();
521                         continue;
522                 }
523                 // Move there
524                 m_pos = p;
525         }
526         return false;
527 }
528
529
530 bool DungeonGen::findPlaceForRoomDoor(v3s16 roomsize, v3s16 &result_doorplace,
531                 v3s16 &result_doordir, v3s16 &result_roomplace)
532 {
533         for (s16 trycount = 0; trycount < 30; trycount++)
534         {
535                 v3s16 doorplace;
536                 v3s16 doordir;
537                 bool r = findPlaceForDoor(doorplace, doordir);
538                 if (r == false)
539                         continue;
540                 v3s16 roomplace;
541                 // X east, Z north, Y up
542 #if 1
543                 if (doordir == v3s16(1, 0, 0)) // X+
544                         roomplace = doorplace +
545                                         v3s16(0, -1, random.range(-roomsize.Z + 2, -2));
546                 if (doordir == v3s16(-1, 0, 0)) // X-
547                         roomplace = doorplace +
548                                         v3s16(-roomsize.X + 1, -1, random.range(-roomsize.Z + 2, -2));
549                 if (doordir == v3s16(0, 0, 1)) // Z+
550                         roomplace = doorplace +
551                                         v3s16(random.range(-roomsize.X + 2, -2), -1, 0);
552                 if (doordir == v3s16(0, 0, -1)) // Z-
553                         roomplace = doorplace +
554                                         v3s16(random.range(-roomsize.X + 2, -2), -1, -roomsize.Z + 1);
555 #endif
556 #if 0
557                 if (doordir == v3s16(1, 0, 0)) // X+
558                         roomplace = doorplace + v3s16(0, -1, -roomsize.Z / 2);
559                 if (doordir == v3s16(-1, 0, 0)) // X-
560                         roomplace = doorplace + v3s16(-roomsize.X+1,-1,-roomsize.Z / 2);
561                 if (doordir == v3s16(0, 0, 1)) // Z+
562                         roomplace = doorplace + v3s16(-roomsize.X / 2, -1, 0);
563                 if (doordir == v3s16(0, 0, -1)) // Z-
564                         roomplace = doorplace + v3s16(-roomsize.X / 2, -1, -roomsize.Z + 1);
565 #endif
566
567                 // Check fit
568                 bool fits = true;
569                 for (s16 z = 1; z < roomsize.Z - 1; z++)
570                 for (s16 y = 1; y < roomsize.Y - 1; y++)
571                 for (s16 x = 1; x < roomsize.X - 1; x++)
572                 {
573                         v3s16 p = roomplace + v3s16(x, y, z);
574                         if (vmanip->m_area.contains(p) == false)
575                         {
576                                 fits = false;
577                                 break;
578                         }
579                         if (vmanip->m_flags[vmanip->m_area.index(p)]
580                                         & VMANIP_FLAG_DUNGEON_INSIDE)
581                         {
582                                 fits = false;
583                                 break;
584                         }
585                 }
586                 if(fits == false)
587                 {
588                         // Find new place
589                         continue;
590                 }
591                 result_doorplace = doorplace;
592                 result_doordir   = doordir;
593                 result_roomplace = roomplace;
594                 return true;
595         }
596         return false;
597 }
598
599
600 v3s16 rand_ortho_dir(PseudoRandom &random, bool diagonal_dirs)
601 {
602         // Make diagonal directions somewhat rare
603         if (diagonal_dirs && (random.next() % 4 == 0)) {
604                 v3s16 dir;
605                 int trycount = 0;
606
607                 do {
608                         trycount++;
609                         dir = v3s16(random.next() % 3 - 1, 0, random.next() % 3 - 1);
610                 } while ((dir.X == 0 && dir.Z == 0) && trycount < 10);
611
612                 return dir;
613         } else {
614                 if (random.next() % 2 == 0)
615                         return random.next() % 2 ? v3s16(-1, 0, 0) : v3s16(1, 0, 0);
616                 else
617                         return random.next() % 2 ? v3s16(0, 0, -1) : v3s16(0, 0, 1);
618         }
619 }
620
621
622 v3s16 turn_xz(v3s16 olddir, int t)
623 {
624         v3s16 dir;
625         if (t == 0)
626         {
627                 // Turn right
628                 dir.X = olddir.Z;
629                 dir.Z = -olddir.X;
630                 dir.Y = olddir.Y;
631         }
632         else
633         {
634                 // Turn left
635                 dir.X = -olddir.Z;
636                 dir.Z = olddir.X;
637                 dir.Y = olddir.Y;
638         }
639         return dir;
640 }
641
642
643 v3s16 random_turn(PseudoRandom &random, v3s16 olddir)
644 {
645         int turn = random.range(0, 2);
646         v3s16 dir;
647         if (turn == 0)
648         {
649                 // Go straight
650                 dir = olddir;
651         }
652         else if (turn == 1)
653                 // Turn right
654                 dir = turn_xz(olddir, 0);
655         else
656                 // Turn left
657                 dir = turn_xz(olddir, 1);
658         return dir;
659 }
660
661
662 int dir_to_facedir(v3s16 d) {
663         if (abs(d.X) > abs(d.Z))
664                 return d.X < 0 ? 3 : 1;
665         else
666                 return d.Z < 0 ? 2 : 0;
667 }