summaryrefslogtreecommitdiffstats
path: root/src/Generating/FinishGen.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/Generating/FinishGen.cpp')
-rw-r--r--src/Generating/FinishGen.cpp664
1 files changed, 664 insertions, 0 deletions
diff --git a/src/Generating/FinishGen.cpp b/src/Generating/FinishGen.cpp
new file mode 100644
index 000000000..8899e4bd0
--- /dev/null
+++ b/src/Generating/FinishGen.cpp
@@ -0,0 +1,664 @@
+
+// FinishGen.cpp
+
+/* Implements the various finishing generators:
+ - cFinishGenSnow
+ - cFinishGenIce
+ - cFinishGenSprinkleFoliage
+*/
+
+#include "Globals.h"
+
+#include "FinishGen.h"
+#include "../Noise.h"
+#include "../BlockID.h"
+#include "../Simulator/FluidSimulator.h" // for cFluidSimulator::CanWashAway()
+#include "../World.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"
+
+
+
+
+
+static inline bool IsWater(BLOCKTYPE a_BlockType)
+{
+ return (a_BlockType == E_BLOCK_STATIONARY_WATER) || (a_BlockType == E_BLOCK_WATER);
+}
+
+
+
+
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// 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)) &&
+ !IsWater(a_ChunkDesc.GetBlockType(a_RelX , a_RelY, a_RelZ - 1)) &&
+ !IsWater(a_ChunkDesc.GetBlockType(a_RelX , a_RelY, a_RelZ + 1))
+ )
+ {
+ return false;
+ }
+
+ // 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):
+
+ // TODO: Update heightmap with 1-block-tall foliage
+ for (int z = 0; z < cChunkDef::Width; z++)
+ {
+ int BlockZ = a_ChunkDesc.GetChunkZ() * cChunkDef::Width + z;
+ const float zz = (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;
+ }
+ int 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;
+ }
+
+ const float xx = (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))
+ {
+ a_ChunkDesc.SetBlockTypeMeta(x, ++Top, z, E_BLOCK_PUMPKIN, (int)(val3 * 8) % 4);
+ }
+ break;
+ } // case E_BLOCK_GRASS
+
+ 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)
+ )
+ {
+ 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
+}
+
+
+
+
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// 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++)
+ {
+ switch (a_ChunkDesc.GetBiome(x, z))
+ {
+ case biIcePlains:
+ case biIceMountains:
+ case biTaiga:
+ case biTaigaHills:
+ case biFrozenRiver:
+ case biFrozenOcean:
+ {
+ int Height = a_ChunkDesc.GetHeight(x, z);
+ if (g_BlockIsSnowable[a_ChunkDesc.GetBlockType(x, Height, z)])
+ {
+ a_ChunkDesc.SetBlockType(x, Height + 1, z, E_BLOCK_SNOW);
+ a_ChunkDesc.SetHeight(x, z, Height + 1);
+ }
+ break;
+ }
+ }
+ }
+ } // 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++)
+ {
+ switch (a_ChunkDesc.GetBiome(x, z))
+ {
+ case biIcePlains:
+ case biIceMountains:
+ case biTaiga:
+ case biTaigaHills:
+ case biFrozenRiver:
+ case biFrozenOcean:
+ {
+ int Height = a_ChunkDesc.GetHeight(x, z);
+ switch (a_ChunkDesc.GetBlockType(x, Height, z))
+ {
+ case E_BLOCK_WATER:
+ case E_BLOCK_STATIONARY_WATER:
+ {
+ a_ChunkDesc.SetBlockType(x, Height, z, E_BLOCK_ICE);
+ break;
+ }
+ }
+ break;
+ }
+ }
+ }
+ } // for z
+}
+
+
+
+
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// cFinishGenLilypads:
+
+int cFinishGenSingleBiomeSingleTopBlock::GetNumToGen(const cChunkDef::BiomeMap & a_BiomeMap)
+{
+ int res = 0;
+ for (int i = 0; i < ARRAYCOUNT(a_BiomeMap); i++)
+ {
+ if (a_BiomeMap[i] == m_Biome)
+ {
+ res++;
+ }
+ } // for i - a_BiomeMap[]
+ return m_Amount * res / 256;
+}
+
+
+
+
+
+void cFinishGenSingleBiomeSingleTopBlock::GenFinish(cChunkDesc & a_ChunkDesc)
+{
+ // Add Lilypads on top of water surface in Swampland
+
+ 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:
+ if (a_ChunkDesc.GetBiome(x, z) != m_Biome)
+ {
+ // Incorrect biome
+ continue;
+ }
+ int Height = a_ChunkDesc.GetHeight(x, z);
+ if (Height >= cChunkDef::Height)
+ {
+ // 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 ((BlockBelow == m_AllowedBelow1) || (BlockBelow == m_AllowedBelow2))
+ {
+ a_ChunkDesc.SetBlockType(x, Height + 1, z, m_BlockType);
+ a_ChunkDesc.SetHeight(x, z, Height + 1);
+ }
+ } // for i
+}
+
+
+
+
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// 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(void)
+{
+ // Nothing needed yet
+}
+
+
+
+
+
+void cFinishGenPreSimulator::GenFinish(cChunkDesc & a_ChunkDesc)
+{
+ CollapseSandGravel(a_ChunkDesc.GetBlockTypes(), a_ChunkDesc.GetHeightMap());
+ StationarizeFluid(a_ChunkDesc.GetBlockTypes(), a_ChunkDesc.GetHeightMap(), E_BLOCK_WATER, E_BLOCK_STATIONARY_WATER);
+ 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
+ cChunkDef::SetHeight(a_HeightMap, x, z, 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 (int 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
+
+ // 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, const cWorld & a_World) :
+ 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(255),
+ m_Fluid(a_Fluid)
+{
+ bool IsWater = (a_Fluid == E_BLOCK_WATER);
+ AString SectionName = IsWater ? "WaterSprings" : "LavaSprings";
+ AString DefaultHeightDistribution;
+ int DefaultChance;
+ switch (a_World.GetDimension())
+ {
+ 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))
+ {
+ 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;
+ }
+
+ // 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);
+
+ // 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;
+ }
+
+ 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 (int 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;
+ }
+
+ // Has exactly one air neighbor, place a spring:
+ a_ChunkDesc.SetBlockTypeMeta(x, y, z, m_Fluid, 0);
+ return true;
+}
+
+
+
+