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