diff --git a/src/Generating/CMakeLists.txt b/src/Generating/CMakeLists.txt index e299c97d7..df1726675 100644 --- a/src/Generating/CMakeLists.txt +++ b/src/Generating/CMakeLists.txt @@ -18,6 +18,7 @@ target_sources( MineShafts.cpp Noise3DGenerator.cpp PieceGeneratorBFSTree.cpp + PieceModifier.cpp PiecePool.cpp PieceStructuresGen.cpp Prefab.cpp @@ -52,6 +53,7 @@ target_sources( MineShafts.h Noise3DGenerator.h PieceGeneratorBFSTree.h + PieceModifier.h PiecePool.h PieceStructuresGen.h Prefab.h diff --git a/src/Generating/PieceModifier.cpp b/src/Generating/PieceModifier.cpp new file mode 100644 index 000000000..54d6a2c21 --- /dev/null +++ b/src/Generating/PieceModifier.cpp @@ -0,0 +1,402 @@ + +// PieceModifier.cpp + +// Implements the various classes descending from cPiece::cPieceModifier + +#include "Globals.h" +#include "PieceModifier.h" +#include "../Noise/Noise.h" + + + + + +// Constant that is added to seed +static const int SEED_OFFSET = 135 * 13; + + + + + +// Emit a warning if the first param is true +#define CONDWARNING(ShouldLog, Fmt, ...) \ + do { \ + if (ShouldLog) \ + { \ + LOGWARNING(Fmt, __VA_ARGS__); \ + } \ + } while (false) + + + + + +//////////////////////////////////////////////////////////////////////////////// +/** A modifier which is pseudo-randomly replacing blocks to other types and metas. */ +class cPieceModifierRandomizeBlocks: + public cPiece::cPieceModifier +{ +public: + cPieceModifierRandomizeBlocks(void) : + m_Seed() + { + } + + virtual bool InitializeFromString(const AString & a_Params, bool a_LogWarnings) override + { + m_AllWeights = 0; + AString Params = a_Params; + + + /** BlocksToReplace parsing */ + auto idxPipe = Params.find('|'); + if (idxPipe != AString::npos) + { + AString blocksToReplaceStr = Params.substr(0, idxPipe); + auto blocksToReplace = StringSplitAndTrim(blocksToReplaceStr, ","); + for (size_t i = 0; i < blocksToReplace.size(); i++) + { + BLOCKTYPE blockType = static_cast(BlockStringToType(blocksToReplace[i])); + if ((blockType == E_BLOCK_AIR) && !NoCaseCompare(blocksToReplace[i], "Air")) + { + CONDWARNING(a_LogWarnings, "Cannot parse block type from string \"%s\"!", blocksToReplace[i].c_str()); + return false; + } + m_BlocksToReplace[blockType] = 1; + } + + Params = Params.substr(idxPipe + 1, Params.length() - 1); + } + if (m_BlocksToReplace.size() == 0) + { + CONDWARNING(a_LogWarnings, "You must specify at least one block to replace%s!", ""); + return false; + } + + + /** Meta params parsing */ + auto idxSqBracketStart = Params.find('['); + auto idxSqBracketStartLast = Params.find_last_of('['); + + bool isMultiMeta = false; + if ((idxSqBracketStart != idxSqBracketStartLast) && (idxSqBracketStartLast != Params.length() - 1)) + { + // Meta per block type + isMultiMeta = true; + } + else + { + auto idxSqBracketEnd = Params.find(']'); + if ((idxSqBracketEnd == Params.length() - 1) && (idxSqBracketStart != AString::npos)) + { + AString metaParamsStr = Params.substr(idxSqBracketStart + 1, Params.length() - idxSqBracketStart - 2); + std::array metaParamsInt; + if (!ParseMeta(metaParamsStr, metaParamsInt, a_LogWarnings)) + { + return false; + } + + m_MinMeta = metaParamsInt[0]; + m_MaxMeta = metaParamsInt[1]; + m_MinNoiseMeta = metaParamsInt[2]; + m_MaxNoiseMeta = metaParamsInt[3]; + + Params = Params.substr(0, idxSqBracketStart); + } + } + + + // BlocksToRandomize parsing + auto BlocksToRandomize = StringSplitAndTrim(Params, ";"); + for (size_t i = 0; i < BlocksToRandomize.size(); i++) + { + AString block = BlocksToRandomize[i]; + + cRandomizedBlock Block{}; + + if (isMultiMeta) + { + auto sqBrStart = block.find('['); + if (sqBrStart != AString::npos) + { + auto sqBrEnd = block.find(']'); + if (sqBrEnd != block.size() - 1) + { + CONDWARNING(a_LogWarnings, "If present, block meta params must be at the end of block to randomize definition \"%s\"!", block.c_str()); + return false; + + } + AString metaParamsStr = block.substr(sqBrStart + 1, block.size() - sqBrStart - 2); + + std::array metaParamsInt; + if (!ParseMeta(metaParamsStr, metaParamsInt, a_LogWarnings)) + { + return false; + } + + Block.m_MinMeta = metaParamsInt[0]; + Block.m_MaxMeta = metaParamsInt[1]; + Block.m_MinNoiseMeta = metaParamsInt[2]; + Block.m_MaxNoiseMeta = metaParamsInt[3]; + + block = block.substr(0, sqBrStart); + + } + // No meta randomization for this block + } + + auto BlockParams = StringSplitAndTrim(block, ","); + if (BlockParams.size() < 2) + { + CONDWARNING(a_LogWarnings, "Block weight is required param \"%s\"!", BlockParams[0].c_str()); + return false; + } + + BLOCKTYPE BlockType = static_cast(BlockStringToType(BlockParams[0])); + int BlockWeight = 0; + if ((BlockType != E_BLOCK_AIR) && !NoCaseCompare(BlockParams[0], "Air")) + { + // Failed to parse block type + CONDWARNING( + a_LogWarnings, "Cannot parse block type from string \"%s\"!", + BlockParams[0].c_str()); + return false; + } + + if (!StringToInteger(BlockParams[1], BlockWeight)) + { + // Failed to parse the crop weight: + CONDWARNING( + a_LogWarnings, + "Cannot parse block weight from string \"%s\"!", + BlockParams[1].c_str()); + return false; + } + + + Block.m_Type = BlockType; + Block.m_Weight = BlockWeight; + m_AllWeights += BlockWeight; + + m_BlocksToRandomize.push_back(Block); + } + if (m_BlocksToRandomize.size() == 0) + { + CONDWARNING(a_LogWarnings, "You must specify at least one block to randomize%s!", ""); + return false; + } + + return true; + } + + + + + + bool ParseMeta(const AString & a_Meta, std::array & a_ParsedMeta, bool a_LogWarnings) + { + auto MetaParams = StringSplitAndTrim(a_Meta, ","); + + for (size_t i = 0; i < MetaParams.size(); i++) + { + int Value; + if (!StringToInteger(MetaParams[i], Value)) + { + // Failed to parse meta parameter from string: + CONDWARNING(a_LogWarnings, "Cannot parse meta param from string \"%s\", meta: %s!", MetaParams[i].c_str(), a_Meta.c_str()); + return false; + } + + if (i > 3) + { + CONDWARNING(a_LogWarnings, "Unsupported meta param \"%d\"!", Value); + return false; + } + + a_ParsedMeta[i] = Value; + } + + if (MetaParams.size() == 1) + { + // Noise is not used for meta + a_ParsedMeta[1] = a_ParsedMeta[0]; + } + else if (MetaParams.size() == 2) + { + // Noise range is same as meta range + a_ParsedMeta[2] = a_ParsedMeta[0]; + a_ParsedMeta[3] = a_ParsedMeta[1]; + } + else if (MetaParams.size() == 3) + { + a_ParsedMeta[3] = a_ParsedMeta[1]; + } + + return true; + } + + + + + + virtual void Modify(cBlockArea & a_Image, const Vector3i a_PiecePos, const int a_PieceRot) override + { + cNoise Noise(m_Seed); + cNoise PieceNoise(Noise.IntNoise3DInt(a_PiecePos)); + + size_t NumBlocks = a_Image.GetBlockCount(); + BLOCKTYPE * BlockTypes = a_Image.GetBlockTypes(); + BLOCKTYPE * BlockMetas = a_Image.GetBlockMetas(); + + for (size_t i = 0; i < NumBlocks; i++) + { + if (m_BlocksToReplace.count(BlockTypes[i])) + { + float BlockRnd = PieceNoise.IntNoise2DInRange(a_PieceRot, static_cast(i), 0, m_AllWeights); + + int weightDelta = 0; + for (auto & blockToRnd : m_BlocksToRandomize) + { + weightDelta += blockToRnd.m_Weight; + if (BlockRnd <= weightDelta) + { + BlockTypes[i] = blockToRnd.m_Type; + + // Per block meta params + if (blockToRnd.m_MinMeta < blockToRnd.m_MaxMeta) + { + int BlockMetaRnd = std::clamp(static_cast(PieceNoise.IntNoise2DInRange(a_PieceRot*2, static_cast(i), blockToRnd.m_MinNoiseMeta, blockToRnd.m_MaxNoiseMeta)), blockToRnd.m_MinMeta, blockToRnd.m_MaxMeta); + BlockMetas[i] = static_cast(BlockMetaRnd); + } + else if ((blockToRnd.m_MaxMeta > -1) && (blockToRnd.m_MaxMeta == blockToRnd.m_MinMeta)) + { + // Change meta if at least minimum meta was specified + BlockMetas[i] = static_cast(blockToRnd.m_MaxMeta); + } + break; + } + } + + // All blocks meta params + if (m_MaxMeta > m_MinMeta) + { + int BlockMetaRnd = std::clamp(static_cast(PieceNoise.IntNoise2DInRange(a_PieceRot*2, static_cast(i), m_MinNoiseMeta, m_MaxNoiseMeta)), m_MinMeta, m_MaxMeta); + BlockMetas[i] = static_cast(BlockMetaRnd); + } + else if ((m_MaxMeta > -1) && (m_MaxMeta == m_MinMeta)) + { + // Change meta if at least minimum meta was specified + BlockMetas[i] = static_cast(m_MaxMeta); + } + } + } // for i - BlockTypes[] + } + + virtual void AssignSeed(int a_Seed) override + { + m_Seed = a_Seed + SEED_OFFSET; + } +protected: + int m_Seed; + int m_AllWeights = 0; + + + /** Block types of a blocks which are being replaced by this strategy */ + std::map m_BlocksToReplace; + + /** Randomized blocks with their weights and meta params */ + cRandomizedBlocks m_BlocksToRandomize; + + /** Minimum meta to randomize */ + int m_MinMeta = 0; + + /** Maximum meta to randomize */ + int m_MaxMeta = -1; + + /** Maximum meta in noise range */ + int m_MaxNoiseMeta = 0; + + /** Minimum meta in noise range */ + int m_MinNoiseMeta = 0; +}; + + + + + +//////////////////////////////////////////////////////////////////////////////// +// CreatePieceModifierFromString: + +bool CreatePieceModifierFromString(const AString & a_Definition, std::shared_ptr & a_Modifiers, bool a_LogWarnings) +{ + + auto idxCurlyStart = a_Definition.find('{'); + auto idxCurlyFirstEnd = a_Definition.find('}'); + if ((idxCurlyStart == AString::npos) && (idxCurlyFirstEnd == AString::npos)) + { + CONDWARNING(a_LogWarnings, "Piece metadata \"Modifiers\" needs at least one valid modifier definition \"%s\"!", a_Definition.c_str()); + return false; + } + + auto modifiersStr = StringSplitAndTrim(a_Definition, "{"); + + for (size_t i = 0; i < modifiersStr.size(); i++) + { + AString modifierStr = TrimString(modifiersStr[i]); + + if (modifierStr.size() == 0) + { + continue; + } + auto idxCurlyEnd = modifierStr.find('}'); + if (idxCurlyEnd == AString::npos) + { + CONDWARNING(a_LogWarnings, "Modifier definition must end with curly bracket \"%s\"!!", modifierStr.c_str()); + return false; + } + + modifierStr = modifierStr.substr(0, idxCurlyEnd); + + // Break apart the modifier class, the first parameter before the first pipe char: + auto idxPipe = modifierStr.find('|'); + if (idxPipe == AString::npos) + { + idxPipe = modifierStr.length(); + } + AString ModifierClass = modifierStr.substr(0, idxPipe); + + // Create a modifier class based on the class string: + cPiece::cPieceModifierPtr Modifier; + if (NoCaseCompare(ModifierClass, "RandomizeBlocks") == 0) + { + Modifier = std::make_shared(); + } + + if (Modifier == nullptr) + { + CONDWARNING(a_LogWarnings, "Unknown modifier class \"%s\" %s!", ModifierClass.c_str(), modifierStr.c_str()); + return false; + } + + // Initialize the modifier's parameters: + AString Params; + if (idxPipe < modifierStr.length()) + { + Params = modifierStr.substr(idxPipe + 1); + } + + if (!Modifier->InitializeFromString(Params, a_LogWarnings)) + { + CONDWARNING(a_LogWarnings, "InitializeFromString error \"%s\" -- %!", Params.c_str(), modifierStr.c_str()); + return false; + } + + a_Modifiers->push_back(Modifier); + } + + return true; +} + + + + diff --git a/src/Generating/PieceModifier.h b/src/Generating/PieceModifier.h new file mode 100644 index 000000000..88fba5070 --- /dev/null +++ b/src/Generating/PieceModifier.h @@ -0,0 +1,45 @@ + +// PieceModifier.h + +// Declares the public interface for cPiece's cPieceModifier implementations + + + + + +#pragma once + +#include "PiecePool.h" + + + + + +bool CreatePieceModifierFromString(const AString & a_Definition, std::shared_ptr & a_Modifiers, bool a_LogWarnings); + + + + + +/** Used to store block type, meta, weight and some more params */ +class cRandomizedBlock +{ +public: + BLOCKTYPE m_Type; + + int m_Weight; + + /** Minimum meta to randomize */ + int m_MinMeta = 0; + + /** Maximum meta to randomize */ + int m_MaxMeta = -1; + + /** Maximum meta in noise range */ + int m_MaxNoiseMeta = 0; + + /** Minimum meta in noise range */ + int m_MinNoiseMeta = 0; +}; + +typedef std::vector cRandomizedBlocks; diff --git a/src/Generating/PiecePool.cpp b/src/Generating/PiecePool.cpp index 4b4303516..a7c28e75a 100644 --- a/src/Generating/PiecePool.cpp +++ b/src/Generating/PiecePool.cpp @@ -8,6 +8,7 @@ #include "PiecePool.h" #include "VerticalStrategy.h" #include "VerticalLimit.h" +#include "PieceModifier.h" @@ -46,6 +47,29 @@ bool cPiece::SetVerticalLimitFromString(const AString & a_LimitDesc, bool a_LogW +bool cPiece::SetPieceModifiersFromString(const AString & a_Definition, bool a_LogWarnings) +{ + auto modifiers = std::make_shared(); + if (!CreatePieceModifierFromString(a_Definition, modifiers, a_LogWarnings)) + { + return false; + } + + cPieceModifiers Modifiers; + for (size_t i = 0; i < modifiers->size(); i++) + { + Modifiers.push_back(std::move(modifiers->at(i))); + } + + m_Modifiers = Modifiers; + + return true; +} + + + + + Vector3i cPiece::RotatePos(const Vector3i & a_Pos, int a_NumCCWRotations) const { Vector3i Size = GetSize(); diff --git a/src/Generating/PiecePool.h b/src/Generating/PiecePool.h index 8beaf0c31..5897f32c7 100644 --- a/src/Generating/PiecePool.h +++ b/src/Generating/PiecePool.h @@ -148,12 +148,41 @@ public: typedef std::shared_ptr cVerticalLimitPtr; + /** Base class (interface) for piece modifiers. */ + class cPieceModifier + { + public: + // Force a virtual destructor in descendants + virtual ~cPieceModifier() {} + + /** Initializes the modifier's parameters from the string + representation. a_Params is the string containing only the parameters + If a_LogWarnings is true, logs any problems to the console. + Returns true if successful, false if the string parsing failed. + Used when loading the modifier from a file. */ + virtual bool InitializeFromString(const AString & a_Params, bool a_LogWarnings) = 0; + + /** Called prior to writing piece to a chunk, so that modifiers can modify blocks in the blockarea */ + virtual void Modify(cBlockArea & a_Image, const Vector3i a_PiecePos, const int a_PieceNumRotations) = 0; + + /** Called when the piece pool is assigned to a generator, + so that the modifiers can access world seed. */ + virtual void AssignSeed(int a_Seed) {} + }; + + + typedef std::shared_ptr cPieceModifierPtr; + + typedef std::vector cPieceModifiers; + /** The strategy used for vertical placement of this piece when it is used as a starting piece. */ cVerticalStrategyPtr m_VerticalStrategy; /** The checker that verifies each placement's vertical position. */ cVerticalLimitPtr m_VerticalLimit; + /** The modifiers which are modifying piece's blocks. */ + cPieceModifiers m_Modifiers; /** Returns all of the available connectors that the piece has. Each connector has a (relative) position in the piece, and a type associated with it. */ @@ -196,6 +225,11 @@ public: return m_VerticalLimit; } + cPieceModifiers GetModifiers(void) const + { + return m_Modifiers; + } + /** Sets the vertical strategy based on the description in the string. If a_LogWarnings is true, logs the parsing problems into the server console. Returns true if successful, false if strategy parsing failed (no strategy assigned). */ @@ -206,6 +240,12 @@ public: If a_LogWarnings is true, any problem is reported into the server console. */ bool SetVerticalLimitFromString(const AString & a_LimitDesc, bool a_LogWarnings); + /** Sets the modifiers with their params in the string. + If a_LogWarnings is true, logs the parsing problems into the server console. + Returns true if successful, false if strategy parsing failed (no strategy + assigned). */ + bool SetPieceModifiersFromString(const AString & a_Definition, bool a_LogWarnings); + /** Returns a copy of the a_Pos after rotating the piece the specified number of CCW rotations. */ Vector3i RotatePos(const Vector3i & a_Pos, int a_NumCCWRotations) const; diff --git a/src/Generating/Prefab.cpp b/src/Generating/Prefab.cpp index 7c8c3c0bc..0a7b2a89b 100644 --- a/src/Generating/Prefab.cpp +++ b/src/Generating/Prefab.cpp @@ -157,8 +157,24 @@ void cPrefab::Draw(cChunkDesc & a_Dest, const Vector3i & a_Placement, int a_NumR return; } - // Write the image: - a_Dest.WriteBlockArea(Image, Placement.x, Placement.y, Placement.z, m_MergeStrategy); + if (m_Modifiers.size() == 0) + { + // Write the image: + a_Dest.WriteBlockArea(Image, Placement.x, Placement.y, Placement.z, m_MergeStrategy); + } + else + { + cBlockArea RandomizedImage; + Image.CopyTo(RandomizedImage); + + for (size_t i = 0; i < m_Modifiers.size(); i++) + { + m_Modifiers[i]->Modify(RandomizedImage, a_Placement, a_NumRotations); + } + + // Write the modified image: + a_Dest.WriteBlockArea(RandomizedImage, Placement.x, Placement.y, Placement.z, m_MergeStrategy); + } // If requested, draw the floor (from the bottom of the prefab down to the nearest non-air) switch (m_ExtendFloorStrategy) diff --git a/src/Generating/PrefabPiecePool.cpp b/src/Generating/PrefabPiecePool.cpp index 88e670026..67d464d13 100644 --- a/src/Generating/PrefabPiecePool.cpp +++ b/src/Generating/PrefabPiecePool.cpp @@ -615,6 +615,12 @@ bool cPrefabPiecePool::ReadPieceMetadataCubesetVer1( } a_Prefab->SetVerticalStrategyFromString(VerticalStrategy, a_LogWarnings); + AString ModifiersStr; + if (a_LuaState.GetNamedValue("Modifiers", ModifiersStr)) + { + a_Prefab->SetPieceModifiersFromString(ModifiersStr, a_LogWarnings); + } + return true; } @@ -744,6 +750,14 @@ void cPrefabPiecePool::AssignGens(int a_Seed, cBiomeGen & a_BiomeGen, cTerrainHe { verticalLimit->AssignGens(a_Seed, a_BiomeGen, a_HeightGen, a_SeaLevel); } + auto modifiers = piece->GetModifiers(); + if (modifiers.size() > 0) + { + for (size_t i = 0; i < modifiers.size(); i++) + { + modifiers[i]->AssignSeed(a_Seed); + } + } } // for piece - m_AllPieces[] } diff --git a/tests/Generating/CMakeLists.txt b/tests/Generating/CMakeLists.txt index d6b5617c3..73352d34b 100644 --- a/tests/Generating/CMakeLists.txt +++ b/tests/Generating/CMakeLists.txt @@ -51,6 +51,7 @@ set (GENERATING_SRCS ${PROJECT_SOURCE_DIR}/src/Generating/MineShafts.cpp ${PROJECT_SOURCE_DIR}/src/Generating/Noise3DGenerator.cpp ${PROJECT_SOURCE_DIR}/src/Generating/PieceGeneratorBFSTree.cpp + ${PROJECT_SOURCE_DIR}/src/Generating/PieceModifier.cpp ${PROJECT_SOURCE_DIR}/src/Generating/PiecePool.cpp ${PROJECT_SOURCE_DIR}/src/Generating/PieceStructuresGen.cpp ${PROJECT_SOURCE_DIR}/src/Generating/Prefab.cpp @@ -119,6 +120,7 @@ set (GENERATING_HDRS ${PROJECT_SOURCE_DIR}/src/Generating/MineShafts.h ${PROJECT_SOURCE_DIR}/src/Generating/Noise3DGenerator.h ${PROJECT_SOURCE_DIR}/src/Generating/PieceGeneratorBFSTree.h + ${PROJECT_SOURCE_DIR}/src/Generating/PieceModifier.h ${PROJECT_SOURCE_DIR}/src/Generating/PiecePool.h ${PROJECT_SOURCE_DIR}/src/Generating/PieceStructuresGen.h ${PROJECT_SOURCE_DIR}/src/Generating/Prefab.h diff --git a/tests/LuaThreadStress/CMakeLists.txt b/tests/LuaThreadStress/CMakeLists.txt index 83bd8a0ca..bd6b40f21 100644 --- a/tests/LuaThreadStress/CMakeLists.txt +++ b/tests/LuaThreadStress/CMakeLists.txt @@ -14,6 +14,7 @@ set (SHARED_SRCS ${PROJECT_SOURCE_DIR}/src/Bindings/LuaState.cpp ${PROJECT_SOURCE_DIR}/src/Generating/ChunkDesc.cpp + ${PROJECT_SOURCE_DIR}/src/Generating/PieceModifier.cpp ${PROJECT_SOURCE_DIR}/src/Generating/PiecePool.cpp ${PROJECT_SOURCE_DIR}/src/Generating/Prefab.cpp ${PROJECT_SOURCE_DIR}/src/Generating/PrefabPiecePool.cpp @@ -45,6 +46,7 @@ set (SHARED_HDRS ${PROJECT_SOURCE_DIR}/src/Bindings/LuaState.h ${PROJECT_SOURCE_DIR}/src/Generating/ChunkDesc.h + ${PROJECT_SOURCE_DIR}/src/Generating/PieceModifier.h ${PROJECT_SOURCE_DIR}/src/Generating/PiecePool.h ${PROJECT_SOURCE_DIR}/src/Generating/Prefab.h ${PROJECT_SOURCE_DIR}/src/Generating/PrefabPiecePool.h diff --git a/tests/LuaThreadStress/Stubs.cpp b/tests/LuaThreadStress/Stubs.cpp index e77b02038..8f374900f 100644 --- a/tests/LuaThreadStress/Stubs.cpp +++ b/tests/LuaThreadStress/Stubs.cpp @@ -297,3 +297,12 @@ bool cUUID::FromString(const AString &) { return true; } + + + + + +int BlockStringToType(const AString &) +{ + return 0; +}