aboutsummaryrefslogtreecommitdiff
path: root/src/unittest/test_schematic.cpp
blob: d2f027eb4c85c0a5f8bb85fe571acccdc128464b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
 /*
Minetest
Copyright (C) 2010-2014 kwolekr, Ryan Kwolek <kwolekr@minetest.net>

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/

#include "test.h"

#include "mapgen/mg_schematic.h"
#include "gamedef.h"
#include "nodedef.h"

class TestSchematic : public TestBase {
public:
	TestSchematic() { TestManager::registerTestModule(this); }
	const char *getName() { return "TestSchematic"; }

	void runTests(IGameDef *gamedef);

	void testMtsSerializeDeserialize(const NodeDefManager *ndef);
	void testLuaTableSerialize(const NodeDefManager *ndef);
	void testFileSerializeDeserialize(const NodeDefManager *ndef);

	static const content_t test_schem1_data[7 * 6 * 4];
	static const content_t test_schem2_data[3 * 3 * 3];
	static const u8 test_schem2_prob[3 * 3 * 3];
	static const char *expected_lua_output;
};

static TestSchematic g_test_instance;

void TestSchematic::runTests(IGameDef *gamedef)
{
	NodeDefManager *ndef =
		(NodeDefManager *)gamedef->getNodeDefManager();

	ndef->setNodeRegistrationStatus(true);

	TEST(testMtsSerializeDeserialize, ndef);
	TEST(testLuaTableSerialize, ndef);
	TEST(testFileSerializeDeserialize, ndef);

	ndef->resetNodeResolveState();
}

////////////////////////////////////////////////////////////////////////////////

void TestSchematic::testMtsSerializeDeserialize(const NodeDefManager *ndef)
{
	static const v3s16 size(7, 6, 4);
	static const u32 volume = size.X * size.Y * size.Z;

	std::stringstream ss(std::ios_base::binary |
		std::ios_base::in | std::ios_base::out);

	Schematic schem;
	{
		std::vector<std::string> &names = schem.m_nodenames;
		names.emplace_back("foo");
		names.emplace_back("bar");
		names.emplace_back("baz");
		names.emplace_back("qux");
	}

	schem.flags       = 0;
	schem.size        = size;
	schem.schemdata   = new MapNode[volume];
	schem.slice_probs = new u8[size.Y];
	for (size_t i = 0; i != volume; i++)
		schem.schemdata[i] = MapNode(test_schem1_data[i], MTSCHEM_PROB_ALWAYS, 0);
	for (s16 y = 0; y != size.Y; y++)
		schem.slice_probs[y] = MTSCHEM_PROB_ALWAYS;

	UASSERT(schem.serializeToMts(&ss));

	ss.seekg(0);

	Schematic schem2;
	UASSERT(schem2.deserializeFromMts(&ss));

	{
		std::vector<std::string> &names = schem2.m_nodenames;
		UASSERTEQ(size_t, names.size(), 4);
		UASSERTEQ(std::string, names[0], "foo");
		UASSERTEQ(std::string, names[1], "bar");
		UASSERTEQ(std::string, names[2], "baz");
		UASSERTEQ(std::string, names[3], "qux");
	}

	UASSERT(schem2.size == size);
	for (size_t i = 0; i != volume; i++)
		UASSERT(schem2.schemdata[i] == schem.schemdata[i]);
	for (s16 y = 0; y != size.Y; y++)
		UASSERTEQ(u8, schem2.slice_probs[y], schem.slice_probs[y]);
}


void TestSchematic::testLuaTableSerialize(const NodeDefManager *ndef)
{
	static const v3s16 size(3, 3, 3);
	static const u32 volume = size.X * size.Y * size.Z;

	Schematic schem;

	schem.flags       = 0;
	schem.size        = size;
	schem.schemdata   = new MapNode[volume];
	schem.slice_probs = new u8[size.Y];
	for (size_t i = 0; i != volume; i++)
		schem.schemdata[i] = MapNode(test_schem2_data[i], test_schem2_prob[i], 0);
	for (s16 y = 0; y != size.Y; y++)
		schem.slice_probs[y] = MTSCHEM_PROB_ALWAYS;

	std::vector<std::string> &names = schem.m_nodenames;
	names.emplace_back("air");
	names.emplace_back("default:lava_source");
	names.emplace_back("default:glass");

	std::ostringstream ss(std::ios_base::binary);

	UASSERT(schem.serializeToLua(&ss, false, 0));
	UASSERTEQ(std::string, ss.str(), expected_lua_output);
}


void TestSchematic::testFileSerializeDeserialize(const NodeDefManager *ndef)
{
	static const v3s16 size(3, 3, 3);
	static const u32 volume = size.X * size.Y * size.Z;
	static const content_t content_map[] = {
		CONTENT_AIR,
		t_CONTENT_STONE,
		t_CONTENT_LAVA,
	};
	static const content_t content_map2[] = {
		CONTENT_AIR,
		t_CONTENT_STONE,
		t_CONTENT_WATER,
	};
	StringMap replace_names;
	replace_names["default:lava"] = "default:water";

	Schematic schem1, schem2;

	//// Construct the schematic to save
	schem1.flags          = 0;
	schem1.size           = size;
	schem1.schemdata      = new MapNode[volume];
	schem1.slice_probs    = new u8[size.Y];
	schem1.slice_probs[0] = 80;
	schem1.slice_probs[1] = 160;
	schem1.slice_probs[2] = 240;
	// Node resolving happened manually.
	schem1.m_resolve_done = true;

	for (size_t i = 0; i != volume; i++) {
		content_t c = content_map[test_schem2_data[i]];
		schem1.schemdata[i] = MapNode(c, test_schem2_prob[i], 0);
	}

	std::string temp_file = getTestTempFile();
	UASSERT(schem1.saveSchematicToFile(temp_file, ndef));
	UASSERT(schem2.loadSchematicFromFile(temp_file, ndef, &replace_names));

	UASSERT(schem2.size == size);
	UASSERT(schem2.slice_probs[0] == 80);
	UASSERT(schem2.slice_probs[1] == 160);
	UASSERT(schem2.slice_probs[2] == 240);

	for (size_t i = 0; i != volume; i++) {
		content_t c = content_map2[test_schem2_data[i]];
		UASSERT(schem2.schemdata[i] == MapNode(c, test_schem2_prob[i], 0));
	}
}


// Should form a cross-shaped-thing...?
const content_t TestSchematic::test_schem1_data[7 * 6 * 4] = {
	3, 3, 1, 1, 1, 3, 3, // Y=0, Z=0
	3, 0, 1, 2, 1, 0, 3, // Y=1, Z=0
	3, 0, 1, 2, 1, 0, 3, // Y=2, Z=0
	3, 1, 1, 2, 1, 1, 3, // Y=3, Z=0
	3, 2, 2, 2, 2, 2, 3, // Y=4, Z=0
	3, 1, 1, 2, 1, 1, 3, // Y=5, Z=0

	0, 0, 1, 1, 1, 0, 0, // Y=0, Z=1
	0, 0, 1, 2, 1, 0, 0, // Y=1, Z=1
	0, 0, 1, 2, 1, 0, 0, // Y=2, Z=1
	1, 1, 1, 2, 1, 1, 1, // Y=3, Z=1
	1, 2, 2, 2, 2, 2, 1, // Y=4, Z=1
	1, 1, 1, 2, 1, 1, 1, // Y=5, Z=1

	0, 0, 1, 1, 1, 0, 0, // Y=0, Z=2
	0, 0, 1, 2, 1, 0, 0, // Y=1, Z=2
	0, 0, 1, 2, 1, 0, 0, // Y=2, Z=2
	1, 1, 1, 2, 1, 1, 1, // Y=3, Z=2
	1, 2, 2, 2, 2, 2, 1, // Y=4, Z=2
	1, 1, 1, 2, 1, 1, 1, // Y=5, Z=2

	3, 3, 1, 1, 1, 3, 3, // Y=0, Z=3
	3, 0, 1, 2, 1, 0, 3, // Y=1, Z=3
	3, 0, 1, 2, 1, 0, 3, // Y=2, Z=3
	3, 1, 1, 2, 1, 1, 3, // Y=3, Z=3
	3, 2, 2, 2, 2, 2, 3, // Y=4, Z=3
	3, 1, 1, 2, 1, 1, 3, // Y=5, Z=3
};

const content_t TestSchematic::test_schem2_data[3 * 3 * 3] = {
	0, 0, 0,
	0, 2, 0,
	0, 0, 0,

	0, 2, 0,
	2, 1, 2,
	0, 2, 0,

	0, 0, 0,
	0, 2, 0,
	0, 0, 0,
};

const u8 TestSchematic::test_schem2_prob[3 * 3 * 3] = {
	0x00, 0x00, 0x00,
	0x00, 0xFF, 0x00,
	0x00, 0x00, 0x00,

	0x00, 0xFF, 0x00,
	0xFF, 0xFF, 0xFF,
	0x00, 0xFF, 0x00,

	0x00, 0x00, 0x00,
	0x00, 0xFF, 0x00,
	0x00, 0x00, 0x00,
};

const char *TestSchematic::expected_lua_output =
	"schematic = {\n"
	"\tsize = {x=3, y=3, z=3},\n"
	"\tyslice_prob = {\n"
	"\t\t{ypos=0, prob=254},\n"
	"\t\t{ypos=1, prob=254},\n"
	"\t\t{ypos=2, prob=254},\n"
	"\t},\n"
	"\tdata = {\n"
	"\t\t{name=\"air\", prob=0, param2=0},\n"
	"\t\t{name=\"air\", prob=0, param2=0},\n"
	"\t\t{name=\"air\", prob=0, param2=0},\n"
	"\t\t{name=\"air\", prob=0, param2=0},\n"
	"\t\t{name=\"default:glass\", prob=254, param2=0, force_place=true},\n"
	"\t\t{name=\"air\", prob=0, param2=0},\n"
	"\t\t{name=\"air\", prob=0, param2=0},\n"
	"\t\t{name=\"air\", prob=0, param2=0},\n"
	"\t\t{name=\"air\", prob=0, param2=0},\n"
	"\t\t{name=\"air\", prob=0, param2=0},\n"
	"\t\t{name=\"default:glass\", prob=254, param2=0, force_place=true},\n"
	"\t\t{name=\"air\", prob=0, param2=0},\n"
	"\t\t{name=\"default:glass\", prob=254, param2=0, force_place=true},\n"
	"\t\t{name=\"default:lava_source\", prob=254, param2=0, force_place=true},\n"
	"\t\t{name=\"default:glass\", prob=254, param2=0, force_place=true},\n"
	"\t\t{name=\"air\", prob=0, param2=0},\n"
	"\t\t{name=\"default:glass\", prob=254, param2=0, force_place=true},\n"
	"\t\t{name=\"air\", prob=0, param2=0},\n"
	"\t\t{name=\"air\", prob=0, param2=0},\n"
	"\t\t{name=\"air\", prob=0, param2=0},\n"
	"\t\t{name=\"air\", prob=0, param2=0},\n"
	"\t\t{name=\"air\", prob=0, param2=0},\n"
	"\t\t{name=\"default:glass\", prob=254, param2=0, force_place=true},\n"
	"\t\t{name=\"air\", prob=0, param2=0},\n"
	"\t\t{name=\"air\", prob=0, param2=0},\n"
	"\t\t{name=\"air\", prob=0, param2=0},\n"
	"\t\t{name=\"air\", prob=0, param2=0},\n"
	"\t},\n"
	"}\n";
class="hl kwc">MapgenV6::baseTerrainLevelFromMap(v2s16 p) { int index = (p.Y - node_min.Z) * ystride + (p.X - node_min.X); return baseTerrainLevelFromMap(index); } float MapgenV6::baseTerrainLevelFromMap(int index) { if (flags & MG_FLAT) return water_level; float terrain_base = noise_terrain_base->result[index]; float terrain_higher = noise_terrain_higher->result[index]; float steepness = noise_steepness->result[index]; float height_select = noise_height_select->result[index]; return baseTerrainLevel(terrain_base, terrain_higher, steepness, height_select); } s16 MapgenV6::find_ground_level_from_noise(u64 seed, v2s16 p2d, s16 precision) { return baseTerrainLevelFromNoise(p2d) + AVERAGE_MUD_AMOUNT; } int MapgenV6::getGroundLevelAtPoint(v2s16 p) { return baseTerrainLevelFromNoise(p) + AVERAGE_MUD_AMOUNT; } //////////////////////// Noise functions float MapgenV6::getMudAmount(v2s16 p) { int index = (p.Y - node_min.Z) * ystride + (p.X - node_min.X); return getMudAmount(index); } bool MapgenV6::getHaveBeach(v2s16 p) { int index = (p.Y - node_min.Z) * ystride + (p.X - node_min.X); return getHaveBeach(index); } BiomeType MapgenV6::getBiome(v2s16 p) { int index = (p.Y - node_min.Z) * ystride + (p.X - node_min.X); return getBiome(index, p); } float MapgenV6::getHumidity(v2s16 p) { /*double noise = noise2d_perlin( 0.5+(float)p.X/500, 0.5+(float)p.Y/500, seed+72384, 4, 0.66); noise = (noise + 1.0)/2.0;*/ float noise = NoisePerlin2D(np_humidity, p.X, p.Y, seed); if (noise < 0.0) noise = 0.0; if (noise > 1.0) noise = 1.0; return noise; } float MapgenV6::getTreeAmount(v2s16 p) { /*double noise = noise2d_perlin( 0.5+(float)p.X/125, 0.5+(float)p.Y/125, seed+2, 4, 0.66);*/ float noise = NoisePerlin2D(np_trees, p.X, p.Y, seed); float zeroval = -0.39; if (noise < zeroval) return 0; else return 0.04 * (noise-zeroval) / (1.0-zeroval); } bool MapgenV6::getHaveAppleTree(v2s16 p) { /*is_apple_tree = noise2d_perlin( 0.5+(float)p.X/100, 0.5+(float)p.Z/100, data->seed+342902, 3, 0.45) > 0.2;*/ float noise = NoisePerlin2D(np_apple_trees, p.X, p.Y, seed); return noise > 0.2; } float MapgenV6::getMudAmount(int index) { if (flags & MG_FLAT) return AVERAGE_MUD_AMOUNT; /*return ((float)AVERAGE_MUD_AMOUNT + 2.0 * noise2d_perlin( 0.5+(float)p.X/200, 0.5+(float)p.Y/200, seed+91013, 3, 0.55));*/ return noise_mud->result[index]; } bool MapgenV6::getHaveBeach(int index) { // Determine whether to have sand here /*double sandnoise = noise2d_perlin( 0.2+(float)p2d.X/250, 0.7+(float)p2d.Y/250, seed+59420, 3, 0.50);*/ float sandnoise = noise_beach->result[index]; return (sandnoise > freq_beach); } BiomeType MapgenV6::getBiome(int index, v2s16 p) { // Just do something very simple as for now /*double d = noise2d_perlin( 0.6+(float)p2d.X/250, 0.2+(float)p2d.Y/250, seed+9130, 3, 0.50);*/ float d = noise_biome->result[index]; if (d > freq_desert) return BT_DESERT; if ((flags & MGV6_BIOME_BLEND) && (d > freq_desert - 0.10) && ((noise2d(p.X, p.Y, seed) + 1.0) > (freq_desert - d) * 20.0)) return BT_DESERT; return BT_NORMAL; } u32 MapgenV6::get_blockseed(u64 seed, v3s16 p) { s32 x=p.X, y=p.Y, z=p.Z; return (u32)(seed%0x100000000ULL) + z*38134234 + y*42123 + x*23; } //////////////////////// Map generator void MapgenV6::makeChunk(BlockMakeData *data) { assert(data->vmanip); assert(data->nodedef); assert(data->blockpos_requested.X >= data->blockpos_min.X && data->blockpos_requested.Y >= data->blockpos_min.Y && data->blockpos_requested.Z >= data->blockpos_min.Z); assert(data->blockpos_requested.X <= data->blockpos_max.X && data->blockpos_requested.Y <= data->blockpos_max.Y && data->blockpos_requested.Z <= data->blockpos_max.Z); this->generating = true; this->vm = data->vmanip; this->ndef = data->nodedef; // Hack: use minimum block coords for old code that assumes a single block v3s16 blockpos = data->blockpos_requested; v3s16 blockpos_min = data->blockpos_min; v3s16 blockpos_max = data->blockpos_max; // Area of central chunk node_min = blockpos_min*MAP_BLOCKSIZE; node_max = (blockpos_max+v3s16(1,1,1))*MAP_BLOCKSIZE-v3s16(1,1,1); // Full allocated area full_node_min = (blockpos_min-1)*MAP_BLOCKSIZE; full_node_max = (blockpos_max+2)*MAP_BLOCKSIZE-v3s16(1,1,1); central_area_size = node_max - node_min + v3s16(1,1,1); assert(central_area_size.X == central_area_size.Z); int volume_blocks = (blockpos_max.X - blockpos_min.X + 1) * (blockpos_max.Y - blockpos_min.Y + 1) * (blockpos_max.Z - blockpos_max.Z + 1); volume_nodes = volume_blocks * MAP_BLOCKSIZE * MAP_BLOCKSIZE * MAP_BLOCKSIZE; // Create a block-specific seed blockseed = get_blockseed(data->seed, full_node_min); // Make some noise calculateNoise(); c_stone = ndef->getId("mapgen_stone"); c_dirt = ndef->getId("mapgen_dirt"); c_dirt_with_grass = ndef->getId("mapgen_dirt_with_grass"); c_sand = ndef->getId("mapgen_sand"); c_water_source = ndef->getId("mapgen_water_source"); c_lava_source = ndef->getId("mapgen_lava_source"); c_gravel = ndef->getId("mapgen_gravel"); c_cobble = ndef->getId("mapgen_cobble"); c_desert_sand = ndef->getId("mapgen_desert_sand"); c_desert_stone = ndef->getId("mapgen_desert_stone"); c_mossycobble = ndef->getId("mapgen_mossycobble"); c_sandbrick = ndef->getId("mapgen_sandstonebrick"); c_stair_cobble = ndef->getId("mapgen_stair_cobble"); c_stair_sandstone = ndef->getId("mapgen_stair_sandstone"); if (c_desert_sand == CONTENT_IGNORE) c_desert_sand = c_sand; if (c_desert_stone == CONTENT_IGNORE) c_desert_stone = c_stone; if (c_mossycobble == CONTENT_IGNORE) c_mossycobble = c_cobble; if (c_sandbrick == CONTENT_IGNORE) c_sandbrick = c_desert_stone; if (c_stair_cobble == CONTENT_IGNORE) c_stair_cobble = c_cobble; if (c_stair_sandstone == CONTENT_IGNORE) c_stair_sandstone = c_sandbrick; // Maximum height of the stone surface and obstacles. // This is used to guide the cave generation s16 stone_surface_max_y; // Generate general ground level to full area stone_surface_max_y = generateGround(); generateExperimental(); const s16 max_spread_amount = MAP_BLOCKSIZE; // Limit dirt flow area by 1 because mud is flown into neighbors. s16 mudflow_minpos = -max_spread_amount + 1; s16 mudflow_maxpos = central_area_size.X + max_spread_amount - 2; // Loop this part, it will make stuff look older and newer nicely const u32 age_loops = 2; for (u32 i_age = 0; i_age < age_loops; i_age++) { // Aging loop // Make caves (this code is relatively horrible) if (flags & MG_CAVES) generateCaves(stone_surface_max_y); // Add mud to the central chunk addMud(); // Add blobs of dirt and gravel underground addDirtGravelBlobs(); // Flow mud away from steep edges flowMud(mudflow_minpos, mudflow_maxpos); } // Add dungeons if (flags & MG_DUNGEONS) { DungeonParams dp; dp.np_rarity = nparams_dungeon_rarity; dp.np_density = nparams_dungeon_density; dp.np_wetness = nparams_dungeon_wetness; dp.c_water = c_water_source; if (getBiome(0, v2s16(node_min.X, node_min.Z)) == BT_NORMAL) { dp.c_cobble = c_cobble; dp.c_moss = c_mossycobble; dp.c_stair = c_stair_cobble; dp.diagonal_dirs = false; dp.mossratio = 3.0; dp.holesize = v3s16(1, 2, 1); dp.roomsize = v3s16(0, 0, 0); } else { dp.c_cobble = c_sandbrick; dp.c_moss = c_sandbrick; // should make this 'cracked sandstone' later dp.c_stair = c_stair_sandstone; dp.diagonal_dirs = true; dp.mossratio = 0.0; dp.holesize = v3s16(2, 3, 2); dp.roomsize = v3s16(2, 5, 2); } DungeonGen dgen(ndef, data->seed, water_level, &dp); dgen.generate(vm, blockseed, full_node_min, full_node_max); } // Add top and bottom side of water to transforming_liquid queue updateLiquid(&data->transforming_liquid, full_node_min, full_node_max); // Grow grass growGrass(); // Generate some trees, and add grass, if a jungle if (flags & MG_TREES) placeTreesAndJungleGrass(); // Generate the registered decorations for (unsigned int i = 0; i != emerge->decorations.size(); i++) { Decoration *deco = emerge->decorations[i]; deco->placeDeco(this, blockseed + i, node_min, node_max); } // Generate the registered ores for (unsigned int i = 0; i != emerge->ores.size(); i++) { Ore *ore = emerge->ores[i]; ore->placeOre(this, blockseed + i, node_min, node_max); } // Calculate lighting if (!(flags & MG_NOLIGHT)) calcLighting(node_min - v3s16(1, 1, 1) * MAP_BLOCKSIZE, node_max + v3s16(1, 0, 1) * MAP_BLOCKSIZE); this->generating = false; } void MapgenV6::calculateNoise() { int x = node_min.X; int z = node_min.Z; // Need to adjust for the original implementation's +.5 offset... if (!(flags & MG_FLAT)) { noise_terrain_base->perlinMap2D( x + 0.5 * noise_terrain_base->np->spread.X, z + 0.5 * noise_terrain_base->np->spread.Z); noise_terrain_base->transformNoiseMap(); noise_terrain_higher->perlinMap2D( x + 0.5 * noise_terrain_higher->np->spread.X, z + 0.5 * noise_terrain_higher->np->spread.Z); noise_terrain_higher->transformNoiseMap(); noise_steepness->perlinMap2D( x + 0.5 * noise_steepness->np->spread.X, z + 0.5 * noise_steepness->np->spread.Z); noise_steepness->transformNoiseMap(); noise_height_select->perlinMap2D( x + 0.5 * noise_height_select->np->spread.X, z + 0.5 * noise_height_select->np->spread.Z); noise_mud->perlinMap2D( x + 0.5 * noise_mud->np->spread.X, z + 0.5 * noise_mud->np->spread.Z); noise_mud->transformNoiseMap(); } noise_beach->perlinMap2D( x + 0.2 * noise_beach->np->spread.X, z + 0.7 * noise_beach->np->spread.Z); noise_biome->perlinMap2D( x + 0.6 * noise_biome->np->spread.X, z + 0.2 * noise_biome->np->spread.Z); } int MapgenV6::generateGround() { //TimeTaker timer1("Generating ground level"); MapNode n_air(CONTENT_AIR), n_water_source(c_water_source); MapNode n_stone(c_stone), n_desert_stone(c_desert_stone); int stone_surface_max_y = -MAP_GENERATION_LIMIT; u32 index = 0; for (s16 z = node_min.Z; z <= node_max.Z; z++) for (s16 x = node_min.X; x <= node_max.X; x++, index++) { // Surface height s16 surface_y = (s16)baseTerrainLevelFromMap(index); // Log it if (surface_y > stone_surface_max_y) stone_surface_max_y = surface_y; BiomeType bt = getBiome(index, v2s16(x, z)); // Fill ground with stone v3s16 em = vm->m_area.getExtent(); u32 i = vm->m_area.index(x, node_min.Y, z); for (s16 y = node_min.Y; y <= node_max.Y; y++) { if (vm->m_data[i].getContent() == CONTENT_IGNORE) { if (y <= surface_y) { vm->m_data[i] = (y > water_level && bt == BT_DESERT) ? n_desert_stone : n_stone; } else if (y <= water_level) { vm->m_data[i] = n_water_source; } else { vm->m_data[i] = n_air; } } vm->m_area.add_y(em, i, 1); } } return stone_surface_max_y; } void MapgenV6::addMud() { // 15ms @cs=8 //TimeTaker timer1("add mud"); MapNode n_dirt(c_dirt), n_gravel(c_gravel); MapNode n_sand(c_sand), n_desert_sand(c_desert_sand); MapNode addnode; u32 index = 0; for (s16 z = node_min.Z; z <= node_max.Z; z++) for (s16 x = node_min.X; x <= node_max.X; x++, index++) { // Randomize mud amount s16 mud_add_amount = getMudAmount(index) / 2.0 + 0.5; // Find ground level s16 surface_y = find_stone_level(v2s16(x, z)); /////////////////optimize this! // Handle area not found if (surface_y == vm->m_area.MinEdge.Y - 1) continue; BiomeType bt = getBiome(index, v2s16(x, z)); addnode = (bt == BT_DESERT) ? n_desert_sand : n_dirt; if (bt == BT_DESERT && surface_y + mud_add_amount <= water_level + 1) { addnode = n_sand; } else if (mud_add_amount <= 0) { mud_add_amount = 1 - mud_add_amount; addnode = n_gravel; } else if (bt == BT_NORMAL && getHaveBeach(index) && surface_y + mud_add_amount <= water_level + 2) { addnode = n_sand; } if (bt == BT_DESERT && surface_y > 20) mud_add_amount = MYMAX(0, mud_add_amount - (surface_y - 20) / 5); // If topmost node is grass, change it to mud. It might be if it was // flown to there from a neighboring chunk and then converted. u32 i = vm->m_area.index(x, surface_y, z); if (vm->m_data[i].getContent() == c_dirt_with_grass) vm->m_data[i] = n_dirt; // Add mud on ground s16 mudcount = 0; v3s16 em = vm->m_area.getExtent(); s16 y_start = surface_y + 1; i = vm->m_area.index(x, y_start, z); for (s16 y = y_start; y <= node_max.Y; y++) { if (mudcount >= mud_add_amount) break; vm->m_data[i] = addnode; mudcount++; vm->m_area.add_y(em, i, 1); } } } void MapgenV6::flowMud(s16 &mudflow_minpos, s16 &mudflow_maxpos) { // 340ms @cs=8 TimeTaker timer1("flow mud"); // Iterate a few times for(s16 k = 0; k < 3; k++) { for (s16 z = mudflow_minpos; z <= mudflow_maxpos; z++) for (s16 x = mudflow_minpos; x <= mudflow_maxpos; x++) { // Invert coordinates every 2nd iteration if (k % 2 == 0) { x = mudflow_maxpos - (x - mudflow_minpos); z = mudflow_maxpos - (z - mudflow_minpos); } // Node position in 2d v2s16 p2d = v2s16(node_min.X, node_min.Z) + v2s16(x, z); v3s16 em = vm->m_area.getExtent(); u32 i = vm->m_area.index(p2d.X, node_max.Y, p2d.Y); s16 y = node_max.Y; while(y >= node_min.Y) { for(;; y--) { MapNode *n = NULL; // Find mud for(; y >= node_min.Y; y--) { n = &vm->m_data[i]; if (n->getContent() == c_dirt || n->getContent() == c_dirt_with_grass || n->getContent() == c_gravel) break; vm->m_area.add_y(em, i, -1); } // Stop if out of area //if(vmanip.m_area.contains(i) == false) if (y < node_min.Y) break; if (n->getContent() == c_dirt || n->getContent() == c_dirt_with_grass) { // Make it exactly mud n->setContent(c_dirt); // Don't flow it if the stuff under it is not mud { u32 i2 = i; vm->m_area.add_y(em, i2, -1); // Cancel if out of area if(vm->m_area.contains(i2) == false) continue; MapNode *n2 = &vm->m_data[i2]; if (n2->getContent() != c_dirt && n2->getContent() != c_dirt_with_grass) continue; } } v3s16 dirs4[4] = { v3s16(0,0,1), // back v3s16(1,0,0), // right v3s16(0,0,-1), // front v3s16(-1,0,0), // left }; // Check that upper is air or doesn't exist. // Cancel dropping if upper keeps it in place u32 i3 = i; vm->m_area.add_y(em, i3, 1); if (vm->m_area.contains(i3) == true && ndef->get(vm->m_data[i3]).walkable) continue; // Drop mud on side for(u32 di=0; di<4; di++) { v3s16 dirp = dirs4[di]; u32 i2 = i; // Move to side vm->m_area.add_p(em, i2, dirp); // Fail if out of area if (vm->m_area.contains(i2) == false) continue; // Check that side is air MapNode *n2 = &vm->m_data[i2]; if (ndef->get(*n2).walkable) continue; // Check that under side is air vm->m_area.add_y(em, i2, -1); if (vm->m_area.contains(i2) == false) continue; n2 = &vm->m_data[i2]; if (ndef->get(*n2).walkable) continue; // Loop further down until not air bool dropped_to_unknown = false; do { vm->m_area.add_y(em, i2, -1); n2 = &vm->m_data[i2]; // if out of known area if(vm->m_area.contains(i2) == false || n2->getContent() == CONTENT_IGNORE) { dropped_to_unknown = true; break; } } while (ndef->get(*n2).walkable == false); // Loop one up so that we're in air vm->m_area.add_y(em, i2, 1); n2 = &vm->m_data[i2]; bool old_is_water = (n->getContent() == c_water_source); // Move mud to new place if (!dropped_to_unknown) { *n2 = *n; // Set old place to be air (or water) if(old_is_water) *n = MapNode(c_water_source); else *n = MapNode(CONTENT_AIR); } // Done break; } } } } } } void MapgenV6::addDirtGravelBlobs() { if (getBiome(v2s16(node_min.X, node_min.Z)) != BT_NORMAL) return; PseudoRandom pr(blockseed + 983); for (int i = 0; i < volume_nodes/10/10/10; i++) { bool only_fill_cave = (myrand_range(0,1) != 0); v3s16 size( pr.range(1, 8), pr.range(1, 8), pr.range(1, 8) ); v3s16 p0( pr.range(node_min.X, node_max.X) - size.X / 2, pr.range(node_min.Y, node_max.Y) - size.Y / 2, pr.range(node_min.Z, node_max.Z) - size.Z / 2 ); MapNode n1((p0.Y > -32 && !pr.range(0, 1)) ? c_dirt : c_gravel); for (int z1 = 0; z1 < size.Z; z1++) for (int y1 = 0; y1 < size.Y; y1++) for (int x1 = 0; x1 < size.X; x1++) { v3s16 p = p0 + v3s16(x1, y1, z1); u32 i = vm->m_area.index(p); if (!vm->m_area.contains(i)) continue; // Cancel if not stone and not cave air if (vm->m_data[i].getContent() != c_stone && !(vm->m_flags[i] & VMANIP_FLAG_CAVE)) continue; if (only_fill_cave && !(vm->m_flags[i] & VMANIP_FLAG_CAVE)) continue; vm->m_data[i] = n1; } } } void MapgenV6::placeTreesAndJungleGrass() { //TimeTaker t("placeTrees"); if (node_max.Y < water_level) return; PseudoRandom grassrandom(blockseed + 53); content_t c_junglegrass = ndef->getId("mapgen_junglegrass"); // if we don't have junglegrass, don't place cignore... that's bad if (c_junglegrass == CONTENT_IGNORE) c_junglegrass = CONTENT_AIR; MapNode n_junglegrass(c_junglegrass); v3s16 em = vm->m_area.getExtent(); // Divide area into parts s16 div = 8; s16 sidelen = central_area_size.X / div; double area = sidelen * sidelen; // N.B. We must add jungle grass first, since tree leaves will // obstruct the ground, giving us a false ground level for (s16 z0 = 0; z0 < div; z0++) for (s16 x0 = 0; x0 < div; x0++) { // Center position of part of division v2s16 p2d_center( node_min.X + sidelen / 2 + sidelen * x0, node_min.Z + sidelen / 2 + sidelen * z0 ); // Minimum edge of part of division v2s16 p2d_min( node_min.X + sidelen * x0, node_min.Z + sidelen * z0 ); // Maximum edge of part of division v2s16 p2d_max( node_min.X + sidelen + sidelen * x0 - 1, node_min.Z + sidelen + sidelen * z0 - 1 ); // Amount of trees, jungle area u32 tree_count = area * getTreeAmount(p2d_center); float humidity; bool is_jungle = false; if (flags & MGV6_JUNGLES) { humidity = getHumidity(p2d_center); if (humidity > 0.75) { is_jungle = true; tree_count *= 4; } } // Add jungle grass if (is_jungle) { u32 grass_count = 5 * humidity * tree_count; for (u32 i = 0; i < grass_count; i++) { s16 x = grassrandom.range(p2d_min.X, p2d_max.X); s16 z = grassrandom.range(p2d_min.Y, p2d_max.Y); s16 y = findGroundLevelFull(v2s16(x, z)); ////////////////optimize this! if (y < water_level || y < node_min.Y || y > node_max.Y) continue; u32 vi = vm->m_area.index(x, y, z); // place on dirt_with_grass, since we know it is exposed to sunlight if (vm->m_data[vi].getContent() == c_dirt_with_grass) { vm->m_area.add_y(em, vi, 1); vm->m_data[vi] = n_junglegrass; } } } // Put trees in random places on part of division for (u32 i = 0; i < tree_count; i++) { s16 x = myrand_range(p2d_min.X, p2d_max.X); s16 z = myrand_range(p2d_min.Y, p2d_max.Y); s16 y = findGroundLevelFull(v2s16(x, z)); ////////////////////optimize this! // Don't make a tree under water level // Don't make a tree so high that it doesn't fit if(y < water_level || y > node_max.Y - 6) continue; v3s16 p(x,y,z); // Trees grow only on mud and grass { u32 i = vm->m_area.index(p); MapNode *n = &vm->m_data[i]; if (n->getContent() != c_dirt && n->getContent() != c_dirt_with_grass) continue; } p.Y++; // Make a tree if (is_jungle) { treegen::make_jungletree(*vm, p, ndef, myrand()); } else { bool is_apple_tree = (myrand_range(0, 3) == 0) && getHaveAppleTree(v2s16(x, z)); treegen::make_tree(*vm, p, is_apple_tree, ndef, myrand()); } } } //printf("placeTreesAndJungleGrass: %dms\n", t.stop()); } void MapgenV6::growGrass() { for (s16 z = full_node_min.Z; z <= full_node_max.Z; z++) for (s16 x = full_node_min.X; x <= full_node_max.X; x++) { // Find the lowest surface to which enough light ends up to make // grass grow. Basically just wait until not air and not leaves. s16 surface_y = 0; { v3s16 em = vm->m_area.getExtent(); u32 i = vm->m_area.index(x, node_max.Y, z); s16 y; // Go to ground level for (y = node_max.Y; y >= full_node_min.Y; y--) { MapNode &n = vm->m_data[i]; if (ndef->get(n).param_type != CPT_LIGHT || ndef->get(n).liquid_type != LIQUID_NONE) break; vm->m_area.add_y(em, i, -1); } surface_y = (y >= full_node_min.Y) ? y : full_node_min.Y; } u32 i = vm->m_area.index(x, surface_y, z); MapNode *n = &vm->m_data[i]; if (n->getContent() == c_dirt && surface_y >= water_level - 20) n->setContent(c_dirt_with_grass); } } void MapgenV6::generateCaves(int max_stone_y) { float cave_amount = NoisePerlin2D(np_cave, node_min.X, node_min.Y, seed); int volume_nodes = (node_max.X - node_min.X + 1) * (node_max.Y - node_min.Y + 1) * MAP_BLOCKSIZE; cave_amount = MYMAX(0.0, cave_amount); u32 caves_count = cave_amount * volume_nodes / 50000; u32 bruises_count = 1; PseudoRandom ps(blockseed + 21343); PseudoRandom ps2(blockseed + 1032); if (ps.range(1, 6) == 1) bruises_count = ps.range(0, ps.range(0, 2)); if (getBiome(v2s16(node_min.X, node_min.Z)) == BT_DESERT) { caves_count /= 3; bruises_count /= 3; } for (u32 i = 0; i < caves_count + bruises_count; i++) { bool large_cave = (i >= caves_count); CaveV6 cave(this, &ps, &ps2, large_cave); cave.makeCave(node_min, node_max, max_stone_y); } }