2012-01-29 19:28:19 +00:00
#include "Globals.h"
2011-12-25 12:55:20 +00:00
#include "cChunkGenerator.h"
#include "cWorld.h"
2012-05-25 07:18:52 +00:00
#include "../iniFile/iniFile.h"
#include "BioGen.h"
#include "HeiGen.h"
#include "CompoGen.h"
#include "StructGen.h"
#include "FinishGen.h"
2011-12-25 12:55:20 +00:00
2012-01-29 14:29:26 +00:00
/// If the generation queue size exceeds this number, a warning will be output
2012-02-13 21:47:03 +00:00
const int QUEUE_WARNING_LIMIT = 1000 ;
/// If the generation queue size exceeds this number, chunks with no clients will be skipped
2012-02-28 10:01:42 +00:00
const int QUEUE_SKIP_LIMIT = 500 ;
2012-01-29 14:29:26 +00:00
2011-12-25 12:55:20 +00:00
2012-02-13 21:47:03 +00:00
cChunkGenerator :: cChunkGenerator ( void )
: super ( "cChunkGenerator" )
, m_World ( NULL )
2012-05-25 07:18:52 +00:00
, m_BiomeGen ( NULL )
, m_HeightGen ( NULL )
, m_CompositionGen ( NULL )
2011-12-25 12:55:20 +00:00
{
2012-02-13 21:47:03 +00:00
}
2012-01-29 14:29:26 +00:00
2012-02-13 21:47:03 +00:00
cChunkGenerator ::~ cChunkGenerator ()
{
Stop ();
}
2012-01-29 14:29:26 +00:00
2011-12-25 12:55:20 +00:00
2012-05-25 07:18:52 +00:00
bool cChunkGenerator :: Start ( cWorld * a_World , cIniFile & a_IniFile )
2011-12-25 12:55:20 +00:00
{
2012-05-25 07:18:52 +00:00
MTRand rnd ;
2012-02-13 21:47:03 +00:00
m_World = a_World ;
2012-05-25 07:18:52 +00:00
m_Seed = a_IniFile . GetValueI ( "Seed" , "Seed" , rnd . randInt ());
2012-02-13 21:47:03 +00:00
2012-05-25 07:18:52 +00:00
// TODO: Remove this after INI file interface changes ( http://forum.mc-server.org/showthread.php?tid=427 )
a_IniFile . DeleteValue ( "Seed" , "Seed" );
a_IniFile . SetValueI ( "Seed" , "Seed" , m_Seed );
InitBiomeGen ( a_IniFile );
InitHeightGen ( a_IniFile );
InitCompositionGen ( a_IniFile );
InitStructureGens ( a_IniFile );
InitFinishGens ( a_IniFile );
a_IniFile . WriteFile ();
2012-02-13 21:47:03 +00:00
return super :: Start ();
2011-12-25 12:55:20 +00:00
}
2012-01-29 14:29:26 +00:00
2012-02-13 21:47:03 +00:00
void cChunkGenerator :: Stop ( void )
2011-12-25 12:55:20 +00:00
{
2012-03-10 17:37:00 +00:00
m_ShouldTerminate = true ;
2012-02-13 21:47:03 +00:00
m_Event . Set ();
2012-02-18 17:53:22 +00:00
m_evtRemoved . Set (); // Wake up anybody waiting for empty queue
2012-02-13 21:47:03 +00:00
Wait ();
2012-05-25 07:18:52 +00:00
// Delete the generating composition:
for ( cFinishGenList :: const_iterator itr = m_FinishGens . begin (); itr != m_FinishGens . end (); ++ itr )
{
delete * itr ;
}
m_FinishGens . clear ();
for ( cStructureGenList :: const_iterator itr = m_StructureGens . begin (); itr != m_StructureGens . end (); ++ itr )
{
delete * itr ;
}
m_StructureGens . clear ();
delete m_CompositionGen ;
m_CompositionGen = NULL ;
delete m_HeightGen ;
m_HeightGen = NULL ;
delete m_BiomeGen ;
m_BiomeGen = NULL ;
}
void cChunkGenerator :: InitBiomeGen ( cIniFile & a_IniFile )
{
AString BiomeGenName = a_IniFile . GetValue ( "Generator" , "BiomeGen" , "" );
if ( BiomeGenName . empty ())
{
LOGWARN ( "[Generator]::BiomeGen value not found in world.ini, using \" constant \" ." );
BiomeGenName = "constant" ;
}
2012-02-13 21:47:03 +00:00
2012-06-02 13:21:59 +00:00
bool CacheOffByDefault = false ;
2012-05-25 07:18:52 +00:00
if ( NoCaseCompare ( BiomeGenName , "constant" ) == 0 )
{
2012-05-27 10:57:36 +00:00
AString Biome = a_IniFile . GetValue ( "Generator" , "ConstantBiome" , "Plains" );
EMCSBiome b = StringToBiome ( Biome );
if ( b == - 1 )
{
2012-06-02 12:19:20 +00:00
LOGWARN ( "[Generator]::ConstantBiome value \" %s \" not recognized, using \" Plains \" ." , Biome . c_str ());
2012-05-27 10:57:36 +00:00
b = biPlains ;
}
m_BiomeGen = new cBioGenConstant ( b );
2012-06-02 13:21:59 +00:00
CacheOffByDefault = true ; // we're generating faster than a cache would retrieve data :)
2012-05-25 07:18:52 +00:00
}
else if ( NoCaseCompare ( BiomeGenName , "checkerboard" ) == 0 )
{
int BiomeSize = a_IniFile . GetValueI ( "Generator" , "CheckerboardBiomeSize" , 64 );
2012-05-27 10:51:04 +00:00
AString Biomes = a_IniFile . GetValue ( "Generator" , "CheckerBoardBiomes" , "" );
m_BiomeGen = new cBioGenCheckerboard ( BiomeSize , Biomes );
2012-06-02 13:21:59 +00:00
CacheOffByDefault = true ; // we're (probably) generating faster than a cache would retrieve data
2012-05-25 07:18:52 +00:00
}
2012-05-27 14:17:47 +00:00
else if ( NoCaseCompare ( BiomeGenName , "voronoi" ) == 0 )
{
int CellSize = a_IniFile . GetValueI ( "Generator" , "VoronoiCellSize" , 64 );
AString Biomes = a_IniFile . GetValue ( "Generator" , "VoronoiBiomes" , "" );
m_BiomeGen = new cBioGenVoronoi ( m_Seed , CellSize , Biomes );
}
2012-05-25 07:18:52 +00:00
else
{
if ( NoCaseCompare ( BiomeGenName , "distortedvoronoi" ) != 0 )
{
LOGWARNING ( "Unknown BiomeGen \" %s \" , using \" distortedvoronoi \" instead." , BiomeGenName . c_str ());
}
2012-05-31 20:34:58 +00:00
int CellSize = a_IniFile . GetValueI ( "Generator" , "DistortedVoronoiCellSize" , 96 );
AString Biomes = a_IniFile . GetValue ( "Generator" , "DistortedVoronoiBiomes" , "" );
m_BiomeGen = new cBioGenDistortedVoronoi ( m_Seed , CellSize , Biomes );
2012-05-25 07:18:52 +00:00
}
2012-06-02 13:21:59 +00:00
// Add a cache, if requested:
int CacheSize = a_IniFile . GetValueI ( "Generator" , "BiomeGenCacheSize" , CacheOffByDefault ? 0 : 64 );
if ( CacheSize > 0 )
{
if ( CacheSize < 4 )
{
LOGWARNING ( "Biomegen cache size set too low, would hurt performance instead of helping. Increasing from %d to %d" ,
CacheSize , 4
);
CacheSize = 4 ;
}
LOGINFO ( "Using a cache for biomegen of size %d." , CacheSize );
m_BiomeGen = new cBioGenCache ( m_BiomeGen , CacheSize );
}
2012-05-25 07:18:52 +00:00
}
void cChunkGenerator :: InitHeightGen ( cIniFile & a_IniFile )
{
AString HeightGenName = a_IniFile . GetValue ( "Generator" , "HeightGen" , "" );
if ( HeightGenName . empty ())
{
LOGWARN ( "[Generator]::HeightGen value not found in world.ini, using \" classic \" ." );
HeightGenName = "classic" ;
}
2012-06-02 13:45:57 +00:00
bool CacheOffByDefault = false ;
2012-05-25 07:18:52 +00:00
if ( NoCaseCompare ( HeightGenName , "flat" ) == 0 )
{
int Height = a_IniFile . GetValueI ( "Generator" , "FlatHeight" , 5 );
m_HeightGen = new cHeiGenFlat ( Height );
2012-06-02 13:45:57 +00:00
CacheOffByDefault = true ; // We're generating faster than a cache would retrieve data
2012-05-25 07:18:52 +00:00
}
2012-06-02 12:19:20 +00:00
else if ( NoCaseCompare ( HeightGenName , "classic" ) == 0 )
2012-05-25 07:18:52 +00:00
{
// These used to be in terrain.ini, but now they are in world.ini (so that multiple worlds can have different values):
float HeightFreq1 = ( float ) a_IniFile . GetValueF ( "Generator" , "ClassicHeightFreq1" , 0.1 );
float HeightFreq2 = ( float ) a_IniFile . GetValueF ( "Generator" , "ClassicHeightFreq2" , 1.0 );
float HeightFreq3 = ( float ) a_IniFile . GetValueF ( "Generator" , "ClassicHeightFreq3" , 2.0 );
float HeightAmp1 = ( float ) a_IniFile . GetValueF ( "Generator" , "ClassicHeightAmp1" , 1.0 );
float HeightAmp2 = ( float ) a_IniFile . GetValueF ( "Generator" , "ClassicHeightAmp2" , 0.5 );
float HeightAmp3 = ( float ) a_IniFile . GetValueF ( "Generator" , "ClassicHeightAmp3" , 0.5 );
m_HeightGen = new cHeiGenClassic ( m_Seed , HeightFreq1 , HeightAmp1 , HeightFreq2 , HeightAmp2 , HeightFreq3 , HeightAmp3 );
}
2012-06-02 12:19:20 +00:00
else // "biomal" or <not found>
{
if ( NoCaseCompare ( HeightGenName , "biomal" ) != 0 )
{
LOGWARN ( "Unknown HeightGen \" %s \" , using \" Biomal \" instead." , HeightGenName . c_str ());
}
m_HeightGen = new cHeiGenBiomal ( m_Seed , * m_BiomeGen );
}
2012-06-02 13:45:57 +00:00
// Add a cache, if requested:
int CacheSize = a_IniFile . GetValueI ( "Generator" , "HeightGenCacheSize" , CacheOffByDefault ? 0 : 64 );
if ( CacheSize > 0 )
{
if ( CacheSize < 4 )
{
LOGWARNING ( "Heightgen cache size set too low, would hurt performance instead of helping. Increasing from %d to %d" ,
CacheSize , 4
);
CacheSize = 4 ;
}
LOGINFO ( "Using a cache for Heightgen of size %d." , CacheSize );
m_HeightGen = new cHeiGenCache ( m_HeightGen , CacheSize );
}
2012-05-25 07:18:52 +00:00
}
void cChunkGenerator :: InitCompositionGen ( cIniFile & a_IniFile )
{
AString CompoGenName = a_IniFile . GetValue ( "Generator" , "CompositionGen" , "" );
if ( CompoGenName . empty ())
{
LOGWARN ( "[Generator]::CompositionGen value not found in world.ini, using \" classic \" ." );
CompoGenName = "classic" ;
}
if ( NoCaseCompare ( CompoGenName , "sameblock" ) == 0 )
{
AString BlockType = a_IniFile . GetValue ( "Generator" , "SameBlockType" , "" );
if ( BlockType . empty ())
{
LOGWARN ( "[Generator]::SameBlockType value not found in world.ini, using \" stone \" ." );
BlockType = "stone" ;
}
int Block = BlockStringToType ( BlockType );
if ( Block < 0 )
{
LOGWARN ( "World.ini: [Generator]::SameBlockType value \" %s \" not parseable (use a number or alias from items.ini), using \" stone \" (1)." , BlockType . c_str ());
Block = E_BLOCK_STONE ;
}
bool Bedrocked = ( a_IniFile . GetValueI ( "Generator" , "SameBlockBedrocked" , 1 ) != 0 );
m_CompositionGen = new cCompoGenSameBlock (( BLOCKTYPE ) Block , Bedrocked );
}
else if ( NoCaseCompare ( CompoGenName , "debugbiomes" ) == 0 )
{
2012-06-02 20:44:15 +00:00
m_CompositionGen = new cCompoGenDebugBiomes ;
2012-05-25 07:18:52 +00:00
}
2012-06-02 20:44:15 +00:00
else if ( NoCaseCompare ( CompoGenName , "classic" ) == 0 )
2012-05-25 07:18:52 +00:00
{
int SeaLevel = a_IniFile . GetValueI ( "Generator" , "ClassicSeaLevel" , 60 );
int BeachHeight = a_IniFile . GetValueI ( "Generator" , "ClassicBeachHeight" , 2 );
int BeachDepth = a_IniFile . GetValueI ( "Generator" , "ClassicBeachDepth" , 4 );
m_CompositionGen = new cCompoGenClassic ( SeaLevel , BeachHeight , BeachDepth );
}
2012-06-02 20:44:15 +00:00
else
{
if ( NoCaseCompare ( CompoGenName , "biomal" ) != 0 )
{
LOGWARN ( "Unknown CompositionGen \" %s \" , using \" biomal \" instead." , CompoGenName . c_str ());
}
int SeaLevel = a_IniFile . GetValueI ( "Generator" , "BiomalSeaLevel" , 62 );
m_CompositionGen = new cCompoGenBiomal ( m_Seed , SeaLevel );
}
2012-05-25 07:18:52 +00:00
}
void cChunkGenerator :: InitStructureGens ( cIniFile & a_IniFile )
{
2012-05-27 10:51:04 +00:00
AString Structures = a_IniFile . GetValue ( "Generator" , "Structures" , "Trees,MarbleCaves,OreNests" );
2012-05-25 07:18:52 +00:00
AStringVector Str = StringSplit ( Structures , "," );
for ( AStringVector :: const_iterator itr = Str . begin (); itr != Str . end (); ++ itr )
{
if ( NoCaseCompare ( * itr , "trees" ) == 0 )
{
m_StructureGens . push_back ( new cStructGenTrees ( m_Seed , m_BiomeGen , m_HeightGen , m_CompositionGen ));
}
else if ( NoCaseCompare ( * itr , "marblecaves" ) == 0 )
{
m_StructureGens . push_back ( new cStructGenMarbleCaves ( m_Seed ));
}
else if ( NoCaseCompare ( * itr , "orenests" ) == 0 )
{
m_StructureGens . push_back ( new cStructGenOreNests ( m_Seed ));
}
else
{
LOGWARNING ( "Unknown structure generator: \" %s \" . Ignoring." , itr -> c_str ());
}
} // for itr - Str[]
2011-12-25 12:55:20 +00:00
}
2012-01-29 14:29:26 +00:00
2012-05-25 07:18:52 +00:00
void cChunkGenerator :: InitFinishGens ( cIniFile & a_IniFile )
{
AString Structures = a_IniFile . GetValue ( "Generator" , "Finishers" , "SprinkleFoliage" );
AStringVector Str = StringSplit ( Structures , "," );
for ( AStringVector :: const_iterator itr = Str . begin (); itr != Str . end (); ++ itr )
{
if ( NoCaseCompare ( * itr , "SprinkleFoliage" ) == 0 )
{
m_FinishGens . push_back ( new cFinishGenSprinkleFoliage ( m_Seed ));
}
else if ( NoCaseCompare ( * itr , "Snow" ) == 0 )
{
m_FinishGens . push_back ( new cFinishGenSnow );
}
else if ( NoCaseCompare ( * itr , "Ice" ) == 0 )
{
m_FinishGens . push_back ( new cFinishGenIce );
}
} // for itr - Str[]
}
void cChunkGenerator :: QueueGenerateChunk ( int a_ChunkX , int a_ChunkY , int a_ChunkZ )
2011-12-25 12:55:20 +00:00
{
{
2012-02-28 16:59:59 +00:00
cCSLock Lock ( m_CS );
// Check if it is already in the queue:
for ( cChunkCoordsList :: iterator itr = m_Queue . begin (); itr != m_Queue . end (); ++ itr )
{
if (( itr -> m_ChunkX == a_ChunkX ) && ( itr -> m_ChunkY == a_ChunkY ) && ( itr -> m_ChunkZ == a_ChunkZ ))
{
// Already in the queue, bail out
return ;
}
} // for itr - m_Queue[]
// Add to queue, issue a warning if too many:
if ( m_Queue . size () >= QUEUE_WARNING_LIMIT )
2011-12-25 12:55:20 +00:00
{
2012-02-28 16:59:59 +00:00
LOGWARN ( "WARNING: Adding chunk [%i, %i] to generation queue; Queue is too big! (%i)" , a_ChunkX , a_ChunkZ , m_Queue . size ());
2011-12-25 12:55:20 +00:00
}
2012-02-28 16:59:59 +00:00
m_Queue . push_back ( cChunkCoords ( a_ChunkX , a_ChunkY , a_ChunkZ ));
2011-12-25 12:55:20 +00:00
}
2012-01-29 14:29:26 +00:00
2012-02-13 21:47:03 +00:00
m_Event . Set ();
2011-12-25 12:55:20 +00:00
}
2012-01-29 14:29:26 +00:00
2012-05-25 07:18:52 +00:00
void cChunkGenerator :: GenerateBiomes ( int a_ChunkX , int a_ChunkZ , cChunkDef :: BiomeMap & a_BiomeMap )
{
m_BiomeGen -> GenBiomes ( a_ChunkX , a_ChunkZ , a_BiomeMap );
}
2012-02-18 17:53:22 +00:00
void cChunkGenerator :: WaitForQueueEmpty ( void )
{
cCSLock Lock ( m_CS );
2012-03-10 17:37:00 +00:00
while ( ! m_ShouldTerminate && ! m_Queue . empty ())
2012-02-18 17:53:22 +00:00
{
cCSUnlock Unlock ( Lock );
m_evtRemoved . Wait ();
}
}
int cChunkGenerator :: GetQueueLength ( void )
{
cCSLock Lock ( m_CS );
return ( int ) m_Queue . size ();
}
2012-05-25 07:18:52 +00:00
EMCSBiome cChunkGenerator :: GetBiomeAt ( int a_BlockX , int a_BlockZ )
{
cChunkDef :: BiomeMap Biomes ;
int Y = 0 ;
int ChunkX , ChunkZ ;
cWorld :: AbsoluteToRelative ( a_BlockX , Y , a_BlockZ , ChunkX , Y , ChunkZ );
m_BiomeGen -> GenBiomes ( ChunkX , ChunkZ , Biomes );
return cChunkDef :: GetBiome ( Biomes , a_BlockX , a_BlockZ );
}
2012-02-13 21:47:03 +00:00
void cChunkGenerator :: Execute ( void )
2011-12-25 12:55:20 +00:00
{
2012-03-10 17:37:00 +00:00
while ( ! m_ShouldTerminate )
2011-12-25 12:55:20 +00:00
{
2012-02-13 21:47:03 +00:00
cCSLock Lock ( m_CS );
while ( m_Queue . size () == 0 )
2011-12-25 12:55:20 +00:00
{
2012-01-29 14:29:26 +00:00
cCSUnlock Unlock ( Lock );
2012-02-13 21:47:03 +00:00
m_Event . Wait ();
2012-03-10 17:37:00 +00:00
if ( m_ShouldTerminate )
2012-02-13 21:47:03 +00:00
{
return ;
}
2011-12-25 12:55:20 +00:00
}
2012-01-29 14:29:26 +00:00
2012-02-13 21:47:03 +00:00
cChunkCoords coords = m_Queue . front (); // Get next coord from queue
m_Queue . erase ( m_Queue . begin () ); // Remove coordinate from queue
bool SkipEnabled = ( m_Queue . size () > QUEUE_SKIP_LIMIT );
2012-01-29 14:29:26 +00:00
Lock . Unlock (); // Unlock ASAP
2012-02-18 17:53:22 +00:00
m_evtRemoved . Set ();
2011-12-25 12:55:20 +00:00
2012-04-10 11:22:11 +00:00
// Hack for regenerating chunks: if Y != 0, the chunk is considered invalid, even if it has its data set
if (( coords . m_ChunkY == 0 ) && m_World -> IsChunkValid ( coords . m_ChunkX , coords . m_ChunkY , coords . m_ChunkZ ))
2012-02-13 21:47:03 +00:00
{
2012-04-10 11:22:11 +00:00
LOGD ( "Chunk [%d, %d] already generated, skipping generation" , coords . m_ChunkX , coords . m_ChunkZ );
2012-02-28 10:01:42 +00:00
// Already generated, ignore request
continue ;
}
if ( SkipEnabled && ! m_World -> HasChunkAnyClients ( coords . m_ChunkX , coords . m_ChunkY , coords . m_ChunkZ ))
{
2012-04-10 11:22:11 +00:00
LOGWARNING ( "Chunk generator overloaded, skipping chunk [%d, %d, %d]" , coords . m_ChunkX , coords . m_ChunkY , coords . m_ChunkZ );
2011-12-25 12:55:20 +00:00
continue ;
}
2012-02-16 17:45:26 +00:00
2012-02-18 17:53:22 +00:00
LOGD ( "Generating chunk [%d, %d, %d]" , coords . m_ChunkX , coords . m_ChunkY , coords . m_ChunkZ );
2012-02-17 17:56:25 +00:00
DoGenerate ( coords . m_ChunkX , coords . m_ChunkY , coords . m_ChunkZ );
2012-02-13 21:47:03 +00:00
2012-02-16 17:45:26 +00:00
// Save the chunk right after generating, so that we don't have to generate it again on next run
2012-02-17 17:56:25 +00:00
m_World -> GetStorage (). QueueSaveChunk ( coords . m_ChunkX , coords . m_ChunkY , coords . m_ChunkZ );
2012-01-29 14:29:26 +00:00
} // while (!bStop)
2011-12-25 12:55:20 +00:00
}
2012-01-29 14:29:26 +00:00
2012-02-17 17:56:25 +00:00
void cChunkGenerator :: DoGenerate ( int a_ChunkX , int a_ChunkY , int a_ChunkZ )
{
2012-05-25 07:18:52 +00:00
cChunkDef :: BiomeMap BiomeMap ;
cChunkDef :: BlockTypes BlockTypes ;
cChunkDef :: BlockNibbles BlockMeta ;
cChunkDef :: HeightMap HeightMap ;
2012-02-18 17:53:22 +00:00
cEntityList Entities ;
cBlockEntityList BlockEntities ;
2012-03-14 20:56:09 +00:00
2012-05-25 07:18:52 +00:00
// Use the composed generator:
m_BiomeGen -> GenBiomes ( a_ChunkX , a_ChunkZ , BiomeMap );
m_HeightGen -> GenHeightMap ( a_ChunkX , a_ChunkZ , HeightMap );
2012-06-02 20:44:15 +00:00
m_CompositionGen -> ComposeTerrain ( a_ChunkX , a_ChunkZ , BlockTypes , BlockMeta , HeightMap , BiomeMap , Entities , BlockEntities );
2012-05-25 07:18:52 +00:00
for ( cStructureGenList :: iterator itr = m_StructureGens . begin (); itr != m_StructureGens . end (); ++ itr )
{
( * itr ) -> GenStructures ( a_ChunkX , a_ChunkZ , BlockTypes , BlockMeta , HeightMap , Entities , BlockEntities );
} // for itr - m_StructureGens[]
for ( cFinishGenList :: iterator itr = m_FinishGens . begin (); itr != m_FinishGens . end (); ++ itr )
{
( * itr ) -> GenFinish ( a_ChunkX , a_ChunkZ , BlockTypes , BlockMeta , HeightMap , BiomeMap , Entities , BlockEntities );
} // for itr - m_FinishGens[]
m_World -> SetChunkData (
2012-03-14 20:56:09 +00:00
a_ChunkX , a_ChunkY , a_ChunkZ ,
2012-05-25 07:18:52 +00:00
BlockTypes , BlockMeta ,
NULL , NULL , // We don't have lighting, chunk will be lighted when needed
& HeightMap , & BiomeMap ,
Entities , BlockEntities ,
true
2012-03-14 20:56:09 +00:00
);
2012-02-17 17:56:25 +00:00
}