2012-01-29 14:28:19 -05:00
# include "Globals.h"
2011-12-25 07:55:20 -05:00
# include "cChunkGenerator.h"
# include "cWorld.h"
2012-05-25 03:18:52 -04:00
# include "../iniFile/iniFile.h"
# include "BioGen.h"
# include "HeiGen.h"
# include "CompoGen.h"
# include "StructGen.h"
# include "FinishGen.h"
2011-12-25 07:55:20 -05:00
2012-01-29 09:29:26 -05:00
/// If the generation queue size exceeds this number, a warning will be output
2012-02-13 16:47:03 -05: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 05:01:42 -05:00
const int QUEUE_SKIP_LIMIT = 500 ;
2012-01-29 09:29:26 -05:00
2011-12-25 07:55:20 -05:00
2012-02-13 16:47:03 -05:00
cChunkGenerator : : cChunkGenerator ( void )
: super ( " cChunkGenerator " )
, m_World ( NULL )
2012-05-25 03:18:52 -04:00
, m_BiomeGen ( NULL )
, m_HeightGen ( NULL )
, m_CompositionGen ( NULL )
2011-12-25 07:55:20 -05:00
{
2012-02-13 16:47:03 -05:00
}
2012-01-29 09:29:26 -05:00
2012-02-13 16:47:03 -05:00
cChunkGenerator : : ~ cChunkGenerator ( )
{
Stop ( ) ;
}
2012-01-29 09:29:26 -05:00
2011-12-25 07:55:20 -05:00
2012-05-25 03:18:52 -04:00
bool cChunkGenerator : : Start ( cWorld * a_World , cIniFile & a_IniFile )
2011-12-25 07:55:20 -05:00
{
2012-05-25 03:18:52 -04:00
MTRand rnd ;
2012-02-13 16:47:03 -05:00
m_World = a_World ;
2012-05-25 03:18:52 -04:00
m_Seed = a_IniFile . GetValueI ( " Seed " , " Seed " , rnd . randInt ( ) ) ;
2012-02-13 16:47:03 -05:00
2012-05-25 03:18:52 -04: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 16:47:03 -05:00
return super : : Start ( ) ;
2011-12-25 07:55:20 -05:00
}
2012-01-29 09:29:26 -05:00
2012-02-13 16:47:03 -05:00
void cChunkGenerator : : Stop ( void )
2011-12-25 07:55:20 -05:00
{
2012-03-10 12:37:00 -05:00
m_ShouldTerminate = true ;
2012-02-13 16:47:03 -05:00
m_Event . Set ( ) ;
2012-02-18 12:53:22 -05:00
m_evtRemoved . Set ( ) ; // Wake up anybody waiting for empty queue
2012-02-13 16:47:03 -05:00
Wait ( ) ;
2012-05-25 03:18:52 -04: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 16:47:03 -05:00
2012-06-02 09:21:59 -04:00
bool CacheOffByDefault = false ;
2012-05-25 03:18:52 -04:00
if ( NoCaseCompare ( BiomeGenName , " constant " ) = = 0 )
{
2012-05-27 06:57:36 -04:00
AString Biome = a_IniFile . GetValue ( " Generator " , " ConstantBiome " , " Plains " ) ;
EMCSBiome b = StringToBiome ( Biome ) ;
if ( b = = - 1 )
{
2012-06-02 08:19:20 -04:00
LOGWARN ( " [Generator]::ConstantBiome value \" %s \" not recognized, using \" Plains \" . " , Biome . c_str ( ) ) ;
2012-05-27 06:57:36 -04:00
b = biPlains ;
}
m_BiomeGen = new cBioGenConstant ( b ) ;
2012-06-02 09:21:59 -04:00
CacheOffByDefault = true ; // we're generating faster than a cache would retrieve data :)
2012-05-25 03:18:52 -04:00
}
else if ( NoCaseCompare ( BiomeGenName , " checkerboard " ) = = 0 )
{
int BiomeSize = a_IniFile . GetValueI ( " Generator " , " CheckerboardBiomeSize " , 64 ) ;
2012-05-27 06:51:04 -04:00
AString Biomes = a_IniFile . GetValue ( " Generator " , " CheckerBoardBiomes " , " " ) ;
m_BiomeGen = new cBioGenCheckerboard ( BiomeSize , Biomes ) ;
2012-06-02 09:21:59 -04:00
CacheOffByDefault = true ; // we're (probably) generating faster than a cache would retrieve data
2012-05-25 03:18:52 -04:00
}
2012-05-27 10:17:47 -04: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 03:18:52 -04:00
else
{
if ( NoCaseCompare ( BiomeGenName , " distortedvoronoi " ) ! = 0 )
{
LOGWARNING ( " Unknown BiomeGen \" %s \" , using \" distortedvoronoi \" instead. " , BiomeGenName . c_str ( ) ) ;
}
2012-05-31 16:34:58 -04: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 03:18:52 -04:00
}
2012-06-02 09:21:59 -04: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 03:18:52 -04: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 09:45:57 -04:00
bool CacheOffByDefault = false ;
2012-05-25 03:18:52 -04:00
if ( NoCaseCompare ( HeightGenName , " flat " ) = = 0 )
{
int Height = a_IniFile . GetValueI ( " Generator " , " FlatHeight " , 5 ) ;
m_HeightGen = new cHeiGenFlat ( Height ) ;
2012-06-02 09:45:57 -04:00
CacheOffByDefault = true ; // We're generating faster than a cache would retrieve data
2012-05-25 03:18:52 -04:00
}
2012-06-02 08:19:20 -04:00
else if ( NoCaseCompare ( HeightGenName , " classic " ) = = 0 )
2012-05-25 03:18:52 -04: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 08:19:20 -04: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 09:45:57 -04: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 03:18:52 -04: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 )
{
m_CompositionGen = new cCompoGenDebugBiomes ( m_BiomeGen ) ;
}
else
{
if ( NoCaseCompare ( CompoGenName , " classic " ) ! = 0 )
{
LOGWARN ( " Unknown CompositionGen \" %s \" , using \" classic \" instead. " , CompoGenName . c_str ( ) ) ;
}
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 ) ;
}
}
void cChunkGenerator : : InitStructureGens ( cIniFile & a_IniFile )
{
2012-05-27 06:51:04 -04:00
AString Structures = a_IniFile . GetValue ( " Generator " , " Structures " , " Trees,MarbleCaves,OreNests " ) ;
2012-05-25 03:18:52 -04: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 07:55:20 -05:00
}
2012-01-29 09:29:26 -05:00
2012-05-25 03:18:52 -04: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 07:55:20 -05:00
{
{
2012-02-28 11:59:59 -05: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 07:55:20 -05:00
{
2012-02-28 11:59:59 -05: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 07:55:20 -05:00
}
2012-02-28 11:59:59 -05:00
m_Queue . push_back ( cChunkCoords ( a_ChunkX , a_ChunkY , a_ChunkZ ) ) ;
2011-12-25 07:55:20 -05:00
}
2012-01-29 09:29:26 -05:00
2012-02-13 16:47:03 -05:00
m_Event . Set ( ) ;
2011-12-25 07:55:20 -05:00
}
2012-01-29 09:29:26 -05:00
2012-05-25 03:18:52 -04: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 12:53:22 -05:00
void cChunkGenerator : : WaitForQueueEmpty ( void )
{
cCSLock Lock ( m_CS ) ;
2012-03-10 12:37:00 -05:00
while ( ! m_ShouldTerminate & & ! m_Queue . empty ( ) )
2012-02-18 12:53:22 -05:00
{
cCSUnlock Unlock ( Lock ) ;
m_evtRemoved . Wait ( ) ;
}
}
int cChunkGenerator : : GetQueueLength ( void )
{
cCSLock Lock ( m_CS ) ;
return ( int ) m_Queue . size ( ) ;
}
2012-05-25 03:18:52 -04: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 16:47:03 -05:00
void cChunkGenerator : : Execute ( void )
2011-12-25 07:55:20 -05:00
{
2012-03-10 12:37:00 -05:00
while ( ! m_ShouldTerminate )
2011-12-25 07:55:20 -05:00
{
2012-02-13 16:47:03 -05:00
cCSLock Lock ( m_CS ) ;
while ( m_Queue . size ( ) = = 0 )
2011-12-25 07:55:20 -05:00
{
2012-01-29 09:29:26 -05:00
cCSUnlock Unlock ( Lock ) ;
2012-02-13 16:47:03 -05:00
m_Event . Wait ( ) ;
2012-03-10 12:37:00 -05:00
if ( m_ShouldTerminate )
2012-02-13 16:47:03 -05:00
{
return ;
}
2011-12-25 07:55:20 -05:00
}
2012-01-29 09:29:26 -05:00
2012-02-13 16:47:03 -05: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 09:29:26 -05:00
Lock . Unlock ( ) ; // Unlock ASAP
2012-02-18 12:53:22 -05:00
m_evtRemoved . Set ( ) ;
2011-12-25 07:55:20 -05:00
2012-04-10 07:22:11 -04: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 16:47:03 -05:00
{
2012-04-10 07:22:11 -04:00
LOGD ( " Chunk [%d, %d] already generated, skipping generation " , coords . m_ChunkX , coords . m_ChunkZ ) ;
2012-02-28 05:01:42 -05:00
// Already generated, ignore request
continue ;
}
if ( SkipEnabled & & ! m_World - > HasChunkAnyClients ( coords . m_ChunkX , coords . m_ChunkY , coords . m_ChunkZ ) )
{
2012-04-10 07:22:11 -04:00
LOGWARNING ( " Chunk generator overloaded, skipping chunk [%d, %d, %d] " , coords . m_ChunkX , coords . m_ChunkY , coords . m_ChunkZ ) ;
2011-12-25 07:55:20 -05:00
continue ;
}
2012-02-16 12:45:26 -05:00
2012-02-18 12:53:22 -05:00
LOGD ( " Generating chunk [%d, %d, %d] " , coords . m_ChunkX , coords . m_ChunkY , coords . m_ChunkZ ) ;
2012-02-17 12:56:25 -05:00
DoGenerate ( coords . m_ChunkX , coords . m_ChunkY , coords . m_ChunkZ ) ;
2012-02-13 16:47:03 -05:00
2012-02-16 12:45:26 -05:00
// Save the chunk right after generating, so that we don't have to generate it again on next run
2012-02-17 12:56:25 -05:00
m_World - > GetStorage ( ) . QueueSaveChunk ( coords . m_ChunkX , coords . m_ChunkY , coords . m_ChunkZ ) ;
2012-01-29 09:29:26 -05:00
} // while (!bStop)
2011-12-25 07:55:20 -05:00
}
2012-01-29 09:29:26 -05:00
2012-02-17 12:56:25 -05:00
void cChunkGenerator : : DoGenerate ( int a_ChunkX , int a_ChunkY , int a_ChunkZ )
{
2012-05-25 03:18:52 -04:00
cChunkDef : : BiomeMap BiomeMap ;
cChunkDef : : BlockTypes BlockTypes ;
cChunkDef : : BlockNibbles BlockMeta ;
cChunkDef : : HeightMap HeightMap ;
2012-02-18 12:53:22 -05:00
cEntityList Entities ;
cBlockEntityList BlockEntities ;
2012-03-14 16:56:09 -04:00
2012-05-25 03:18:52 -04:00
// Use the composed generator:
m_BiomeGen - > GenBiomes ( a_ChunkX , a_ChunkZ , BiomeMap ) ;
m_HeightGen - > GenHeightMap ( a_ChunkX , a_ChunkZ , HeightMap ) ;
m_CompositionGen - > ComposeTerrain ( a_ChunkX , a_ChunkZ , BlockTypes , BlockMeta , HeightMap , Entities , BlockEntities ) ;
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 16:56:09 -04:00
a_ChunkX , a_ChunkY , a_ChunkZ ,
2012-05-25 03:18:52 -04:00
BlockTypes , BlockMeta ,
NULL , NULL , // We don't have lighting, chunk will be lighted when needed
& HeightMap , & BiomeMap ,
Entities , BlockEntities ,
true
2012-03-14 16:56:09 -04:00
) ;
2012-02-17 12:56:25 -05:00
}