// VerticalStrategy.cpp // Implements the various classes descending from cPiece::cVerticalStrategy #include "Globals.h" #include "VerticalStrategy.h" // Constant that is added to random seed static const int SEED_OFFSET = 135; // Emit a warning if the first param is true #define CONDWARNING(ShouldLog, Fmt, ...) \ if (ShouldLog) \ { \ LOGWARNING(Fmt, __VA_ARGS__); \ } //////////////////////////////////////////////////////////////////////////////// // Globals: /** Parses a string containing a range in which both values are optional ("|") into Min, Range. Returns true if successful, false on failure. If a_LogWarnings is true, outputs failure reasons to console. The range is returned in a_Min and a_Range, they are left unchanged if the range value is not present in the string. */ static bool ParseRange(const AString & a_Params, int & a_Min, int & a_Range, bool a_LogWarnings) { auto params = StringSplitAndTrim(a_Params, "|"); if (params.size() == 0) { // No params, generate directly on top: return true; } if (!StringToInteger(params[0], a_Min)) { // Failed to parse the min rel height: CONDWARNING(a_LogWarnings, "Cannot parse minimum height from string \"%s\"!", params[0].c_str()); return false; } if (params.size() == 1) { // Only one param was given, there's no range return true; } int maxHeight = a_Min; if (!StringToInteger(params[1], maxHeight)) { CONDWARNING(a_LogWarnings, "Cannot parse maximum height from string \"%s\"!", params[1].c_str()); return false; } if (maxHeight < a_Min) { std::swap(maxHeight, a_Min); } a_Range = maxHeight - a_Min + 1; return true; } //////////////////////////////////////////////////////////////////////////////// /** A vertical strategy that places the piece at a predefined height. */ class cVerticalStrategyFixed: public cPiece::cVerticalStrategy { public: cVerticalStrategyFixed(void): m_Height(-1000) // Default to "unassigned" height { } virtual int GetVerticalPlacement(int a_BlockX, int a_BlockZ) override { return m_Height; } virtual bool InitializeFromString(const AString & a_Params, bool a_LogWarnings) override { // Params: "", compulsory if (!StringToInteger(a_Params, m_Height)) { CONDWARNING(a_LogWarnings, "Cannot parse the fixed height from string \"%s\"!", a_Params.c_str()); return false; } return true; } protected: /** Height at which the pieces are placed. Note that this height may be outside the world, so that only a part of the piece is generated. */ int m_Height; }; //////////////////////////////////////////////////////////////////////////////// /** A vertical strategy that places the piece in a random height between two heights. */ class cVerticalStrategyRange: public cPiece::cVerticalStrategy { public: cVerticalStrategyRange(void): m_Seed(0), m_Min(-1), // Default to "unassigned" height m_Range(1) { } virtual int GetVerticalPlacement(int a_BlockX, int a_BlockZ) override { cNoise Noise(m_Seed); return m_Min + (Noise.IntNoise2DInt(a_BlockX, a_BlockZ) / 7) % m_Range; } virtual bool InitializeFromString(const AString & a_Params, bool a_LogWarnings) override { // Params: "|", all compulsory auto params = StringSplitAndTrim(a_Params, "|"); if (params.size() != 2) { CONDWARNING(a_LogWarnings, "Cannot parse the range parameters from string \"%s\"!", a_Params.c_str()); return false; } int Max = 0; if (!StringToInteger(params[0], m_Min) || !StringToInteger(params[1], Max)) { CONDWARNING(a_LogWarnings, "Cannot parse the minimum or maximum height from string \"%s\"!", a_Params.c_str()); return false; } if (m_Min > Max) { std::swap(m_Min, Max); } m_Range = Max - m_Min + 1; return true; } virtual void AssignGens(int a_Seed, cBiomeGenPtr & a_BiomeGen, cTerrainHeightGenPtr & a_TerrainHeightGen, int a_SeaLevel) override { m_Seed = a_Seed + SEED_OFFSET; } protected: /** Seed for the random generator. Received in AssignGens(). */ int m_Seed; /** Range for the random generator. Received in InitializeFromString(). */ int m_Min, m_Range; }; //////////////////////////////////////////////////////////////////////////////// /** A vertical strategy that places the piece in a specified range relative to the top of the terrain. */ class cVerticalStrategyTerrainTop: public cPiece::cVerticalStrategy { public: virtual int GetVerticalPlacement(int a_BlockX, int a_BlockZ) override { ASSERT(m_HeightGen != nullptr); int ChunkX, ChunkZ; cChunkDef::BlockToChunk(a_BlockX, a_BlockZ, ChunkX, ChunkZ); cChunkDef::HeightMap HeightMap; m_HeightGen->GenHeightMap(ChunkX, ChunkZ, HeightMap); cNoise noise(m_Seed); int rel = m_MinRelHeight + (noise.IntNoise2DInt(a_BlockX, a_BlockZ) / 7) % m_RelHeightRange + 1; return cChunkDef::GetHeight(HeightMap, a_BlockX - ChunkX * cChunkDef::Width, a_BlockZ - ChunkZ * cChunkDef::Width) + rel; } virtual bool InitializeFromString(const AString & a_Params, bool a_LogWarnings) override { // Params: "|", all optional m_MinRelHeight = 0; m_RelHeightRange = 1; return ParseRange(a_Params, m_MinRelHeight, m_RelHeightRange, a_LogWarnings); } virtual void AssignGens(int a_Seed, cBiomeGenPtr & a_BiomeGen, cTerrainHeightGenPtr & a_HeightGen, int a_SeaLevel) override { m_Seed = a_Seed + SEED_OFFSET; m_HeightGen = a_HeightGen; } protected: /** Seed for the random generator. */ int m_Seed; /** Height generator from which the top of the terrain is read. */ cTerrainHeightGenPtr m_HeightGen; /** Minimum relative height at which the prefab is placed. */ int m_MinRelHeight; /** Range of the relative heights at which the prefab can be placed above the minimum. */ int m_RelHeightRange; }; //////////////////////////////////////////////////////////////////////////////// /** A vertical strategy that places the piece within a range on top of the terrain or ocean, whichever's higher. */ class cVerticalStrategyTerrainOrOceanTop: public cPiece::cVerticalStrategy { public: virtual int GetVerticalPlacement(int a_BlockX, int a_BlockZ) override { ASSERT(m_HeightGen != nullptr); int ChunkX, ChunkZ; cChunkDef::BlockToChunk(a_BlockX, a_BlockZ, ChunkX, ChunkZ); cChunkDef::HeightMap HeightMap; m_HeightGen->GenHeightMap(ChunkX, ChunkZ, HeightMap); int terrainHeight = static_cast(cChunkDef::GetHeight(HeightMap, a_BlockX - ChunkX * cChunkDef::Width, a_BlockZ - ChunkZ * cChunkDef::Width)); terrainHeight = std::max(1 + terrainHeight, m_SeaLevel); cNoise noise(m_Seed); int rel = m_MinRelHeight + (noise.IntNoise2DInt(a_BlockX, a_BlockZ) / 7) % m_RelHeightRange + 1; return terrainHeight + rel; } virtual bool InitializeFromString(const AString & a_Params, bool a_LogWarnings) override { // Params: "|", all optional m_MinRelHeight = 0; m_RelHeightRange = 1; return ParseRange(a_Params, m_MinRelHeight, m_RelHeightRange, a_LogWarnings); } virtual void AssignGens(int a_Seed, cBiomeGenPtr & a_BiomeGen, cTerrainHeightGenPtr & a_HeightGen, int a_SeaLevel) override { m_Seed = a_Seed + SEED_OFFSET; m_HeightGen = a_HeightGen; m_SeaLevel = a_SeaLevel; } protected: /** Seed for the random generator. */ int m_Seed; /** Height generator from which the top of the terrain is read. */ cTerrainHeightGenPtr m_HeightGen; /** The sea level used by the world. */ int m_SeaLevel; /** Minimum relative height at which the prefab is placed. */ int m_MinRelHeight; /** Range of the relative heights at which the prefab can be placed above the minimum. */ int m_RelHeightRange; }; //////////////////////////////////////////////////////////////////////////////// // CreateVerticalStrategyFromString: cPiece::cVerticalStrategyPtr CreateVerticalStrategyFromString(const AString & a_StrategyDesc, bool a_LogWarnings) { // Break apart the strategy class, the first parameter before the first pipe char: auto idxPipe = a_StrategyDesc.find('|'); if (idxPipe == AString::npos) { idxPipe = a_StrategyDesc.length(); } AString StrategyClass = a_StrategyDesc.substr(0, idxPipe); // Create a strategy class based on the class string: cPiece::cVerticalStrategyPtr Strategy; if (NoCaseCompare(StrategyClass, "Fixed") == 0) { Strategy = std::make_shared(); } else if (NoCaseCompare(StrategyClass, "Range") == 0) { Strategy = std::make_shared(); } else if (NoCaseCompare(StrategyClass, "TerrainTop") == 0) { Strategy = std::make_shared(); } else if (NoCaseCompare(StrategyClass, "TerrainOrOceanTop") == 0) { Strategy = std::make_shared(); } else { return nullptr; } // Initialize the strategy's parameters: AString Params; if (idxPipe < a_StrategyDesc.length()) { Params = a_StrategyDesc.substr(idxPipe + 1); } if (!Strategy->InitializeFromString(Params, a_LogWarnings)) { return nullptr; } return Strategy; }