summaryrefslogtreecommitdiffstats
path: root/src/Generating/VillageGen.cpp
blob: 48bf9e4673a1917c9ea0716de7f7b4c82c82d971 (plain) (blame)
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
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426

// VillageGen.cpp

// Implements the cVillageGen class representing the village generator

#include "Globals.h"
#include "VillageGen.h"
#include "PieceGeneratorBFSTree.h"





/*
How village generating works:
By descending from a cGridStructGen, a semi-random (jitter) grid is generated. A village may be generated for each
of the grid's cells. Each cell checks the biomes in an entire chunk around its center, only generating a village if
all biomes are village-friendly. If yes, the entire village structure is built for that cell. If not, the cell
is left village-less.

A village is generated using the regular BFS piece generator. The well piece is used as the starting piece,
the roads and houses are then used as the following pieces. Only the houses are read from the prefabs,
though, the roads are generated by code and their content is ignored. A special subclass of the cPiecePool
class is used, so that the roads connect to each other and to the well only in predefined manners.

The well has connectors of type "2". The houses have connectors of type "-1". The roads have connectors of
both types' opposites, type "-2" at the far ends and type "1" on the long edges. Additionally, there are
type "2" connectors along the long edges of the roads as well, so that the roads create T junctions.

When the village is about to be drawn into a chunk, it queries the heights for each piece intersecting the
chunk. The pieces are shifted so that their first connector lies on the surface, and the roads are drawn
directly by turning the surface blocks into gravel / sandstone.

The village prefabs are stored in global piecepools (one pool per village type). In order to support
per-village density setting, the cVillage class itself implements the cPiecePool interface, relaying the
calls to the underlying cVillagePiecePool, after processing the density check.
*/

class cVillagePiecePool :
	public cPrefabPiecePool
{
	typedef cPrefabPiecePool super;
public:
	cVillagePiecePool(
		const cPrefab::sDef * a_PieceDefs,         size_t a_NumPieceDefs,
		const cPrefab::sDef * a_StartingPieceDefs, size_t a_NumStartingPieceDefs
	) :
		super(a_PieceDefs, a_NumPieceDefs, a_StartingPieceDefs, a_NumStartingPieceDefs)
	{
		AddRoadPieces();
	}

	cVillagePiecePool(void)
	{
		AddRoadPieces();
	}

	void AddRoadPieces(void)
	{
		// Add the road pieces:
		for (int len = 27; len < 60; len += 12)
		{
			cBlockArea BA;
			BA.Create(len, 1, 3, cBlockArea::baTypes | cBlockArea::baMetas);
			BA.Fill(cBlockArea::baTypes | cBlockArea::baMetas, E_BLOCK_GRAVEL, 0);
			cPrefab * RoadPiece = new cPrefab(BA, 1);
			RoadPiece->AddConnector(0,       0, 1, cPiece::cConnector::dirXM, -2);
			RoadPiece->AddConnector(len - 1, 0, 1, cPiece::cConnector::dirXP, -2);
			RoadPiece->SetDefaultWeight(100);

			// Add the road connectors:
			for (int x = 1; x < len; x += 12)
			{
				RoadPiece->AddConnector(x, 0, 0, cPiece::cConnector::dirZM, 2);
				RoadPiece->AddConnector(x, 0, 2, cPiece::cConnector::dirZP, 2);
			}

			// Add the buildings connectors:
			for (int x = 7; x < len; x += 12)
			{
				RoadPiece->AddConnector(x, 0, 0, cPiece::cConnector::dirZM, 1);
				RoadPiece->AddConnector(x, 0, 2, cPiece::cConnector::dirZP, 1);
			}
			m_AllPieces.push_back(RoadPiece);
			m_PiecesByConnector[-2].push_back(RoadPiece);
			m_PiecesByConnector[1].push_back(RoadPiece);
			m_PiecesByConnector[2].push_back(RoadPiece);
		}  // for len - roads of varying length
	}


	// cPrefabPiecePool overrides:
	virtual int GetPieceWeight(const cPlacedPiece & a_PlacedPiece, const cPiece::cConnector & a_ExistingConnector, const cPiece & a_NewPiece) override
	{
		// Roads cannot branch T-wise (appending -2 connector to a +2 connector on a 1-high piece):
		if ((a_ExistingConnector.m_Type == 2) && (a_PlacedPiece.GetDepth() > 0) && (a_PlacedPiece.GetPiece().GetSize().y == 1))
		{
			return 0;
		}

		return static_cast<const cPrefab &>(a_NewPiece).GetPieceWeight(a_PlacedPiece, a_ExistingConnector);
	}
};





class cVillageGen::cVillage :
	public cGridStructGen::cStructure,
	protected cPiecePool
{
	typedef cGridStructGen::cStructure super;

public:
	cVillage(
		int a_Seed,
		int a_GridX, int a_GridZ,
		int a_OriginX, int a_OriginZ,
		int a_MaxRoadDepth,
		int a_MaxSize,
		int a_Density,
		cVillagePiecePool & a_Prefabs,
		cTerrainHeightGenPtr a_HeightGen
	) :
		super(a_GridX, a_GridZ, a_OriginX, a_OriginZ),
		m_Seed(a_Seed),
		m_Noise(a_Seed),
		m_MaxSize(a_MaxSize),
		m_Density(a_Density),
		m_Borders(
			{a_OriginX - a_MaxSize, 0, a_OriginZ - a_MaxSize},
			{a_OriginX + a_MaxSize, cChunkDef::Height - 1, a_OriginZ + a_MaxSize}
		),
		m_Prefabs(a_Prefabs),
		m_HeightGen(a_HeightGen)
	{
		// Generate the pieces for this village; don't care about the Y coord:
		cPieceGeneratorBFSTree pg(*this, a_Seed);
		pg.PlacePieces(a_OriginX, a_OriginZ, a_MaxRoadDepth + 1, m_Pieces);
		if (m_Pieces.empty())
		{
			return;
		}
	}


protected:
	/** Seed for the random functions */
	int m_Seed;

	/** The noise used as a pseudo-random generator */
	cNoise m_Noise;

	/** Maximum size, in X / Z blocks, of the village (radius from the origin) */
	int m_MaxSize;

	/** The density for this village. Used to refrain from populating all house connectors. Range [0, 100] */
	int m_Density;

	/** Borders of the village - no item may reach out of this cuboid. */
	cCuboid m_Borders;

	/** Prefabs to use for buildings */
	cVillagePiecePool & m_Prefabs;

	/** The underlying height generator, used for placing the structures on top of the terrain. */
	cTerrainHeightGenPtr m_HeightGen;

	/** The village pieces, placed by the generator. */
	cPlacedPieces m_Pieces;


	// cGridStructGen::cStructure overrides:
	virtual void DrawIntoChunk(cChunkDesc & a_Chunk) override
	{
		// Iterate over all items
		// Each intersecting prefab is placed on ground, then drawn
		// Each intersecting road is drawn by replacing top soil blocks with gravel / sandstone blocks
		cChunkDef::HeightMap HeightMap;  // Heightmap for this chunk, used by roads
		m_HeightGen->GenHeightMap(a_Chunk.GetChunkX(), a_Chunk.GetChunkZ(), HeightMap);
		for (cPlacedPieces::iterator itr = m_Pieces.begin(), end = m_Pieces.end(); itr != end; ++itr)
		{
			const cPrefab & Prefab = static_cast<const cPrefab &>((*itr)->GetPiece());
			if ((*itr)->GetPiece().GetSize().y == 1)
			{
				// It's a road, special handling (change top terrain blocks to m_RoadBlock)
				DrawRoad(a_Chunk, **itr, HeightMap);
				continue;
			}
			if (Prefab.ShouldMoveToGround() && !(*itr)->HasBeenMovedToGround())
			{
				PlacePieceOnGround(**itr);
			}
			Prefab.Draw(a_Chunk, itr->get());
		}  // for itr - m_PlacedPieces[]
	}


	/**  Adjusts the Y coord of the given piece so that the piece is on the ground.
	Ground level is assumed to be represented by the first connector in the piece. */
	void PlacePieceOnGround(cPlacedPiece & a_Piece)
	{
		cPiece::cConnector FirstConnector = a_Piece.GetRotatedConnector(0);
		int ChunkX, ChunkZ;
		int BlockX = FirstConnector.m_Pos.x;
		int BlockZ = FirstConnector.m_Pos.z;
		int BlockY;
		cChunkDef::AbsoluteToRelative(BlockX, BlockY, BlockZ, ChunkX, ChunkZ);
		cChunkDef::HeightMap HeightMap;
		m_HeightGen->GenHeightMap(ChunkX, ChunkZ, HeightMap);
		int TerrainHeight = cChunkDef::GetHeight(HeightMap, BlockX, BlockZ);
		a_Piece.MoveToGroundBy(TerrainHeight - FirstConnector.m_Pos.y + 1);
	}


	/** Draws the road into the chunk.
	The heightmap is not queried from the heightgen, but is given via parameter, so that it may be queried just
	once for all roads in a chunk. */
	void DrawRoad(cChunkDesc & a_Chunk, cPlacedPiece & a_Road, cChunkDef::HeightMap & a_HeightMap)
	{
		cCuboid RoadCoords = a_Road.GetHitBox();
		RoadCoords.Sort();
		int MinX = std::max(RoadCoords.p1.x - a_Chunk.GetChunkX() * cChunkDef::Width, 0);
		int MaxX = std::min(RoadCoords.p2.x - a_Chunk.GetChunkX() * cChunkDef::Width, cChunkDef::Width - 1);
		int MinZ = std::max(RoadCoords.p1.z - a_Chunk.GetChunkZ() * cChunkDef::Width, 0);
		int MaxZ = std::min(RoadCoords.p2.z - a_Chunk.GetChunkZ() * cChunkDef::Width, cChunkDef::Width - 1);
		auto WaterRoadBlockType = m_Prefabs.GetVillageWaterRoadBlockType();
		auto WaterRoadBlockMeta = m_Prefabs.GetVillageWaterRoadBlockMeta();
		auto RoadBlockType = m_Prefabs.GetVillageRoadBlockType();
		auto RoadBlockMeta = m_Prefabs.GetVillageRoadBlockMeta();
		for (int z = MinZ; z <= MaxZ; z++)
		{
			for (int x = MinX; x <= MaxX; x++)
			{
				auto height = cChunkDef::GetHeight(a_HeightMap, x, z);
				if (IsBlockWater(a_Chunk.GetBlockType(x, height, z)))
				{
					a_Chunk.SetBlockTypeMeta(x, height, z, WaterRoadBlockType, WaterRoadBlockMeta);
				}
				else
				{
					a_Chunk.SetBlockTypeMeta(x, height, z, RoadBlockType, RoadBlockMeta);
				}
			}
		}
	}


	// cPiecePool overrides:
	virtual cPieces GetPiecesWithConnector(int a_ConnectorType) override
	{
		return m_Prefabs.GetPiecesWithConnector(a_ConnectorType);
	}


	virtual cPieces GetStartingPieces(void) override
	{
		return m_Prefabs.GetStartingPieces();
	}


	virtual int GetPieceWeight(
		const cPlacedPiece & a_PlacedPiece,
		const cPiece::cConnector & a_ExistingConnector,
		const cPiece & a_NewPiece
	) override
	{
		// Check against the density:
		if (a_ExistingConnector.m_Type == 1)
		{
			const Vector3i & Coords = a_PlacedPiece.GetRotatedConnector(a_ExistingConnector).m_Pos;
			int rnd = (m_Noise.IntNoise3DInt(Coords.x, Coords.y, Coords.z) / 7) % 100;
			if (rnd > m_Density)
			{
				return 0;
			}
		}

		// Density check passed, relay to m_Prefabs:
		return m_Prefabs.GetPieceWeight(a_PlacedPiece, a_ExistingConnector, a_NewPiece);
	}


	virtual int GetStartingPieceWeight(const cPiece & a_NewPiece) override
	{
		return m_Prefabs.GetStartingPieceWeight(a_NewPiece);
	}


	virtual void PiecePlaced(const cPiece & a_Piece) override
	{
		m_Prefabs.PiecePlaced(a_Piece);
	}


	virtual void Reset(void) override
	{
		m_Prefabs.Reset();
	}


	void MoveAllDescendants(cPlacedPieces & a_PlacedPieces, size_t a_Pivot, int a_HeightDifference)
	{
		size_t num = a_PlacedPieces.size();
		auto & Pivot = a_PlacedPieces[a_Pivot];
		for (size_t i = a_Pivot + 1; i < num; i++)
		{
			if (
				(a_PlacedPieces[i]->GetParent() == Pivot.get()) &&  // It is a direct dependant of the pivot
				!(static_cast<const cPrefab &>(a_PlacedPieces[i]->GetPiece())).ShouldMoveToGround()  // It attaches strictly by connectors
			)
			{
				a_PlacedPieces[i]->MoveToGroundBy(a_HeightDifference);
				MoveAllDescendants(a_PlacedPieces, i, a_HeightDifference);
			}
		}  // for i - a_PlacedPieces[]
	}
} ;





////////////////////////////////////////////////////////////////////////////////
// cVillageGen:

cVillageGen::cVillageGen(
	int a_Seed,
	int a_GridSize,
	int a_MaxOffset,
	int a_MaxDepth,
	int a_MaxSize,
	int a_MinDensity,
	int a_MaxDensity,
	cBiomeGenPtr a_BiomeGen,
	cTerrainHeightGenPtr a_HeightGen,
	int a_SeaLevel,
	const AStringVector & a_PrefabsToLoad
) :
	super(a_Seed, a_GridSize, a_GridSize, a_MaxOffset, a_MaxOffset, a_MaxSize, a_MaxSize, 100),
	m_RandNoise(a_Seed + 1000),
	m_MaxDepth(a_MaxDepth),
	m_MaxSize(a_MaxSize),
	m_MinDensity(a_MinDensity),
	m_MaxDensity(a_MaxDensity),
	m_BiomeGen(a_BiomeGen),
	m_HeightGen(a_HeightGen)
{
	for (const auto & toLoad: a_PrefabsToLoad)
	{
		auto prefabs = std::make_shared<cVillagePiecePool>();
		auto fileName = Printf("Prefabs%sVillages%s%s.cubeset", cFile::GetPathSeparator().c_str(), cFile::GetPathSeparator().c_str(), toLoad.c_str());
		if (prefabs->LoadFromFile(fileName, true))
		{
			if (NoCaseCompare(prefabs->GetIntendedUse(), "village") != 0)
			{
				LOGWARNING("Village generator: File %s is intended for use in \"%s\", rather than villages. Loading the file, but the generator may behave unexpectedly.",
					fileName.c_str(), prefabs->GetIntendedUse().c_str()
				);
			}
			prefabs->AssignGens(a_Seed, m_BiomeGen, m_HeightGen, a_SeaLevel);
			m_Pools.push_back(std::move(prefabs));
		}
	}
}





cGridStructGen::cStructurePtr cVillageGen::CreateStructure(int a_GridX, int a_GridZ, int a_OriginX, int a_OriginZ)
{
	// Generate the biomes for the chunk surrounding the origin:
	int ChunkX, ChunkZ;
	cChunkDef::BlockToChunk(a_OriginX, a_OriginZ, ChunkX, ChunkZ);
	cChunkDef::BiomeMap Biomes;
	m_BiomeGen->GenBiomes({ChunkX, ChunkZ}, Biomes);

	// Get a list of pools that support each biome within the chunk:
	// If just one column's biome is not allowed, the pool is not used because it's likely that an unfriendly biome is too close
	auto availablePools = m_Pools;
	for (size_t i = 0; i < ARRAYCOUNT(Biomes); i++)
	{
		auto biome = Biomes[i];
		availablePools.erase(std::remove_if(availablePools.begin(), availablePools.end(),
			[biome](std::shared_ptr<cVillagePiecePool> & a_Pool)
			{
				return !a_Pool->IsBiomeAllowed(biome);
			}),
			availablePools.end()
		);
		// Bail out if no compatible pools left:
		if (availablePools.empty())
		{
			return cStructurePtr();
		}
	}

	// Pick one pool from the available pools:
	if (availablePools.empty())
	{
		return cStructurePtr();
	}
	auto rnd = m_RandNoise.IntNoise2DInt(a_OriginX, a_OriginZ) / 11;
	auto pool = availablePools[static_cast<size_t>(rnd) % availablePools.size()];
	rnd /= 137;

	// Choose density for the village, random between m_MinDensity and m_MaxDensity:
	int Density;
	if (pool->GetMaxDensity() > pool->GetMinDensity())
	{
		Density = pool->GetMinDensity() + rnd % (pool->GetMaxDensity() - pool->GetMinDensity());
	}
	else
	{
		Density = pool->GetMinDensity();
	}

	// Create a village based on the chosen prefabs:
	return cStructurePtr(new cVillage(m_Seed, a_GridX, a_GridZ, a_OriginX, a_OriginZ, m_MaxDepth, m_MaxSize, Density, *pool.get(), m_HeightGen));
}