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