1
0
Fork 0
cuberite-2a/src/Generating/FinishGen.cpp

2055 lines
56 KiB
C++
Raw Normal View History

// FinishGen.cpp
/* Implements the various finishing generators:
- cFinishGenSnow
- cFinishGenIce
- cFinishGenSprinkleFoliage
*/
#include "Globals.h"
#include "FinishGen.h"
#include "../BlockID.h"
#include "../Simulator/FluidSimulator.h" // for cFluidSimulator::CanWashAway()
#include "../Simulator/FireSimulator.h"
#include "../World.h"
2014-10-23 13:15:10 +00:00
#include "../IniFile.h"
#define DEF_NETHER_WATER_SPRINGS "0, 1; 255, 1"
#define DEF_NETHER_LAVA_SPRINGS "0, 0; 30, 0; 31, 50; 120, 50; 127, 0"
#define DEF_OVERWORLD_WATER_SPRINGS "0, 0; 10, 10; 11, 75; 16, 83; 20, 83; 24, 78; 32, 62; 40, 40; 44, 15; 48, 7; 56, 2; 64, 1; 255, 0"
#define DEF_OVERWORLD_LAVA_SPRINGS "0, 0; 10, 5; 11, 45; 48, 2; 64, 1; 255, 0"
#define DEF_END_WATER_SPRINGS "0, 1; 255, 1"
#define DEF_END_LAVA_SPRINGS "0, 1; 255, 1"
2014-12-02 09:37:17 +00:00
#define DEF_ANIMAL_SPAWN_PERCENT 10
#define DEF_NO_ANIMALS 0
static inline bool IsWater(BLOCKTYPE a_BlockType)
{
return (a_BlockType == E_BLOCK_STATIONARY_WATER) || (a_BlockType == E_BLOCK_WATER);
}
////////////////////////////////////////////////////////////////////////////////
// cFinishGenNetherClumpFoliage:
2014-01-10 15:31:45 +00:00
void cFinishGenNetherClumpFoliage::GenFinish(cChunkDesc & a_ChunkDesc)
2014-01-10 15:31:45 +00:00
{
int ChunkX = a_ChunkDesc.GetChunkX();
int ChunkZ = a_ChunkDesc.GetChunkZ();
2014-01-17 23:27:13 +00:00
int Val1 = m_Noise.IntNoise2DInt(ChunkX ^ ChunkZ, ChunkZ + ChunkX);
int Val2 = m_Noise.IntNoise2DInt(ChunkZ ^ ChunkX, ChunkZ - ChunkX);
2014-01-17 23:27:13 +00:00
int PosX = Val1 % 16;
int PosZ = Val2 % 16;
2014-01-17 23:27:13 +00:00
for (int y = 1; y < cChunkDef::Height; y++)
{
2014-01-17 23:27:13 +00:00
if (a_ChunkDesc.GetBlockType(PosX, y, PosZ) != E_BLOCK_AIR)
{
continue;
}
2014-03-01 19:34:19 +00:00
if (!cBlockInfo::IsSolid(a_ChunkDesc.GetBlockType(PosX, y - 1, PosZ))) // Only place on solid blocks
{
continue;
}
2014-12-03 08:26:15 +00:00
// Choose what block to use.
2015-05-24 11:56:56 +00:00
NOISE_DATATYPE BlockType = m_Noise.IntNoise3D(static_cast<int>(ChunkX), y, static_cast<int>(ChunkZ));
if (BlockType < -0.7)
{
TryPlaceClump(a_ChunkDesc, PosX, y, PosZ, E_BLOCK_BROWN_MUSHROOM);
}
else if (BlockType < 0)
{
TryPlaceClump(a_ChunkDesc, PosX, y, PosZ, E_BLOCK_RED_MUSHROOM);
}
else if (BlockType < 0.7)
{
TryPlaceClump(a_ChunkDesc, PosX, y, PosZ, E_BLOCK_FIRE);
}
}
}
2014-01-10 15:31:45 +00:00
2014-01-18 13:17:26 +00:00
void cFinishGenNetherClumpFoliage::TryPlaceClump(cChunkDesc & a_ChunkDesc, int a_RelX, int a_RelY, int a_RelZ, BLOCKTYPE a_Block)
{
bool IsFireBlock = a_Block == E_BLOCK_FIRE;
2014-07-27 09:59:54 +00:00
int MinX = a_RelX - 4;
if (MinX < 0) // Check if the coordinate is outside the chunk. If it it then adjust it.
{
MinX = 0;
}
int MaxX = a_RelX + 4;
if (MaxX > cChunkDef::Width) // Check if the coordinate is outside the chunk. If it it then adjust it.
{
MaxX = cChunkDef::Width;
}
int MinZ = a_RelZ - 4;
if (MinZ < 0) // Check if the coordinate is outside the chunk. If it it then adjust it.
{
MinZ = 0;
}
int MaxZ = a_RelZ + 4;
if (MaxZ > cChunkDef::Width) // Check if the coordinate is outside the chunk. If it it then adjust it.
{
MaxZ = cChunkDef::Width;
}
int MinY = a_RelY - 2;
if (MinY < 0) // Check if the coordinate is outside the chunk. If it it then adjust it.
{
MinY = 0;
}
int MaxY = a_RelY + 2;
if (MaxY > cChunkDef::Height) // Check if the coordinate is outside the chunk. If it it then adjust it.
{
MaxY = cChunkDef::Height;
}
for (int x = MinX; x < MaxX; x++)
2014-01-10 15:31:45 +00:00
{
int xx = a_ChunkDesc.GetChunkX() * cChunkDef::Width + x;
2014-07-27 09:59:54 +00:00
for (int z = MinZ; z < MaxZ; z++)
2014-01-10 15:31:45 +00:00
{
int zz = a_ChunkDesc.GetChunkZ() * cChunkDef::Width + z;
2014-07-27 09:59:54 +00:00
for (int y = MinY; y < MaxY; y++)
2014-01-10 15:31:45 +00:00
{
2014-06-04 19:01:05 +00:00
if (
((x < 0) || (x >= cChunkDef::Width)) ||
((y < 0) || (y >= cChunkDef::Height)) ||
((z < 0) || (z >= cChunkDef::Width))
)
{
continue;
}
if (a_ChunkDesc.GetBlockType(x, y, z) != E_BLOCK_AIR) // Don't replace non air blocks.
2014-01-10 15:31:45 +00:00
{
continue;
}
BLOCKTYPE BlockBelow = a_ChunkDesc.GetBlockType(x, y - 1, z);
if (!cBlockInfo::FullyOccupiesVoxel(BlockBelow)) // Only place on solid blocks
2014-01-10 15:31:45 +00:00
{
continue;
}
if (IsFireBlock) // don't place fire on non-forever burning blocks.
{
if (!cFireSimulator::DoesBurnForever(BlockBelow))
{
continue;
}
}
NOISE_DATATYPE Val = m_Noise.IntNoise2D(xx, zz);
if (Val < -0.5)
2014-01-10 15:31:45 +00:00
{
2014-01-17 23:27:13 +00:00
a_ChunkDesc.SetBlockType(x, y, z, a_Block);
2014-01-10 15:31:45 +00:00
}
}
}
}
}
2014-01-17 23:27:13 +00:00
2016-03-05 22:56:08 +00:00
////////////////////////////////////////////////////////////////////////////////
// cFinishGenClumpTopBlock
void cFinishGenClumpTopBlock::GenFinish(cChunkDesc & a_ChunkDesc)
{
int ChunkX = a_ChunkDesc.GetChunkX();
int ChunkZ = a_ChunkDesc.GetChunkZ();
int NoiseVal = m_Noise.IntNoise2DInt(ChunkX, ChunkZ);
EMCSBiome Biome = a_ChunkDesc.GetBiome(cChunkDef::Width / 2, cChunkDef::Width / 2);
BiomeInfo info = m_FlowersPerBiome[static_cast<size_t>(Biome)];
const auto & PossibleBlocks = info.m_Blocks;
if (PossibleBlocks.empty())
{
// No need to go any further. This biome can't generate any blocks.
return;
}
int NumClumps = info.m_MaxNumClumpsPerChunk - info.m_MinNumClumpsPerChunk;
if (NumClumps == 0)
{
NumClumps = 1;
}
NumClumps = NoiseVal % NumClumps + info.m_MinNumClumpsPerChunk;
for (int i = 0; i < NumClumps; i++)
{
int Val1 = m_Noise.IntNoise2DInt(ChunkX * ChunkZ * i, ChunkZ + ChunkX + i);
int Val2 = m_Noise.IntNoise2DInt(ChunkZ * ChunkX + i, ChunkZ - ChunkX * i);
int BlockVal = m_Noise.IntNoise2DInt(Val1, Val2);
int PosX = Val1 % (cChunkDef::Width - RANGE_FROM_CENTER * 2) + RANGE_FROM_CENTER;
int PosZ = Val2 % (cChunkDef::Width - RANGE_FROM_CENTER * 2) + RANGE_FROM_CENTER;
int TotalWeight = 0;
for (const auto & Block : PossibleBlocks)
{
TotalWeight += Block.m_Weight;
}
// Prevent division by 0
TotalWeight = (TotalWeight != 0) ? TotalWeight : 1;
int Weight = BlockVal % TotalWeight;
for (const auto & Block : PossibleBlocks)
{
Weight -= Block.m_Weight;
if (Weight < 0)
{
TryPlaceFoliageClump(a_ChunkDesc, PosX, PosZ, Block.m_BlockType, Block.m_BlockMeta, Block.m_BlockType == E_BLOCK_BIG_FLOWER);
break;
}
}
}
}
void cFinishGenClumpTopBlock::TryPlaceFoliageClump(cChunkDesc & a_ChunkDesc, int a_CenterX, int a_CenterZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta, bool a_IsDoubleTall)
{
int ChunkX = a_ChunkDesc.GetChunkX();
int ChunkZ = a_ChunkDesc.GetChunkZ();
int NumBlocks = m_Noise.IntNoise2DInt(a_CenterX + ChunkX * 16, a_CenterZ + ChunkZ * 16) % (MAX_NUM_FOLIAGE - MIN_NUM_FOLIAGE) + MIN_NUM_FOLIAGE + 1;
for (int i = 1; i < NumBlocks; i++)
{
int rnd = m_Noise.IntNoise2DInt(ChunkX + ChunkZ + i, ChunkX - ChunkZ - i) / 59;
int x = a_CenterX + (((rnd % 256) % RANGE_FROM_CENTER * 2) - RANGE_FROM_CENTER);
int z = a_CenterZ + (((rnd / 256) % RANGE_FROM_CENTER * 2) - RANGE_FROM_CENTER);
int Top = a_ChunkDesc.GetHeight(x, z);
if (a_ChunkDesc.GetBlockType(x, Top, z) != E_BLOCK_GRASS)
{
continue;
}
a_ChunkDesc.SetBlockTypeMeta(x, Top + 1, z, a_BlockType, a_BlockMeta);
if (a_IsDoubleTall)
{
a_ChunkDesc.SetBlockTypeMeta(x, Top + 2, z, E_BLOCK_BIG_FLOWER, 8);
}
}
}
void cFinishGenClumpTopBlock::ParseConfigurationString(AString a_RawClumpInfo, std::vector<BiomeInfo> & a_Output)
{
// Initialize the vector for all biomes.
for (int i = static_cast<int>(a_Output.size()); i < static_cast<int>(biMaxVariantBiome); i++)
{
a_Output.push_back(BiomeInfo());
}
AStringVector ClumpInfo = StringSplitAndTrim(a_RawClumpInfo, "=");
// Information about a clump is divided in 2 parts. The biomes they can be in and the blocks that can be placed.
if (ClumpInfo.size() != 2)
{
LOGWARNING("OverworldClumpFoliage: Data missing for \"%s\". Please divide biome and blocks with a semi colon", a_RawClumpInfo.c_str());
return;
}
AStringVector Biomes = StringSplitAndTrim(ClumpInfo[0], ";");
AStringVector Blocks = StringSplitAndTrim(ClumpInfo[1], ";");
for (const auto & RawBiomeInfo : Biomes)
{
AStringVector BiomeInfo = StringSplitAndTrim(RawBiomeInfo, ",");
AString BiomeName = BiomeInfo[0];
EMCSBiome Biome = StringToBiome(BiomeName);
if (Biome == biInvalidBiome)
{
LOGWARNING("Biome \"%s\" is invalid.", BiomeName.c_str());
continue;
}
if (BiomeInfo.size() == 2)
{
// Only the minimum amount of clumps per chunk is changed.
int MinNumClump = 1;
if (!StringToInteger(BiomeInfo[1], MinNumClump))
{
LOGWARNING("OverworldClumpFoliage: Invalid data in \"%s\". Second parameter is either not existing or a number", RawBiomeInfo.c_str());
continue;
}
a_Output[static_cast<size_t>(Biome)].m_MinNumClumpsPerChunk = MinNumClump;
// In case the minimum number is higher than the current maximum value we change the max to the minimum value.
a_Output[static_cast<size_t>(Biome)].m_MaxNumClumpsPerChunk = std::max(MinNumClump, a_Output[static_cast<size_t>(Biome)].m_MaxNumClumpsPerChunk);
}
else if (BiomeInfo.size() == 3)
{
// Both the minimum and maximum amount of clumps per chunk is changed.
int MinNumClumps = 0, MaxNumClumps = 1;
if (!StringToInteger(BiomeInfo[1], MinNumClumps) || !StringToInteger(BiomeInfo[2], MaxNumClumps))
{
LOGWARNING("Invalid data in \"%s\". Second parameter is either not existing or a number", RawBiomeInfo.c_str());
continue;
}
a_Output[static_cast<size_t>(Biome)].m_MaxNumClumpsPerChunk = MaxNumClumps + 1;
a_Output[static_cast<size_t>(Biome)].m_MinNumClumpsPerChunk = MinNumClumps;
}
// TODO: Make the weight configurable.
for (const auto & BlockName : Blocks)
{
cItem Block = cItem();
if (!StringToItem(BlockName, Block) && IsValidBlock(Block.m_ItemType))
{
LOGWARNING("Block \"%s\" is invalid", BlockName.c_str());
continue;
}
FoliageInfo info = FoliageInfo(static_cast<BLOCKTYPE>(Block.m_ItemType), static_cast<NIBBLETYPE>(Block.m_ItemDamage), 100);
a_Output[static_cast<size_t>(Biome)].m_Blocks.push_back(info);
}
}
}
std::vector<cFinishGenClumpTopBlock::BiomeInfo> cFinishGenClumpTopBlock::ParseIniFile(cIniFile & a_IniFile, AString a_ClumpPrefix)
{
// Also check dashes in case we will get more configuration options with the same prefix.
a_ClumpPrefix += "-";
std::vector<cFinishGenClumpTopBlock::BiomeInfo> foliage;
int NumGeneratorValues = a_IniFile.GetNumValues("Generator");
int GeneratorKeyId = a_IniFile.FindKey("Generator");
for (int i = 0; i < NumGeneratorValues; i++)
{
AString ValueName = a_IniFile.GetValueName("Generator", i);
if (ValueName.substr(0, a_ClumpPrefix.size()) == a_ClumpPrefix)
{
AString RawClump = a_IniFile.GetValue(GeneratorKeyId, i);
cFinishGenClumpTopBlock::ParseConfigurationString(RawClump, foliage);
}
}
if (foliage.size() == 0)
{
cFinishGenClumpTopBlock::ParseConfigurationString(a_IniFile.GetValueSet("Generator", a_ClumpPrefix + "-1", "Forest, -2, 2; ForestHills, -3, 2; FlowerForest = yellowflower, redflower, lilac, rosebush"), foliage);
cFinishGenClumpTopBlock::ParseConfigurationString(a_IniFile.GetValueSet("Generator", a_ClumpPrefix + "-2", "Plains, -2, 1; SunflowerPlains = yellowflower, redflower, azurebluet, oxeyedaisy"), foliage);
cFinishGenClumpTopBlock::ParseConfigurationString(a_IniFile.GetValueSet("Generator", a_ClumpPrefix + "-3", "SunflowerPlains, 1, 2 = sunflower"), foliage);
cFinishGenClumpTopBlock::ParseConfigurationString(a_IniFile.GetValueSet("Generator", a_ClumpPrefix + "-4", "FlowerForest, 2, 5 = allium, redtulip, orangetulip, whitetulip, pinktulip, oxeyedaisy"), foliage);
cFinishGenClumpTopBlock::ParseConfigurationString(a_IniFile.GetValueSet("Generator", a_ClumpPrefix + "-5", "Swampland, SwamplandM = brownmushroom, redmushroom, blueorchid"), foliage);
}
return foliage;
}
2015-04-19 17:45:18 +00:00
////////////////////////////////////////////////////////////////////////////////
// cFinishGenGlowStone:
void cFinishGenGlowStone::GenFinish(cChunkDesc & a_ChunkDesc)
{
int ChunkX = a_ChunkDesc.GetChunkX();
int ChunkZ = a_ChunkDesc.GetChunkZ();
2016-02-05 21:45:45 +00:00
2015-04-19 17:45:18 +00:00
// Change the number of attempts to create a vein depending on the maximum height of the chunk. A standard Nether could have 5 veins at most.
int NumGlowStone = m_Noise.IntNoise2DInt(ChunkX, ChunkZ) % a_ChunkDesc.GetMaxHeight() / 23;
for (int i = 1; i <= NumGlowStone; i++)
{
// The maximum size for a string of glowstone can get 3 - 5 blocks long
int Size = 3 + m_Noise.IntNoise3DInt(ChunkX, i, ChunkZ) % 3;
// Generate X / Z coordinates.
2015-04-19 17:45:18 +00:00
int X = Size + (m_Noise.IntNoise2DInt(i, Size) % (cChunkDef::Width - Size * 2));
int Z = Size + (m_Noise.IntNoise2DInt(X, i) % (cChunkDef::Width - Size * 2));
2016-02-05 21:45:45 +00:00
2015-04-19 17:45:18 +00:00
int Height = a_ChunkDesc.GetHeight(X, Z);
for (int y = Height; y > Size; y--)
{
if (!cBlockInfo::IsSolid(a_ChunkDesc.GetBlockType(X, y, Z)))
{
// Current block isn't solid, bail out
continue;
}
if (a_ChunkDesc.GetBlockType(X, y - 1, Z) != E_BLOCK_AIR)
{
// The block below isn't air, bail out
continue;
}
if ((m_Noise.IntNoise3DInt(X, y, Z) % 100) < 95)
{
// Have a 5% chance of creating the glowstone
continue;
}
TryPlaceGlowstone(a_ChunkDesc, X, y, Z, Size, 5 + m_Noise.IntNoise3DInt(X, y, Z) % 7);
break;
}
}
}
void cFinishGenGlowStone::TryPlaceGlowstone(cChunkDesc & a_ChunkDesc, int a_RelX, int a_RelY, int a_RelZ, int a_Size, int a_NumStrings)
{
// The starting point of every glowstone string
Vector3i StartPoint = Vector3i(a_RelX, a_RelY, a_RelZ);
2016-02-05 21:45:45 +00:00
2015-04-19 17:45:18 +00:00
// Array with possible directions for a string of glowstone to go to.
const Vector3i AvailableDirections[] =
{
{ -1, 0, 0 }, { 1, 0, 0 },
2015-04-19 17:59:27 +00:00
{ 0, -1, 0 }, // Don't let the glowstone go up
2015-04-19 17:45:18 +00:00
{ 0, 0, -1 }, { 0, 0, 1 },
2016-02-05 21:45:45 +00:00
2015-04-19 17:45:18 +00:00
// Diagonal direction. Only X or Z with Y.
// If all were changed the glowstone string looks awkward
{ 0, -1, 1 }, { 1, -1, 0 },
{ 0, -1, -1 }, { -1, -1, 0 },
};
for (int i = 1; i <= a_NumStrings; i++)
{
// The current position of the string that is being generated
Vector3i CurrentPos = Vector3i(StartPoint);
// A vector where the previous direction of a glowstone string is stored.
// This is used to make the strings change direction when going one block further
Vector3i PreviousDirection = Vector3i();
for (int j = 0; j < a_Size; j++)
{
2015-05-19 10:50:59 +00:00
Vector3i Direction = AvailableDirections[static_cast<size_t>(m_Noise.IntNoise3DInt(CurrentPos.x, CurrentPos.y * i, CurrentPos.z)) % ARRAYCOUNT(AvailableDirections)];
2015-04-19 17:59:27 +00:00
int Attempts = 2; // multiply by 1 would make no difference, so multiply by 2 instead
2015-04-19 17:45:18 +00:00
while (Direction.Equals(PreviousDirection))
{
// To make the glowstone branches look better we want to make the direction change every time.
2015-05-19 10:50:59 +00:00
Direction = AvailableDirections[static_cast<size_t>(m_Noise.IntNoise3DInt(CurrentPos.x, CurrentPos.y * i * Attempts, CurrentPos.z)) % ARRAYCOUNT(AvailableDirections)];
2015-04-19 17:45:18 +00:00
Attempts++;
}
// Update the previous direction variable
PreviousDirection = Direction;
// Update the position of the glowstone string
CurrentPos += Direction;
if (cBlockInfo::IsSolid(a_ChunkDesc.GetBlockType(CurrentPos.x, CurrentPos.y, CurrentPos.z)) && (a_ChunkDesc.GetBlockType(CurrentPos.x, CurrentPos.y, CurrentPos.z) != E_BLOCK_GLOWSTONE))
{
// The glowstone hit something solid, and it wasn't glowstone. Stop the string.
break;
}
// Place a glowstone block.
a_ChunkDesc.SetBlockType(CurrentPos.x, CurrentPos.y, CurrentPos.z, E_BLOCK_GLOWSTONE);
}
}
}
2014-07-21 16:18:54 +00:00
////////////////////////////////////////////////////////////////////////////////
// cFinishGenTallGrass:
void cFinishGenTallGrass::GenFinish(cChunkDesc & a_ChunkDesc)
{
for (int x = 0; x < cChunkDef::Width; x++)
{
int xx = x + a_ChunkDesc.GetChunkX() * cChunkDef::Width;
for (int z = 0; z < cChunkDef::Width; z++)
{
int zz = z + a_ChunkDesc.GetChunkZ() * cChunkDef::Width;
int BiomeDensity = GetBiomeDensity(a_ChunkDesc.GetBiome(x, z));
2014-07-21 16:18:54 +00:00
// Choose if we want to place long grass here. If not then bail out:
if ((m_Noise.IntNoise2DInt(xx + m_Noise.IntNoise1DInt(xx), zz + m_Noise.IntNoise1DInt(zz)) / 7 % 100) > BiomeDensity)
{
continue;
}
2014-12-03 08:26:15 +00:00
2014-07-21 16:18:54 +00:00
// Get the top block + 1. This is the place where the grass would finaly be placed:
int y = a_ChunkDesc.GetHeight(x, z) + 1;
2014-12-03 08:26:15 +00:00
if (y >= cChunkDef::Height - 1)
{
continue;
}
2014-07-21 16:18:54 +00:00
// Check if long grass can be placed:
if (
(a_ChunkDesc.GetBlockType(x, y, z) != E_BLOCK_AIR) ||
((a_ChunkDesc.GetBlockType(x, y - 1, z) != E_BLOCK_GRASS) && (a_ChunkDesc.GetBlockType(x, y - 1, z) != E_BLOCK_DIRT))
)
{
continue;
}
2014-07-21 16:18:54 +00:00
// Choose what long grass meta we should use:
int GrassType = m_Noise.IntNoise2DInt(xx * 50, zz * 50) / 7 % 100;
if (GrassType < 60)
{
a_ChunkDesc.SetBlockTypeMeta(x, y, z, E_BLOCK_TALL_GRASS, 1);
}
else if (GrassType < 90)
{
a_ChunkDesc.SetBlockTypeMeta(x, y, z, E_BLOCK_TALL_GRASS, 2);
}
else if (!IsBiomeVeryCold(a_ChunkDesc.GetBiome(x, z)))
{
2014-07-21 16:18:54 +00:00
// If double long grass we have to choose what type we should use:
if (a_ChunkDesc.GetBlockType(x, y + 1, z) == E_BLOCK_AIR)
{
NIBBLETYPE Meta = (m_Noise.IntNoise2DInt(xx * 100, zz * 100) / 7 % 100) > 25 ? 2 : 3;
a_ChunkDesc.SetBlockTypeMeta(x, y, z, E_BLOCK_BIG_FLOWER, Meta);
a_ChunkDesc.SetBlockTypeMeta(x, y + 1, z, E_BLOCK_BIG_FLOWER, 8);
}
}
else
{
NIBBLETYPE meta = (m_Noise.IntNoise2DInt(xx * 50, zz * 50) / 7 % 2) + 1;
a_ChunkDesc.SetBlockTypeMeta(x, y, z, E_BLOCK_TALL_GRASS, meta);
}
}
}
}
2014-07-21 14:34:35 +00:00
////////////////////////////////////////////////////////////////////////////////
// cFinishGenVines
bool cFinishGenVines::IsJungleVariant(EMCSBiome a_Biome)
{
switch (a_Biome)
{
case biJungle:
case biJungleEdge:
case biJungleEdgeM:
case biJungleHills:
case biJungleM:
{
return true;
}
}
return false;
}
void cFinishGenVines::GenFinish(cChunkDesc & a_ChunkDesc)
{
for (int x = 0; x < cChunkDef::Width; x++)
{
int xx = x + a_ChunkDesc.GetChunkX() * cChunkDef::Width;
for (int z = 0; z < cChunkDef::Width; z++)
{
int zz = z + a_ChunkDesc.GetChunkZ() * cChunkDef::Width;
if (!IsJungleVariant(a_ChunkDesc.GetBiome(x, z)))
{
// Current biome isn't a jungle
continue;
}
2015-03-01 19:08:05 +00:00
if ((m_Noise.IntNoise2DInt(xx, zz) % 101) < 50)
{
continue;
}
int Height = a_ChunkDesc.GetHeight(x, z);
for (int y = Height; y > m_Level; y--)
{
if (a_ChunkDesc.GetBlockType(x, y, z) != E_BLOCK_AIR)
{
// Can't place vines in non-air blocks
continue;
}
2015-03-01 19:08:05 +00:00
if ((m_Noise.IntNoise3DInt(xx, y, zz) % 101) < 50)
{
continue;
}
std::vector<NIBBLETYPE> Places;
if ((x + 1 < cChunkDef::Width) && cBlockInfo::FullyOccupiesVoxel(a_ChunkDesc.GetBlockType(x + 1, y, z)))
{
Places.push_back(8);
}
if ((x - 1 > 0) && cBlockInfo::FullyOccupiesVoxel(a_ChunkDesc.GetBlockType(x - 1, y, z)))
{
Places.push_back(2);
}
if ((z + 1 < cChunkDef::Width) && cBlockInfo::FullyOccupiesVoxel(a_ChunkDesc.GetBlockType(x, y, z + 1)))
{
Places.push_back(1);
}
if ((z - 1 > 0) && cBlockInfo::FullyOccupiesVoxel(a_ChunkDesc.GetBlockType(x, y, z - 1)))
{
Places.push_back(4);
}
if (Places.size() == 0)
{
continue;
}
2015-05-19 10:50:59 +00:00
NIBBLETYPE Meta = Places[static_cast<size_t>(m_Noise.IntNoise3DInt(xx, y, zz)) % Places.size()];
a_ChunkDesc.SetBlockTypeMeta(x, y, z, E_BLOCK_VINES, Meta);
}
}
}
}
////////////////////////////////////////////////////////////////////////////////
// cFinishGenSprinkleFoliage:
bool cFinishGenSprinkleFoliage::TryAddSugarcane(cChunkDesc & a_ChunkDesc, int a_RelX, int a_RelY, int a_RelZ)
{
// We'll be doing comparison to neighbors, so require the coords to be 1 block away from the chunk edges:
if (
(a_RelX < 1) || (a_RelX >= cChunkDef::Width - 1) ||
(a_RelY < 1) || (a_RelY >= cChunkDef::Height - 2) ||
(a_RelZ < 1) || (a_RelZ >= cChunkDef::Width - 1)
)
{
return false;
}
// Only allow dirt, grass or sand below sugarcane:
switch (a_ChunkDesc.GetBlockType(a_RelX, a_RelY, a_RelZ))
{
case E_BLOCK_DIRT:
case E_BLOCK_GRASS:
case E_BLOCK_SAND:
{
break;
}
default:
{
return false;
}
}
// Water is required next to the block below the sugarcane:
if (
!IsWater(a_ChunkDesc.GetBlockType(a_RelX - 1, a_RelY, a_RelZ)) &&
!IsWater(a_ChunkDesc.GetBlockType(a_RelX + 1, a_RelY, a_RelZ)) &&
2014-07-18 07:57:34 +00:00
!IsWater(a_ChunkDesc.GetBlockType(a_RelX, a_RelY, a_RelZ - 1)) &&
!IsWater(a_ChunkDesc.GetBlockType(a_RelX, a_RelY, a_RelZ + 1))
)
{
return false;
}
2014-12-03 08:26:15 +00:00
// All conditions met, place a sugarcane here:
a_ChunkDesc.SetBlockType(a_RelX, a_RelY + 1, a_RelZ, E_BLOCK_SUGARCANE);
return true;
}
void cFinishGenSprinkleFoliage::GenFinish(cChunkDesc & a_ChunkDesc)
{
// Generate small foliage (1-block):
2014-12-03 08:26:15 +00:00
// TODO: Update heightmap with 1-block-tall foliage
2014-07-17 20:50:58 +00:00
for (int z = 0; z < cChunkDef::Width; z++)
{
int BlockZ = a_ChunkDesc.GetChunkZ() * cChunkDef::Width + z;
2015-05-24 11:56:56 +00:00
const float zz = static_cast<float>(BlockZ);
for (int x = 0; x < cChunkDef::Width; x++)
{
int BlockX = a_ChunkDesc.GetChunkX() * cChunkDef::Width + x;
if (((m_Noise.IntNoise2DInt(BlockX, BlockZ) / 8) % 128) < 124)
{
continue;
}
2015-05-19 10:50:59 +00:00
HEIGHTTYPE Top = a_ChunkDesc.GetHeight(x, z);
if (Top > 250)
{
// Nothing grows above Y=250
continue;
}
if (a_ChunkDesc.GetBlockType(x, Top + 1, z) != E_BLOCK_AIR)
{
// Space already taken by something else, don't grow here
// WEIRD, since we're using heightmap, so there should NOT be anything above it
continue;
}
2014-12-03 08:26:15 +00:00
2015-05-24 11:56:56 +00:00
const float xx = static_cast<float>(BlockX);
float val1 = m_Noise.CubicNoise2D(xx * 0.1f, zz * 0.1f);
float val2 = m_Noise.CubicNoise2D(xx * 0.01f, zz * 0.01f);
switch (a_ChunkDesc.GetBlockType(x, Top, z))
{
case E_BLOCK_GRASS:
{
float val3 = m_Noise.CubicNoise2D(xx * 0.01f + 10, zz * 0.01f + 10);
float val4 = m_Noise.CubicNoise2D(xx * 0.05f + 20, zz * 0.05f + 20);
if (val1 + val2 > 0.2f)
{
a_ChunkDesc.SetBlockType(x, ++Top, z, E_BLOCK_YELLOW_FLOWER);
}
else if (val2 + val3 > 0.2f)
{
a_ChunkDesc.SetBlockType(x, ++Top, z, E_BLOCK_RED_ROSE);
}
else if (val3 + val4 > 0.2f)
{
a_ChunkDesc.SetBlockType(x, ++Top, z, E_BLOCK_RED_MUSHROOM);
}
else if (val1 + val4 > 0.2f)
{
a_ChunkDesc.SetBlockType(x, ++Top, z, E_BLOCK_BROWN_MUSHROOM);
}
else if (val1 + val2 + val3 + val4 < -0.1)
{
a_ChunkDesc.SetBlockTypeMeta(x, ++Top, z, E_BLOCK_TALL_GRASS, E_META_TALL_GRASS_GRASS);
}
else if (TryAddSugarcane(a_ChunkDesc, x, Top, z))
{
++Top;
}
else if ((val1 > 0.5) && (val2 < -0.5))
{
2015-05-24 11:56:56 +00:00
a_ChunkDesc.SetBlockTypeMeta(x, ++Top, z, E_BLOCK_PUMPKIN, static_cast<int>(val3 * 8) % 4);
}
break;
} // case E_BLOCK_GRASS
2014-12-03 08:26:15 +00:00
case E_BLOCK_SAND:
{
int y = Top + 1;
if (
(x > 0) && (x < cChunkDef::Width - 1) &&
(z > 0) && (z < cChunkDef::Width - 1) &&
(val1 + val2 > 0.5f) &&
(a_ChunkDesc.GetBlockType(x + 1, y, z) == E_BLOCK_AIR) &&
(a_ChunkDesc.GetBlockType(x - 1, y, z) == E_BLOCK_AIR) &&
(a_ChunkDesc.GetBlockType(x, y, z + 1) == E_BLOCK_AIR) &&
(a_ChunkDesc.GetBlockType(x, y, z - 1) == E_BLOCK_AIR) &&
2014-12-02 01:47:36 +00:00
IsDesertVariant(a_ChunkDesc.GetBiome(x, z))
)
{
a_ChunkDesc.SetBlockType(x, ++Top, z, E_BLOCK_CACTUS);
}
else if (TryAddSugarcane(a_ChunkDesc, x, Top, z))
{
++Top;
}
break;
}
} // switch (TopBlock)
a_ChunkDesc.SetHeight(x, z, Top);
} // for y
} // for z
}
2014-12-02 01:28:46 +00:00
bool cFinishGenSprinkleFoliage::IsDesertVariant(EMCSBiome a_Biome)
{
2014-12-02 01:47:36 +00:00
return
(
(a_Biome == biDesertHills) ||
(a_Biome == biDesert) ||
(a_Biome == biDesertM)
);
}
////////////////////////////////////////////////////////////////////////////////
// cFinishGenSoulsandRims
void cFinishGenSoulsandRims::GenFinish(cChunkDesc & a_ChunkDesc)
{
int ChunkX = a_ChunkDesc.GetChunkX() * cChunkDef::Width;
int ChunkZ = a_ChunkDesc.GetChunkZ() * cChunkDef::Width;
HEIGHTTYPE MaxHeight = a_ChunkDesc.GetMaxHeight();
2014-12-02 09:58:30 +00:00
for (int x = 0; x < 16; x++)
{
int xx = ChunkX + x;
for (int z = 0; z < 16; z++)
{
int zz = ChunkZ + z;
// Place soulsand rims when netherrack gets thin
for (int y = 2; y < MaxHeight - 2; y++)
{
// The current block is air. Let's bail ut.
BLOCKTYPE Block = a_ChunkDesc.GetBlockType(x, y, z);
2015-04-19 17:45:18 +00:00
if (Block != E_BLOCK_NETHERRACK)
{
continue;
}
if (
((a_ChunkDesc.GetBlockType(x, y + 1, z) != E_BLOCK_AIR) &&
( a_ChunkDesc.GetBlockType(x, y + 2, z) != E_BLOCK_AIR)) ||
((a_ChunkDesc.GetBlockType(x, y - 1, z) != E_BLOCK_AIR) &&
( a_ChunkDesc.GetBlockType(x, y - 2, z) != E_BLOCK_AIR))
)
{
continue;
}
2015-05-24 11:56:56 +00:00
NOISE_DATATYPE NoiseX = (static_cast<NOISE_DATATYPE>(xx)) / 32;
NOISE_DATATYPE NoiseY = (static_cast<NOISE_DATATYPE>(zz)) / 32;
NOISE_DATATYPE CompBlock = m_Noise.CubicNoise3D(NoiseX, static_cast<float>(y) / 4, NoiseY);
if (CompBlock < 0)
{
a_ChunkDesc.SetBlockType(x, y, z, E_BLOCK_SOULSAND);
}
}
}
}
}
////////////////////////////////////////////////////////////////////////////////
// cFinishGenSnow:
void cFinishGenSnow::GenFinish(cChunkDesc & a_ChunkDesc)
{
// Add a snow block in snowy biomes onto blocks that can be snowed over
for (int z = 0; z < cChunkDef::Width; z++)
{
for (int x = 0; x < cChunkDef::Width; x++)
{
2015-05-19 10:50:59 +00:00
HEIGHTTYPE Height = a_ChunkDesc.GetHeight(x, z);
if (GetSnowStartHeight(a_ChunkDesc.GetBiome(x, z)) > Height)
{
// Height isn't high enough for snow to start forming.
continue;
}
if (!cBlockInfo::IsSnowable(a_ChunkDesc.GetBlockType(x, Height, z)) || (Height >= cChunkDef::Height - 1))
{
// The top block can't be snown over.
continue;
}
a_ChunkDesc.SetBlockType(x, Height + 1, z, E_BLOCK_SNOW);
a_ChunkDesc.SetHeight(x, z, Height + 1);
} // for x
} // for z
}
////////////////////////////////////////////////////////////////////////////////
// cFinishGenIce:
void cFinishGenIce::GenFinish(cChunkDesc & a_ChunkDesc)
{
// Turn surface water into ice in icy biomes
for (int z = 0; z < cChunkDef::Width; z++)
{
for (int x = 0; x < cChunkDef::Width; x++)
{
int Height = a_ChunkDesc.GetHeight(x, z);
if (GetSnowStartHeight(a_ChunkDesc.GetBiome(x, z)) > Height)
{
// Height isn't high enough for snow to start forming.
continue;
}
if (!IsBlockWater(a_ChunkDesc.GetBlockType(x, Height, z)))
{
// The block isn't a water block.
continue;
}
if (a_ChunkDesc.GetBlockMeta(x, Height, z) != 0)
{
// The water block isn't a source block.
continue;
}
a_ChunkDesc.SetBlockType(x, Height, z, E_BLOCK_ICE);
} // for x
} // for z
}
////////////////////////////////////////////////////////////////////////////////
// cFinishGenSingleTopBlock:
int cFinishGenSingleTopBlock::GetNumToGen(const cChunkDef::BiomeMap & a_BiomeMap)
{
int res = 0;
for (size_t i = 0; i < ARRAYCOUNT(a_BiomeMap); i++)
{
if (IsAllowedBiome(a_BiomeMap[i]))
{
res++;
}
} // for i - a_BiomeMap[]
return m_Amount * res / 256;
}
void cFinishGenSingleTopBlock::GenFinish(cChunkDesc & a_ChunkDesc)
{
int NumToGen = GetNumToGen(a_ChunkDesc.GetBiomeMap());
int ChunkX = a_ChunkDesc.GetChunkX();
int ChunkZ = a_ChunkDesc.GetChunkZ();
for (int i = 0; i < NumToGen; i++)
{
int x = (m_Noise.IntNoise3DInt(ChunkX + ChunkZ, ChunkZ, i) / 13) % cChunkDef::Width;
int z = (m_Noise.IntNoise3DInt(ChunkX - ChunkZ, i, ChunkZ) / 11) % cChunkDef::Width;
// Place the block at {x, z} if possible:
EMCSBiome Biome = a_ChunkDesc.GetBiome(x, z);
if (!IsAllowedBiome(Biome))
{
// Incorrect biome
continue;
}
2015-05-19 10:50:59 +00:00
HEIGHTTYPE Height = a_ChunkDesc.GetHeight(x, z);
2014-11-30 17:44:44 +00:00
if (Height >= cChunkDef::Height - 1)
{
// Too high up
continue;
}
if (a_ChunkDesc.GetBlockType(x, Height + 1, z) != E_BLOCK_AIR)
{
// Not an empty block
continue;
}
BLOCKTYPE BlockBelow = a_ChunkDesc.GetBlockType(x, Height, z);
if (!IsAllowedBlockBelow(BlockBelow))
{
continue;
}
a_ChunkDesc.SetBlockType(x, Height + 1, z, m_BlockType);
a_ChunkDesc.SetHeight(x, z, Height + 1);
}
}
////////////////////////////////////////////////////////////////////////////////
// cFinishGenBottomLava:
void cFinishGenBottomLava::GenFinish(cChunkDesc & a_ChunkDesc)
{
cChunkDef::BlockTypes & BlockTypes = a_ChunkDesc.GetBlockTypes();
for (int y = m_Level; y > 0; y--)
{
for (int z = 0; z < cChunkDef::Width; z++) for (int x = 0; x < cChunkDef::Width; x++)
{
int Index = cChunkDef::MakeIndexNoCheck(x, y, z);
if (BlockTypes[Index] == E_BLOCK_AIR)
{
BlockTypes[Index] = E_BLOCK_STATIONARY_LAVA;
}
} // for x, for z
} // for y
}
////////////////////////////////////////////////////////////////////////////////
// cFinishGenPreSimulator:
cFinishGenPreSimulator::cFinishGenPreSimulator(bool a_PreSimulateFallingBlocks, bool a_PreSimulateWater, bool a_PreSimulateLava) :
m_PreSimulateFallingBlocks(a_PreSimulateFallingBlocks),
m_PreSimulateWater(a_PreSimulateWater),
m_PreSimulateLava(a_PreSimulateLava)
{
// Nothing needed yet
}
void cFinishGenPreSimulator::GenFinish(cChunkDesc & a_ChunkDesc)
{
if (m_PreSimulateFallingBlocks)
{
CollapseSandGravel(a_ChunkDesc.GetBlockTypes(), a_ChunkDesc.GetHeightMap());
}
if (m_PreSimulateWater)
{
StationarizeFluid(a_ChunkDesc.GetBlockTypes(), a_ChunkDesc.GetHeightMap(), E_BLOCK_WATER, E_BLOCK_STATIONARY_WATER);
}
if (m_PreSimulateLava)
{
StationarizeFluid(a_ChunkDesc.GetBlockTypes(), a_ChunkDesc.GetHeightMap(), E_BLOCK_LAVA, E_BLOCK_STATIONARY_LAVA);
}
// TODO: other operations
}
void cFinishGenPreSimulator::CollapseSandGravel(
cChunkDef::BlockTypes & a_BlockTypes, // Block types to read and change
cChunkDef::HeightMap & a_HeightMap // Height map to update by the current data
)
{
for (int z = 0; z < cChunkDef::Width; z++)
{
for (int x = 0; x < cChunkDef::Width; x++)
{
int LastY = -1;
int HeightY = 0;
for (int y = 0; y < cChunkDef::Height; y++)
{
BLOCKTYPE Block = cChunkDef::GetBlock(a_BlockTypes, x, y, z);
switch (Block)
{
default:
{
// Set the last block onto which stuff can fall to this height:
LastY = y;
HeightY = y;
break;
}
case E_BLOCK_AIR:
{
// Do nothing
break;
}
case E_BLOCK_FIRE:
case E_BLOCK_WATER:
case E_BLOCK_STATIONARY_WATER:
case E_BLOCK_LAVA:
case E_BLOCK_STATIONARY_LAVA:
{
// Do nothing, only remember this height as potentially highest
HeightY = y;
break;
}
case E_BLOCK_SAND:
case E_BLOCK_GRAVEL:
{
if (LastY < y - 1)
{
cChunkDef::SetBlock(a_BlockTypes, x, LastY + 1, z, Block);
cChunkDef::SetBlock(a_BlockTypes, x, y, z, E_BLOCK_AIR);
}
LastY++;
if (LastY > HeightY)
{
HeightY = LastY;
}
break;
}
} // switch (GetBlock)
} // for y
2015-05-19 10:50:59 +00:00
cChunkDef::SetHeight(a_HeightMap, x, z, static_cast<HEIGHTTYPE>(HeightY));
} // for x
} // for z
}
void cFinishGenPreSimulator::StationarizeFluid(
cChunkDef::BlockTypes & a_BlockTypes, // Block types to read and change
cChunkDef::HeightMap & a_HeightMap, // Height map to read
BLOCKTYPE a_Fluid,
BLOCKTYPE a_StationaryFluid
)
{
// Turn fluid in the middle to stationary, unless it has air or washable block next to it:
for (int z = 1; z < cChunkDef::Width - 1; z++)
{
for (int x = 1; x < cChunkDef::Width - 1; x++)
{
for (int y = cChunkDef::GetHeight(a_HeightMap, x, z); y >= 0; y--)
{
BLOCKTYPE Block = cChunkDef::GetBlock(a_BlockTypes, x, y, z);
if ((Block != a_Fluid) && (Block != a_StationaryFluid))
{
continue;
}
static const struct
{
int x, y, z;
} Coords[] =
{
{1, 0, 0},
{-1, 0, 0},
{0, 0, 1},
{0, 0, -1},
{0, -1, 0}
} ;
BLOCKTYPE BlockToSet = a_StationaryFluid; // By default, don't simulate this block
for (size_t i = 0; i < ARRAYCOUNT(Coords); i++)
{
if ((y == 0) && (Coords[i].y < 0))
{
continue;
}
BLOCKTYPE Neighbor = cChunkDef::GetBlock(a_BlockTypes, x + Coords[i].x, y + Coords[i].y, z + Coords[i].z);
if ((Neighbor == E_BLOCK_AIR) || cFluidSimulator::CanWashAway(Neighbor))
{
// There is an air / washable neighbor, simulate this block
BlockToSet = a_Fluid;
break;
}
} // for i - Coords[]
cChunkDef::SetBlock(a_BlockTypes, x, y, z, BlockToSet);
} // for y
} // for x
} // for z
2014-12-03 08:26:15 +00:00
// Turn fluid at the chunk edges into non-stationary fluid:
for (int y = 0; y < cChunkDef::Height; y++)
{
for (int i = 0; i < cChunkDef::Width; i++) // i stands for both x and z here
{
if (cChunkDef::GetBlock(a_BlockTypes, 0, y, i) == a_StationaryFluid)
{
cChunkDef::SetBlock(a_BlockTypes, 0, y, i, a_Fluid);
}
if (cChunkDef::GetBlock(a_BlockTypes, i, y, 0) == a_StationaryFluid)
{
cChunkDef::SetBlock(a_BlockTypes, i, y, 0, a_Fluid);
}
if (cChunkDef::GetBlock(a_BlockTypes, cChunkDef::Width - 1, y, i) == a_StationaryFluid)
{
cChunkDef::SetBlock(a_BlockTypes, cChunkDef::Width - 1, y, i, a_Fluid);
}
if (cChunkDef::GetBlock(a_BlockTypes, i, y, cChunkDef::Width - 1) == a_StationaryFluid)
{
cChunkDef::SetBlock(a_BlockTypes, i, y, cChunkDef::Width - 1, a_Fluid);
}
}
}
}
////////////////////////////////////////////////////////////////////////////////
// cFinishGenFluidSprings:
cFinishGenFluidSprings::cFinishGenFluidSprings(int a_Seed, BLOCKTYPE a_Fluid, cIniFile & a_IniFile, eDimension a_Dimension) :
m_Noise(a_Seed + a_Fluid * 100), // Need to take fluid into account, otherwise water and lava springs generate next to each other
m_HeightDistribution(cChunkDef::Height - 1),
m_Fluid(a_Fluid)
{
bool IsWater = (a_Fluid == E_BLOCK_WATER);
AString SectionName = IsWater ? "WaterSprings" : "LavaSprings";
AString DefaultHeightDistribution;
2014-01-07 13:24:25 +00:00
int DefaultChance = 0;
switch (a_Dimension)
{
case dimNether:
{
DefaultHeightDistribution = IsWater ? DEF_NETHER_WATER_SPRINGS : DEF_NETHER_LAVA_SPRINGS;
DefaultChance = IsWater ? 0 : 15;
break;
}
case dimOverworld:
{
DefaultHeightDistribution = IsWater ? DEF_OVERWORLD_WATER_SPRINGS : DEF_OVERWORLD_LAVA_SPRINGS;
DefaultChance = IsWater ? 24 : 9;
break;
}
case dimEnd:
{
DefaultHeightDistribution = IsWater ? DEF_END_WATER_SPRINGS : DEF_END_LAVA_SPRINGS;
DefaultChance = 0;
break;
}
default:
{
ASSERT(!"Unhandled world dimension");
break;
}
} // switch (dimension)
AString HeightDistribution = a_IniFile.GetValueSet(SectionName, "HeightDistribution", DefaultHeightDistribution);
if (!m_HeightDistribution.SetDefString(HeightDistribution) || (m_HeightDistribution.GetSum() <= 0))
{
2014-07-17 20:50:58 +00:00
LOGWARNING("[%sSprings]: HeightDistribution is invalid, using the default of \"%s\".",
(a_Fluid == E_BLOCK_WATER) ? "Water" : "Lava",
DefaultHeightDistribution.c_str()
);
m_HeightDistribution.SetDefString(DefaultHeightDistribution);
}
m_Chance = a_IniFile.GetValueSetI(SectionName, "Chance", DefaultChance);
}
void cFinishGenFluidSprings::GenFinish(cChunkDesc & a_ChunkDesc)
{
int ChanceRnd = (m_Noise.IntNoise3DInt(128 * a_ChunkDesc.GetChunkX(), 512, 256 * a_ChunkDesc.GetChunkZ()) / 13) % 100;
if (ChanceRnd > m_Chance)
{
// Not in this chunk
return;
}
2014-12-03 08:26:15 +00:00
// Get the height at which to try:
int Height = m_Noise.IntNoise3DInt(128 * a_ChunkDesc.GetChunkX(), 1024, 256 * a_ChunkDesc.GetChunkZ()) / 11;
Height %= m_HeightDistribution.GetSum();
Height = m_HeightDistribution.MapValue(Height);
2014-12-03 08:26:15 +00:00
// Try adding the spring at the height, if unsuccessful, move lower:
for (int y = Height; y > 1; y--)
{
// TODO: randomize the order in which the coords are being checked
for (int z = 1; z < cChunkDef::Width - 1; z++)
{
for (int x = 1; x < cChunkDef::Width - 1; x++)
{
switch (a_ChunkDesc.GetBlockType(x, y, z))
{
case E_BLOCK_NETHERRACK:
case E_BLOCK_STONE:
{
if (TryPlaceSpring(a_ChunkDesc, x, y, z))
{
// Succeeded, bail out
return;
}
}
} // switch (BlockType)
} // for x
} // for y
} // for y
}
bool cFinishGenFluidSprings::TryPlaceSpring(cChunkDesc & a_ChunkDesc, int x, int y, int z)
{
// In order to place a spring, it needs exactly one of the XZ neighbors or a below neighbor to be air
// Also, its neighbor on top of it must be non-air
if (a_ChunkDesc.GetBlockType(x, y + 1, z) == E_BLOCK_AIR)
{
return false;
}
2014-12-03 08:26:15 +00:00
static const struct
{
int x, y, z;
} Coords[] =
{
{-1, 0, 0},
{ 1, 0, 0},
{ 0, -1, 0},
{ 0, 0, -1},
{ 0, 0, 1},
} ;
int NumAirNeighbors = 0;
for (size_t i = 0; i < ARRAYCOUNT(Coords); i++)
{
switch (a_ChunkDesc.GetBlockType(x + Coords[i].x, y + Coords[i].y, z + Coords[i].z))
{
case E_BLOCK_AIR:
{
NumAirNeighbors += 1;
if (NumAirNeighbors > 1)
{
return false;
}
}
}
}
if (NumAirNeighbors == 0)
{
return false;
}
2014-12-03 08:26:15 +00:00
// Has exactly one air neighbor, place a spring:
a_ChunkDesc.SetBlockTypeMeta(x, y, z, m_Fluid, 0);
return true;
}
2014-12-02 09:37:17 +00:00
////////////////////////////////////////////////////////////////////////////////
// cFinishGenPassiveMobs:
cFinishGenPassiveMobs::cFinishGenPassiveMobs(int a_Seed, cIniFile & a_IniFile, eDimension a_Dimension) :
2014-12-02 09:58:30 +00:00
m_Noise(a_Seed)
2014-12-02 09:37:17 +00:00
{
2014-12-02 09:58:30 +00:00
AString SectionName = "Animals";
int DefaultAnimalSpawnChunkPercentage = DEF_ANIMAL_SPAWN_PERCENT;
switch (a_Dimension)
{
case dimOverworld:
{
DefaultAnimalSpawnChunkPercentage = DEF_ANIMAL_SPAWN_PERCENT;
break;
}
case dimNether:
case dimEnd: // No nether or end animals (currently)
{
DefaultAnimalSpawnChunkPercentage = DEF_NO_ANIMALS;
break;
}
default:
{
ASSERT(!"Unhandled world dimension");
DefaultAnimalSpawnChunkPercentage = DEF_NO_ANIMALS;
2014-12-05 01:01:34 +00:00
break;
}
} // switch (dimension)
2014-12-02 09:58:30 +00:00
m_AnimalProbability = a_IniFile.GetValueSetI(SectionName, "AnimalSpawnChunkPercentage", DefaultAnimalSpawnChunkPercentage);
2014-12-04 09:08:17 +00:00
if ((m_AnimalProbability < 0) || (m_AnimalProbability > 100))
2014-12-02 09:58:30 +00:00
{
LOGWARNING("[Animals]: AnimalSpawnChunkPercentage is invalid, using the default of \"%d\".", DefaultAnimalSpawnChunkPercentage);
m_AnimalProbability = DefaultAnimalSpawnChunkPercentage;
2014-12-02 09:58:30 +00:00
}
2014-12-02 09:37:17 +00:00
}
void cFinishGenPassiveMobs::GenFinish(cChunkDesc & a_ChunkDesc)
{
2014-12-02 19:32:53 +00:00
int chunkX = a_ChunkDesc.GetChunkX();
int chunkZ = a_ChunkDesc.GetChunkZ();
int ChanceRnd = (m_Noise.IntNoise2DInt(chunkX, chunkZ) / 7) % 100;
2014-12-02 09:58:30 +00:00
if (ChanceRnd > m_AnimalProbability)
{
return;
2014-12-02 09:58:30 +00:00
}
eMonsterType RandomMob = GetRandomMob(a_ChunkDesc);
if (RandomMob == mtInvalidType)
{
// No mobs here. Don't send an error, because if the biome was a desert it would return mtInvalidType as well.
return;
2014-12-02 09:58:30 +00:00
}
2014-12-04 09:08:17 +00:00
// Try spawning a pack center 10 times, should get roughly the same probability
2014-12-02 09:58:30 +00:00
for (int Tries = 0; Tries < 10; Tries++)
{
2014-12-04 21:28:27 +00:00
int PackCenterX = (m_Noise.IntNoise2DInt(chunkX + chunkZ, Tries) / 7) % cChunkDef::Width;
int PackCenterZ = (m_Noise.IntNoise2DInt(chunkX, chunkZ + Tries) / 7) % cChunkDef::Width;
2014-12-02 09:58:30 +00:00
if (TrySpawnAnimals(a_ChunkDesc, PackCenterX, a_ChunkDesc.GetHeight(PackCenterX, PackCenterZ), PackCenterZ, RandomMob))
{
for (int i = 0; i < 3; i++)
2014-12-02 09:58:30 +00:00
{
2014-12-04 21:28:27 +00:00
int OffsetX = (m_Noise.IntNoise2DInt(chunkX + chunkZ + i, Tries) / 7) % cChunkDef::Width;
int OffsetZ = (m_Noise.IntNoise2DInt(chunkX, chunkZ + Tries + i) / 7) % cChunkDef::Width;
2014-12-02 09:58:30 +00:00
TrySpawnAnimals(a_ChunkDesc, OffsetX, a_ChunkDesc.GetHeight(OffsetX, OffsetZ), OffsetZ, RandomMob);
}
return;
} // if pack center spawn successful
} // for tries
2014-12-02 09:37:17 +00:00
}
bool cFinishGenPassiveMobs::TrySpawnAnimals(cChunkDesc & a_ChunkDesc, int a_RelX, int a_RelY, int a_RelZ, eMonsterType AnimalToSpawn)
{
2014-12-04 09:08:17 +00:00
if ((a_RelY >= cChunkDef::Height - 1) || (a_RelY <= 0))
{
return false;
}
2014-12-02 09:58:30 +00:00
BLOCKTYPE BlockAtHead = a_ChunkDesc.GetBlockType(a_RelX, a_RelY + 1, a_RelZ);
BLOCKTYPE BlockAtFeet = a_ChunkDesc.GetBlockType(a_RelX, a_RelY, a_RelZ);
BLOCKTYPE BlockUnderFeet = a_ChunkDesc.GetBlockType(a_RelX, a_RelY - 1, a_RelZ);
2014-12-04 09:08:17 +00:00
// Check block below (opaque, grass, water), and above (air)
if ((AnimalToSpawn == mtSquid) && (BlockAtFeet != E_BLOCK_WATER))
2014-12-02 09:58:30 +00:00
{
return false;
2014-12-02 09:58:30 +00:00
}
2014-12-04 21:28:27 +00:00
if (
(AnimalToSpawn != mtSquid) &&
(BlockAtHead != E_BLOCK_AIR) &&
(BlockAtFeet != E_BLOCK_AIR) &&
(!cBlockInfo::IsTransparent(BlockUnderFeet))
2014-12-02 09:58:30 +00:00
)
{
return false;
2014-12-02 09:58:30 +00:00
}
2014-12-05 00:44:18 +00:00
if (
(BlockUnderFeet != E_BLOCK_GRASS) &&
2015-05-24 06:28:17 +00:00
((AnimalToSpawn == mtWolf) || (AnimalToSpawn == mtRabbit) || (AnimalToSpawn == mtCow) || (AnimalToSpawn == mtSheep) || (AnimalToSpawn == mtChicken) || (AnimalToSpawn == mtPig))
2014-12-05 00:44:18 +00:00
)
2014-12-02 09:58:30 +00:00
{
return false;
}
2014-12-05 00:44:18 +00:00
if ((AnimalToSpawn == mtMooshroom) && (BlockUnderFeet != E_BLOCK_MYCELIUM))
{
return false;
2014-12-02 09:58:30 +00:00
}
double AnimalX = static_cast<double>(a_ChunkDesc.GetChunkX() * cChunkDef::Width + a_RelX + 0.5);
double AnimalY = a_RelY;
double AnimalZ = static_cast<double>(a_ChunkDesc.GetChunkZ() * cChunkDef::Width + a_RelZ + 0.5);
2014-12-02 09:58:30 +00:00
cMonster * NewMob = cMonster::NewMonsterFromType(AnimalToSpawn);
2015-05-18 04:22:26 +00:00
NewMob->SetHealth(NewMob->GetMaxHealth());
2014-12-02 09:58:30 +00:00
NewMob->SetPosition(AnimalX, AnimalY, AnimalZ);
a_ChunkDesc.GetEntities().push_back(NewMob);
LOGD("Spawning %s #%i at {%.02f, %.02f, %.02f}", NewMob->GetClass(), NewMob->GetUniqueID(), AnimalX, AnimalY, AnimalZ);
2014-12-02 09:58:30 +00:00
return true;
2014-12-02 09:37:17 +00:00
}
eMonsterType cFinishGenPassiveMobs::GetRandomMob(cChunkDesc & a_ChunkDesc)
{
2014-12-02 09:58:30 +00:00
std::set<eMonsterType> ListOfSpawnables;
2014-12-02 19:32:53 +00:00
int chunkX = a_ChunkDesc.GetChunkX();
int chunkZ = a_ChunkDesc.GetChunkZ();
2014-12-04 21:28:27 +00:00
int x = (m_Noise.IntNoise2DInt(chunkX, chunkZ + 10) / 7) % cChunkDef::Width;
int z = (m_Noise.IntNoise2DInt(chunkX + chunkZ, chunkZ) / 7) % cChunkDef::Width;
2014-12-02 09:58:30 +00:00
// Check biomes first to get a list of animals
2014-12-02 09:58:30 +00:00
switch (a_ChunkDesc.GetBiome(x, z))
{
// No animals in deserts or non-overworld dimensions
case biNether:
case biEnd:
case biDesertHills:
case biDesert:
case biDesertM:
2014-12-04 09:08:17 +00:00
{
return mtInvalidType;
2014-12-04 09:08:17 +00:00
}
// Mooshroom only - no other mobs on mushroom islands
case biMushroomIsland:
case biMushroomShore:
{
2014-12-05 09:01:10 +00:00
return mtMooshroom;
}
// Add squid in ocean biomes
case biOcean:
case biFrozenOcean:
case biFrozenRiver:
case biRiver:
case biDeepOcean:
2014-12-04 09:08:17 +00:00
{
ListOfSpawnables.insert(mtSquid);
2014-12-05 08:57:40 +00:00
break;
2014-12-04 09:08:17 +00:00
}
// Add ocelots in jungle biomes
case biJungle:
case biJungleHills:
case biJungleEdge:
case biJungleM:
case biJungleEdgeM:
2014-12-04 09:08:17 +00:00
{
ListOfSpawnables.insert(mtOcelot);
2014-12-05 08:57:40 +00:00
break;
2014-12-04 09:08:17 +00:00
}
// Add horses in plains-like biomes
case biPlains:
case biSunflowerPlains:
case biSavanna:
case biSavannaPlateau:
case biSavannaM:
case biSavannaPlateauM:
2014-12-04 09:08:17 +00:00
{
ListOfSpawnables.insert(mtHorse);
2014-12-05 08:57:40 +00:00
break;
2014-12-04 09:08:17 +00:00
}
// Add wolves in forest and spruce forests
case biForest:
case biTaiga:
case biMegaTaiga:
case biColdTaiga:
case biColdTaigaM:
2014-12-04 09:08:17 +00:00
{
ListOfSpawnables.insert(mtWolf);
2014-12-05 08:57:40 +00:00
break;
2014-12-04 09:08:17 +00:00
}
2014-12-05 08:57:40 +00:00
// Nothing special about this biome
default:
2014-12-04 09:08:17 +00:00
{
2014-12-05 08:57:40 +00:00
break;
2014-12-04 09:08:17 +00:00
}
2014-12-02 09:58:30 +00:00
}
ListOfSpawnables.insert(mtChicken);
ListOfSpawnables.insert(mtCow);
ListOfSpawnables.insert(mtPig);
ListOfSpawnables.insert(mtSheep);
2014-12-02 09:58:30 +00:00
if (ListOfSpawnables.empty())
2014-12-02 09:58:30 +00:00
{
return mtInvalidType;
2014-12-02 09:58:30 +00:00
}
auto MobIter = ListOfSpawnables.begin();
Fix FreeBSD/clang errors caused by -Werror With FreeBSD/clang, -Werror combined with the configured warning flags yields some fatal errors, specifically related to signed conversion, 64 to 32 bit conversion, and tautological compares. CONTRIBUTORS Add myself to the contributor list src/Generating/FinishGen.cpp In cFinishGenPassiveMobs::GetRandomMob(), change the type of RandMob from size_t to the difference_type of the ListOfSpawnables iterator MobIter. Using size_t triggers a 64 bit to 32 bit conversion if the difference_type of the iterator class is 64 bit Also explicitly cast the noise expression to unsigned long so we don't get a signed conversion warning from the modulo against ListOfSpawnables.size() src/OSSupport/StackTrace.cpp FreeBSD 10 and above includes a non glibc implementation of benchmark() for which size_t, not int, is the return type. To account for this and prevent a signed conversion warning, abstract the type for numItems with a macro btsize src/StringUtils.h In StringToInteger(), correct a tautological compare warning for unsigned types with the template. If T is unsigned, comparing std::numeric_limits<T>::min() to the unsigned result is always false. That control can enter this branch in an evaluated template with an unsigned type T may also permit a signed number to be parsed and erroneously stripped of its signedness at runtime. To guard against this and avoid the warning in the case that the number parsed from the string is non-positive, return false and don't try to parse if T is unsigned and control enters the non-positive branch
2015-05-30 08:23:57 +00:00
using diff_type =
std::iterator_traits<decltype(MobIter)>::difference_type;
diff_type RandMob = static_cast<diff_type>
2015-05-30 11:31:05 +00:00
(static_cast<size_t>(m_Noise.IntNoise2DInt(chunkX - chunkZ + 2, chunkX + 5) / 7)
Fix FreeBSD/clang errors caused by -Werror With FreeBSD/clang, -Werror combined with the configured warning flags yields some fatal errors, specifically related to signed conversion, 64 to 32 bit conversion, and tautological compares. CONTRIBUTORS Add myself to the contributor list src/Generating/FinishGen.cpp In cFinishGenPassiveMobs::GetRandomMob(), change the type of RandMob from size_t to the difference_type of the ListOfSpawnables iterator MobIter. Using size_t triggers a 64 bit to 32 bit conversion if the difference_type of the iterator class is 64 bit Also explicitly cast the noise expression to unsigned long so we don't get a signed conversion warning from the modulo against ListOfSpawnables.size() src/OSSupport/StackTrace.cpp FreeBSD 10 and above includes a non glibc implementation of benchmark() for which size_t, not int, is the return type. To account for this and prevent a signed conversion warning, abstract the type for numItems with a macro btsize src/StringUtils.h In StringToInteger(), correct a tautological compare warning for unsigned types with the template. If T is unsigned, comparing std::numeric_limits<T>::min() to the unsigned result is always false. That control can enter this branch in an evaluated template with an unsigned type T may also permit a signed number to be parsed and erroneously stripped of its signedness at runtime. To guard against this and avoid the warning in the case that the number parsed from the string is non-positive, return false and don't try to parse if T is unsigned and control enters the non-positive branch
2015-05-30 08:23:57 +00:00
% ListOfSpawnables.size());
std::advance(MobIter, RandMob);
2014-12-02 09:58:30 +00:00
return *MobIter;
2014-12-02 09:37:17 +00:00
}
////////////////////////////////////////////////////////////////////////////////
// cFinishGenOres:
void cFinishGenOres::GenFinish(cChunkDesc & a_ChunkDesc)
{
int seq = 1;
// Generate the ores from the ore list.
for (const auto & ore: m_OreInfos)
{
GenerateOre(
a_ChunkDesc,
ore.m_BlockType, ore.m_BlockMeta,
ore.m_MaxHeight, ore.m_NumNests, ore.m_NestSize,
seq
);
seq++;
}
}
const cFinishGenOres::OreInfos & cFinishGenOres::DefaultOverworldOres(void)
{
static OreInfos res
{
// OreType, OreMeta, MaxHeight, NumNests, NestSize
{E_BLOCK_COAL_ORE, 0, 127, 20, 16},
{E_BLOCK_IRON_ORE, 0, 64, 20, 8},
{E_BLOCK_GOLD_ORE, 0, 32, 2, 8},
{E_BLOCK_REDSTONE_ORE, 0, 16, 8, 7},
{E_BLOCK_DIAMOND_ORE, 0, 15, 1, 7},
{E_BLOCK_LAPIS_ORE, 0, 30, 1, 6},
};
return res;
}
const cFinishGenOres::OreInfos & cFinishGenOres::DefaultNetherOres(void)
{
static OreInfos res
{
// OreType, OreMeta, MaxHeight, NumNests, NestSize
{E_BLOCK_NETHER_QUARTZ_ORE, 0, 127, 20, 8},
};
return res;
}
const cFinishGenOres::OreInfos & cFinishGenOres::DefaultNaturalPatches(void)
{
static OreInfos res
{
// OreType, OreMeta, MaxHeight, NumNests, NestSize
{E_BLOCK_DIRT, 0, 127, 20, 32},
{E_BLOCK_GRAVEL, 0, 127, 10, 32},
{E_BLOCK_STONE, E_META_STONE_GRANITE, 127, 20, 32},
{E_BLOCK_STONE, E_META_STONE_DIORITE, 127, 20, 32},
{E_BLOCK_STONE, E_META_STONE_ANDESITE, 127, 20, 32},
};
return res;
}
cFinishGenOres::OreInfos cFinishGenOres::OreInfosFromString(const AString & a_OreInfosString)
{
// The string is expected to be formatted as "<OreInfo1> | <OreInfo2> | <OreInfo3> | ..."
// Each OreInfo is expected to be formatted as "<OreType> : <OreMeta> : <MaxHeight> : <NumNests> : <NestSize>"
OreInfos res;
auto ores = StringSplitAndTrim(a_OreInfosString, "|");
for (const auto & ore: ores)
{
auto parts = StringSplitAndTrim(ore, ":");
if (parts.size() != 5)
{
LOGWARNING("Cannot parse ore information from string, not enough OreInfo members (exp 5, got %d). Offending item: \"%s\".",
static_cast<unsigned>(parts.size()), ore.c_str()
);
continue;
}
auto oreType = BlockStringToType(parts[0]);
if (oreType < 0)
{
LOGWARNING("Cannot parse ore information from string, invalid OreType: \"%s\".", parts[0].c_str());
continue;
}
NIBBLETYPE oreMeta;
int maxHeight, numNests, nestSize;
if (
!StringToInteger(parts[1], oreMeta) ||
!StringToInteger(parts[2], maxHeight) ||
!StringToInteger(parts[3], numNests) ||
!StringToInteger(parts[4], nestSize)
)
{
LOGWARNING("Cannot parse ore information from string, invalid number in OreInfo \"%s\".", ore.c_str());
continue;
}
res.emplace_back(oreType, oreMeta, maxHeight, numNests, nestSize);
} // for i - split[]
return res;
}
AString cFinishGenOres::OreInfosToString(const cFinishGenOres::OreInfos & a_OreInfos)
{
AString res;
for (const auto & ore: a_OreInfos)
{
if (!res.empty())
{
res.append(" | ");
}
AppendPrintf(res, "%s:%d:%d:%d:%d",
ItemTypeToString(ore.m_BlockType).c_str(), ore.m_BlockMeta,
ore.m_MaxHeight, ore.m_NumNests, ore.m_NestSize
);
} // for ore - a_OreInfos[]
return res;
}
void cFinishGenOres::SetSeed(int a_Seed)
{
m_Noise.SetSeed(a_Seed);
}
////////////////////////////////////////////////////////////////////////////////
// cFinishGenOreNests:
void cFinishGenOreNests::GenerateOre(
cChunkDesc & a_ChunkDesc,
BLOCKTYPE a_OreType, NIBBLETYPE a_OreMeta,
int a_MaxHeight, int a_NumNests, int a_NestSize,
int a_Seq
)
{
// This function generates several "nests" of ore, each nest consisting of number of ore blocks relatively adjacent to each other.
// It does so by making a random XYZ walk and adding ore along the way in cuboids of different (random) sizes
// Only "terraformable" blocks get replaced with ore, all other blocks stay (so the nest can actually be smaller than specified).
auto chunkX = a_ChunkDesc.GetChunkX();
auto chunkZ = a_ChunkDesc.GetChunkZ();
auto & blockTypes = a_ChunkDesc.GetBlockTypes();
auto & blockMetas = a_ChunkDesc.GetBlockMetasUncompressed();
for (int i = 0; i < a_NumNests; i++)
{
int nestRnd = m_Noise.IntNoise3DInt(chunkX + i, a_Seq, chunkZ + 64 * i) / 8;
int BaseX = nestRnd % cChunkDef::Width;
nestRnd /= cChunkDef::Width;
int BaseZ = nestRnd % cChunkDef::Width;
nestRnd /= cChunkDef::Width;
int BaseY = nestRnd % a_MaxHeight;
nestRnd /= a_MaxHeight;
int NestSize = a_NestSize + (nestRnd % (a_NestSize / 4)); // The actual nest size may be up to 1 / 4 larger
int Num = 0;
while (Num < NestSize)
{
// Put a cuboid around [BaseX, BaseY, BaseZ]
int rnd = m_Noise.IntNoise3DInt(chunkX + 64 * i, 2 * a_Seq + Num, chunkZ + 32 * i) / 8;
int xsize = rnd % 2;
int ysize = (rnd / 4) % 2;
int zsize = (rnd / 16) % 2;
rnd >>= 8;
for (int x = xsize; x >= 0; --x)
{
int BlockX = BaseX + x;
if (!cChunkDef::IsValidWidth(BlockX))
{
Num++; // So that the cycle finishes even if the base coords wander away from the chunk
continue;
}
for (int y = ysize; y >= 0; --y)
{
int BlockY = BaseY + y;
if (!cChunkDef::IsValidHeight(BlockY))
{
Num++; // So that the cycle finishes even if the base coords wander away from the chunk
continue;
}
for (int z = zsize; z >= 0; --z)
{
int BlockZ = BaseZ + z;
if (!cChunkDef::IsValidWidth(BlockZ))
{
Num++; // So that the cycle finishes even if the base coords wander away from the chunk
continue;
}
int Index = cChunkDef::MakeIndexNoCheck(BlockX, BlockY, BlockZ);
auto blockType = blockTypes[Index];
if ((blockType == E_BLOCK_STONE) || (blockType == E_BLOCK_NETHERRACK))
{
blockTypes[Index] = a_OreType;
blockMetas[Index] = a_OreMeta;
}
Num++;
} // for z
} // for y
} // for x
// Move the base to a neighbor voxel
switch (rnd % 4)
{
case 0: BaseX--; break;
case 1: BaseX++; break;
}
switch ((rnd >> 3) % 4)
{
case 0: BaseY--; break;
case 1: BaseY++; break;
}
switch ((rnd >> 6) % 4)
{
case 0: BaseZ--; break;
case 1: BaseZ++; break;
}
} // while (Num < NumBlocks)
} // for i - NumNests
}
////////////////////////////////////////////////////////////////////////////////
// cFinishGenOrePockets:
bool cFinishGenOrePockets::Initialize(cIniFile & a_IniFile, const AString & a_GenName)
{
// Read the OreInfos configuration:
auto valueName = a_GenName + "Blocks";
auto pocketCfg = a_IniFile.GetValue("Generator", valueName, "");
if (pocketCfg.empty())
{
// There's no config currently stored in the INI file. Store the defaults as the config:
a_IniFile.SetValue("Generator", valueName, OreInfosToString(m_OreInfos));
}
else
{
m_OreInfos = OreInfosFromString(pocketCfg);
}
// Read the optional seed configuration (but do not store the default):
valueName = a_GenName + "Seed";
SetSeed(a_IniFile.GetValueI("Generator", valueName, m_Noise.GetSeed()));
return true;
}
void cFinishGenOrePockets::GenerateOre(
cChunkDesc & a_ChunkDesc,
BLOCKTYPE a_OreType, NIBBLETYPE a_OreMeta,
int a_MaxHeight, int a_NumNests, int a_NestSize,
int a_Seq
)
{
// This function generates several "pockets" of the specified ore
// Each chunk can contain only pockets that are generated for that chunk, or for its XM / ZM neighbors.
// Generate for the 3 neighbors in the XP / ZP direction as well, so that pockets crossing the boundaries are accounted for as well:
int chunkZ = a_ChunkDesc.GetChunkZ();
int chunkX = a_ChunkDesc.GetChunkX();
imprintChunkOrePockets(chunkX - 1, chunkZ - 1, a_ChunkDesc, a_OreType, a_OreMeta, a_MaxHeight, a_NumNests, a_NestSize, a_Seq);
imprintChunkOrePockets(chunkX - 1, chunkZ, a_ChunkDesc, a_OreType, a_OreMeta, a_MaxHeight, a_NumNests, a_NestSize, a_Seq);
imprintChunkOrePockets(chunkX, chunkZ - 1, a_ChunkDesc, a_OreType, a_OreMeta, a_MaxHeight, a_NumNests, a_NestSize, a_Seq);
imprintChunkOrePockets(chunkX, chunkZ, a_ChunkDesc, a_OreType, a_OreMeta, a_MaxHeight, a_NumNests, a_NestSize, a_Seq);
}
void cFinishGenOrePockets::imprintChunkOrePockets(
int a_ChunkX, int a_ChunkZ,
cChunkDesc & a_ChunkDesc,
BLOCKTYPE a_OreType, NIBBLETYPE a_OreMeta,
int a_MaxHeight, int a_NumNests, int a_NestSize,
int a_Seq
)
{
// Pick a starting coord for each nest:
int baseBlockX = a_ChunkX * cChunkDef::Width;
int baseBlockZ = a_ChunkZ * cChunkDef::Width;
for (int i = 0; i < a_NumNests; i++)
{
int nestRnd = m_Noise.IntNoise3DInt(a_ChunkX + i, a_Seq, a_ChunkZ + 64 * i) / 7;
int baseX = (nestRnd % cChunkDef::Width) + baseBlockX;
nestRnd /= cChunkDef::Width;
int baseZ = (nestRnd % cChunkDef::Width) + baseBlockZ;
nestRnd /= cChunkDef::Width;
int baseY = nestRnd % a_MaxHeight;
nestRnd /= a_MaxHeight;
imprintPocket(
a_ChunkDesc,
baseX, baseY, baseZ,
a_NestSize, i + 200 * a_Seq,
a_OreType, a_OreMeta
);
} // for i - NumNests
}
void cFinishGenOrePockets::imprintPocket(
cChunkDesc & a_ChunkDesc,
int a_MinPocketX, int a_PocketY, int a_MinPocketZ,
int a_NestSize, int a_Seq,
BLOCKTYPE a_OreType, NIBBLETYPE a_OreMeta
)
{
// A line segment in a random direction is chosen. Then, several spheres are formed along this line segment,
// with their diameters diminishing towards the line ends (one half of a sinusoid)
double x1 = static_cast<double>(a_MinPocketX) + 0.5;
double y1 = static_cast<double>(a_PocketY) + 0.5;
double z1 = static_cast<double>(a_MinPocketZ) + 0.5;
int rnd = m_Noise.IntNoise2DInt(a_MinPocketX + 7 * a_Seq, a_MinPocketZ + a_PocketY * 11) / 7;
double angle = static_cast<double>(rnd % 256) / (256.0 * M_PI / 2.0); // range [0 .. pi / 2]
rnd /= 256;
double length = static_cast<double>(a_NestSize) / 4.0;
double x2 = x1 + sin(angle) * length; // Always larger than x1
double z2 = z1 + cos(angle) * length; // Always larger than z1
double y2 = y1 + static_cast<double>((rnd % 3) - 1); // Up to 1 block away from y1
// Iterate over the line segment in a total of a_NestSize steps:
double stepX = (x2 - x1) / static_cast<double>(a_NestSize);
double stepY = (y2 - y1) / static_cast<double>(a_NestSize);
double stepZ = (z2 - z1) / static_cast<double>(a_NestSize);
double stepR = M_PI / static_cast<double>(a_NestSize);
double size = static_cast<double>(a_NestSize) / 16.0;
for (int i = 0; i < a_NestSize; ++i)
{
double iDbl = static_cast<double>(i);
double sphereX = x1 + stepX * iDbl;
double sphereY = y1 + stepY * iDbl;
double sphereZ = z1 + stepZ * iDbl;
double radius = (sin(stepR * iDbl) + 1.0) * size + 1.0;
imprintSphere(a_ChunkDesc, sphereX, sphereY, sphereZ, radius, a_OreType, a_OreMeta);
} // for i
}
void cFinishGenOrePockets::imprintSphere(
cChunkDesc & a_ChunkDesc,
double a_SphereX, double a_SphereY, double a_SphereZ, double a_Radius,
BLOCKTYPE a_OreType, NIBBLETYPE a_OreMeta
)
{
// Get the sphere's bounding box, unioned with the chunk's bounding box (possibly empty):
int baseX = a_ChunkDesc.GetChunkX() * cChunkDef::Width;
int baseZ = a_ChunkDesc.GetChunkZ() * cChunkDef::Width;
int minX = std::max(FloorC(a_SphereX - a_Radius), baseX);
int minY = std::max(FloorC(a_SphereY - a_Radius), 0);
int minZ = std::max(FloorC(a_SphereZ - a_Radius), baseZ);
int maxX = std::min(CeilC(a_SphereX + a_Radius), baseX + cChunkDef::Width - 1);
int maxY = std::min(CeilC(a_SphereY + a_Radius), cChunkDef::Height - 1);
int maxZ = std::min(CeilC(a_SphereZ + a_Radius), baseZ + cChunkDef::Width - 1);
/*
// DEBUG:
int blockX = FloorC(a_SphereX);
int blockY = FloorC(a_SphereY);
int blockZ = FloorC(a_SphereZ);
if (
(blockX >= baseX) && (blockX < baseX + cChunkDef::Width) &&
(blockY >= 0) && (blockY < cChunkDef::Height) &&
(blockZ >= baseZ) && (blockZ < baseZ + cChunkDef::Width)
)
{
// LOGD("Imprinting a sphere center at {%d, %d, %d}", blockX, blockY, blockZ);
a_ChunkDesc.SetBlockTypeMeta(blockX - baseX, blockY, blockZ - baseZ, a_OreType, a_OreMeta);
}
return;
//*/
// Imprint the parts of the sphere intersecting the chunk:
double radiusSq = a_Radius * a_Radius / 4.0;
for (int y = minY; y <= maxY; y++)
{
double relY = static_cast<double>(y) + 0.5 - a_SphereY;
double relYSq = relY * relY;
if (relYSq > radiusSq)
{
// outside the sphere, bail out
continue;
}
for (int z = minZ; z <= maxZ; z++)
{
double relZ = static_cast<double>(z) + 0.5 - a_SphereZ;
double relZSq = relZ * relZ;
if (relZSq + relYSq > radiusSq)
{
// outside the sphere, bail out
continue;
}
for (int x = minX; x <= maxX; x++)
{
double relX = static_cast<double>(x) + 0.5 - a_SphereX;
double relXSq = relX * relX;
if (relZSq + relYSq + relXSq > radiusSq)
{
// outside the sphere, bail out
continue;
}
int bX = x - baseX;
int bZ = z - baseZ;
auto blockType = a_ChunkDesc.GetBlockType(bX, y, bZ);
if ((blockType == E_BLOCK_STONE) || (blockType == E_BLOCK_NETHERRACK))
{
a_ChunkDesc.SetBlockTypeMeta(bX, y, bZ, a_OreType, a_OreMeta);
}
} // for x
} // for z
} // for y
}