// PrefabPiecePool.cpp // Implements the cPrefabPiecePool class that represents a cPiecePool descendant that uses cPrefab instances as the pieces #include "Globals.h" #include "PrefabPiecePool.h" #include "../Bindings/LuaState.h" #include "SelfTests.h" #include "WorldStorage/SchematicFileSerializer.h" #include "VerticalStrategy.h" #include "../StringCompression.h" // Conditionally log a warning #define CONDWARNING(ShouldLog, ...) \ if (ShouldLog) \ { \ LOGWARNING(__VA_ARGS__); \ } /** Returns the map of string => eMergeStrategy used when translating cubeset file merge strategies. */ static std::map & GetMergeStrategyMap(void) { static std::map msmap; if (msmap.empty()) { // This is the first use, initialize the map: msmap["msOverwrite"] = cBlockArea::msOverwrite; msmap["msFillAir"] = cBlockArea::msFillAir; msmap["msImprint"] = cBlockArea::msImprint; msmap["msLake"] = cBlockArea::msLake; msmap["msSpongePrint"] = cBlockArea::msSpongePrint; msmap["msDifference"] = cBlockArea::msDifference; msmap["msSimpleCompare"] = cBlockArea::msSimpleCompare; msmap["msMask"] = cBlockArea::msMask; } return msmap; } //////////////////////////////////////////////////////////////////////////////// // cPrefabPiecePool: cPrefabPiecePool::cPrefabPiecePool(void) { } cPrefabPiecePool::cPrefabPiecePool( const cPrefab::sDef * a_PieceDefs, size_t a_NumPieceDefs, const cPrefab::sDef * a_StartingPieceDefs, size_t a_NumStartingPieceDefs, int a_DefaultStartingPieceHeight ) { AddPieceDefs(a_PieceDefs, a_NumPieceDefs); if (a_StartingPieceDefs != nullptr) { AddStartingPieceDefs(a_StartingPieceDefs, a_NumStartingPieceDefs, a_DefaultStartingPieceHeight); } } cPrefabPiecePool::cPrefabPiecePool(const AString & a_FileName, bool a_LogWarnings) { LoadFromFile(a_FileName, a_LogWarnings); } cPrefabPiecePool::~cPrefabPiecePool() { Clear(); } void cPrefabPiecePool::Clear(void) { m_PiecesByConnector.clear(); for (cPieces::iterator itr = m_AllPieces.begin(), end = m_AllPieces.end(); itr != end; ++itr) { delete *itr; } m_AllPieces.clear(); for (cPieces::iterator itr = m_StartingPieces.begin(), end = m_StartingPieces.end(); itr != end; ++itr) { delete *itr; } m_StartingPieces.clear(); } void cPrefabPiecePool::AddPieceDefs(const cPrefab::sDef * a_PieceDefs, size_t a_NumPieceDefs) { ASSERT(a_PieceDefs != nullptr); for (size_t i = 0; i < a_NumPieceDefs; i++) { cPrefab * Prefab = new cPrefab(a_PieceDefs[i]); m_AllPieces.push_back(Prefab); AddToPerConnectorMap(Prefab); } } void cPrefabPiecePool::AddStartingPieceDefs( const cPrefab::sDef * a_StartingPieceDefs, size_t a_NumStartingPieceDefs, int a_DefaultPieceHeight ) { ASSERT(a_StartingPieceDefs != nullptr); auto verticalStrategy = CreateVerticalStrategyFromString(Printf("Fixed|%d", a_DefaultPieceHeight), false); for (size_t i = 0; i < a_NumStartingPieceDefs; i++) { cPrefab * Prefab = new cPrefab(a_StartingPieceDefs[i]); if (a_DefaultPieceHeight >= 0) { Prefab->SetVerticalStrategy(verticalStrategy); } m_StartingPieces.push_back(Prefab); } } bool cPrefabPiecePool::LoadFromFile(const AString & a_FileName, bool a_LogWarnings) { // Read the file into a string buffer, load from string: auto contents = cFile::ReadWholeFile(a_FileName); if (contents.empty()) { CONDWARNING(a_LogWarnings, "Cannot read data from file %s", a_FileName.c_str()); return false; } return LoadFromString(contents, a_FileName, a_LogWarnings); } bool cPrefabPiecePool::LoadFromString(const AString & a_Contents, const AString & a_FileName, bool a_LogWarnings) { // If the contents start with GZip signature, ungzip and retry: if (a_Contents.substr(0, 3) == "\x1f\x8b\x08") { AString Uncompressed; auto res = UncompressStringGZIP(a_Contents.data(), a_Contents.size(), Uncompressed); if (res == Z_OK) { return LoadFromString(Uncompressed, a_FileName, a_LogWarnings); } else { CONDWARNING(a_LogWarnings, "Failed to decompress Gzip data in file %s: %d", a_FileName.c_str(), res); return false; } } // Read the first 4 KiB of the file in order to auto-detect format: auto Header = a_Contents.substr(0, 4096); if (Header.find("CubesetFormatVersion =") != AString::npos) { return LoadFromCubeset(a_Contents, a_FileName, a_LogWarnings); } CONDWARNING(a_LogWarnings, "Cannot load prefabs from file %s, unknown file format", a_FileName.c_str()); return false; } bool cPrefabPiecePool::LoadFromCubeset(const AString & a_Contents, const AString & a_FileName, bool a_LogWarnings) { // Load the file in the Lua interpreter: cLuaState Lua(Printf("LoadablePiecePool %s", a_FileName.c_str())); Lua.Create(); if (!Lua.LoadString(a_Contents, a_FileName, a_LogWarnings)) { // Reason for failure has already been logged in LoadFile() return false; } // Check the version: int Version = 0; if (!Lua.GetNamedGlobal("Cubeset.Metadata.CubesetFormatVersion", Version)) { CONDWARNING(a_LogWarnings, "Cannot load cubeset %s, it doesn't contain version information.", a_FileName.c_str()); return false; } // Load the data, using the correct version loader: if (Version == 1) { return LoadFromCubesetVer1(a_FileName, Lua, a_LogWarnings); } // Unknown version: CONDWARNING(a_LogWarnings, "Cannot load cubeset %s, version (%d) not supported.", a_FileName.c_str(), Version); return false; } void cPrefabPiecePool::AddToPerConnectorMap(cPrefab * a_Prefab) { cPiece::cConnectors Connectors = (static_cast(a_Prefab))->GetConnectors(); for (cPiece::cConnectors::const_iterator itr = Connectors.begin(), end = Connectors.end(); itr != end; ++itr) { m_PiecesByConnector[itr->m_Type].push_back(a_Prefab); } } bool cPrefabPiecePool::LoadFromCubesetVer1(const AString & a_FileName, cLuaState & a_LuaState, bool a_LogWarnings) { // Load the metadata and apply the known ones: ReadPoolMetadataCubesetVer1(a_FileName, a_LuaState, a_LogWarnings); ApplyBaseMetadataCubesetVer1(a_FileName, a_LogWarnings); // Push the Cubeset.Pieces global value on the stack: auto pieces = a_LuaState.WalkToNamedGlobal("Cubeset.Pieces"); if (!pieces.IsValid() || !lua_istable(a_LuaState, -1)) { CONDWARNING(a_LogWarnings, "The cubeset file %s doesn't contain any pieces", a_FileName.c_str()); return false; } // Iterate over all items in the Cubeset.Pieces value: int idx = 1; bool res = true; while (true) { lua_pushinteger(a_LuaState, idx); // stk: [Pieces] [idx] lua_gettable(a_LuaState, -2); // stk: [Pieces] [PieceItem] if (!lua_istable(a_LuaState, -1)) { // The PieceItem is not present, we've iterated over all items lua_pop(a_LuaState, 1); // stk: [Pieces] break; } if (!LoadCubesetPieceVer1(a_FileName, a_LuaState, idx, a_LogWarnings)) { res = false; } lua_pop(a_LuaState, 1); // stk: [Pieces] idx += 1; } return res; } bool cPrefabPiecePool::LoadCubesetPieceVer1(const AString & a_FileName, cLuaState & a_LuaState, int a_PieceIndex, bool a_LogWarnings) { ASSERT(lua_istable(a_LuaState, -1)); // The piece name is optional, but useful for debugging messages: AString PieceName; if (!a_LuaState.GetNamedValue("OriginData.ExportName", PieceName)) { Printf(PieceName, "Piece #%d", a_PieceIndex); } // Read the hitbox dimensions: cCuboid Hitbox; if ( !a_LuaState.GetNamedValue("Hitbox.MinX", Hitbox.p1.x) || !a_LuaState.GetNamedValue("Hitbox.MinY", Hitbox.p1.y) || !a_LuaState.GetNamedValue("Hitbox.MinZ", Hitbox.p1.z) || !a_LuaState.GetNamedValue("Hitbox.MaxX", Hitbox.p2.x) || !a_LuaState.GetNamedValue("Hitbox.MaxY", Hitbox.p2.y) || !a_LuaState.GetNamedValue("Hitbox.MaxZ", Hitbox.p2.z) ) { CONDWARNING(a_LogWarnings, "Cannot load piece %s from file %s, it's missing hitbox information", PieceName.c_str(), a_FileName.c_str()); return false; } // Load the prefab data: auto prefab = LoadPrefabFromCubesetVer1(a_FileName, a_LuaState, PieceName, a_LogWarnings); if (prefab == nullptr) { return false; } prefab->SetHitBox(Hitbox); // Read the connectors if (!ReadConnectorsCubesetVer1(a_FileName, a_LuaState, PieceName, prefab.get(), a_LogWarnings)) { return false; } // Read the allowed rotations. It is an optional metadata value, default to 0: int AllowedRotations = 0; a_LuaState.GetNamedValue("Metadata.AllowedRotations", AllowedRotations); prefab->SetAllowedRotations(AllowedRotations); // Read the relevant metadata for the piece: if (!ReadPieceMetadataCubesetVer1(a_FileName, a_LuaState, PieceName, prefab.get(), a_LogWarnings)) { return false; } // If the piece is a starting piece, check that it has a vertical strategy: int IsStartingPiece = 0; a_LuaState.GetNamedValue("Metadata.IsStarting", IsStartingPiece); if (IsStartingPiece != 0) { if (prefab->GetVerticalStrategy() == nullptr) { CONDWARNING(a_LogWarnings, "Starting prefab %s in file %s doesn't have its VerticalStrategy set. Setting to Fixed|150.", PieceName.c_str(), a_FileName.c_str() ); VERIFY(prefab->SetVerticalStrategyFromString("Fixed|150", false)); } } // Add the prefab into the list of pieces: if (IsStartingPiece != 0) { m_StartingPieces.push_back(prefab.release()); } else { auto p = prefab.release(); m_AllPieces.push_back(p); AddToPerConnectorMap(p); } return true; } UniquePtr cPrefabPiecePool::LoadPrefabFromCubesetVer1( const AString & a_FileName, cLuaState & a_LuaState, const AString & a_PieceName, bool a_LogWarnings ) { // First try loading a referenced schematic file, if any: AString SchematicFileName; if (a_LuaState.GetNamedValue("SchematicFileName", SchematicFileName)) { auto PathEnd = a_FileName.find_last_of("/\\"); // Find the last path separator if (PathEnd != AString::npos) { SchematicFileName = a_FileName.substr(0, PathEnd) + SchematicFileName; } cBlockArea area; if (!cSchematicFileSerializer::LoadFromSchematicFile(area, SchematicFileName)) { CONDWARNING(a_LogWarnings, "Cannot load schematic file \"%s\" for piece %s in cubeset %s.", SchematicFileName.c_str(), a_PieceName.c_str(), a_FileName.c_str() ); return nullptr; } return cpp14::make_unique(area); } // if (SchematicFileName) // There's no referenced schematic file, load from BlockDefinitions / BlockData. // Get references to the data and the table.concat function: cLuaState::cRef TableConcat, BlockDefinitions, BlockData; if ( !a_LuaState.GetNamedGlobal("table.concat", TableConcat) || !a_LuaState.GetNamedValue("BlockDefinitions", BlockDefinitions) || !a_LuaState.GetNamedValue("BlockData", BlockData) ) { CONDWARNING(a_LogWarnings, "Cannot parse block data for piece %s in cubeset %s", a_PieceName.c_str(), a_FileName.c_str()); return nullptr; } // Call table.concat() on the BlockDefinitions: AString BlockDefStr; if (!a_LuaState.Call(TableConcat, BlockDefinitions, "\n", cLuaState::Return, BlockDefStr)) { CONDWARNING(a_LogWarnings, "Cannot concat block definitions for piece %s in cubeset %s", a_PieceName.c_str(), a_FileName.c_str()); return nullptr; } // Call table.concat() on the BlockData: AString BlockDataStr; if (!a_LuaState.Call(TableConcat, BlockData, "", cLuaState::Return, BlockDataStr)) { CONDWARNING(a_LogWarnings, "Cannot concat block data for piece %s in cubeset %s", a_PieceName.c_str(), a_FileName.c_str()); return nullptr; } // Read the size: int SizeX = 0, SizeY = 0, SizeZ = 0; if ( !a_LuaState.GetNamedValue("Size.x", SizeX) || !a_LuaState.GetNamedValue("Size.y", SizeY) || !a_LuaState.GetNamedValue("Size.z", SizeZ) ) { CONDWARNING(a_LogWarnings, "Cannot load piece %s from file %s, its size information is missing", a_PieceName.c_str(), a_FileName.c_str()); return nullptr; } // Check that the size matches the data length: if (static_cast(SizeX * SizeY * SizeZ) != BlockDataStr.size()) { CONDWARNING(a_LogWarnings, "Cannot create piece %s from file %s, its size (%d) doesn't match the blockdata length (%u)", a_PieceName.c_str(), a_FileName.c_str(), SizeX * SizeY * SizeZ, static_cast(BlockDataStr.size()) ); return nullptr; } return cpp14::make_unique(BlockDefStr, BlockDataStr, SizeX, SizeY, SizeZ); } bool cPrefabPiecePool::ReadConnectorsCubesetVer1( const AString & a_FileName, cLuaState & a_LuaState, const AString & a_PieceName, cPrefab * a_Prefab, bool a_LogWarnings ) { // Get the Connectors subtable: auto conns = a_LuaState.WalkToValue("Connectors"); if (!conns.IsValid()) { CONDWARNING(a_LogWarnings, "Cannot load piece %s from file %s, it has no connectors definition.", a_PieceName.c_str(), a_FileName.c_str()); return false; } // Iterate over all items in the Connectors table: int idx = 1; bool res = true; while (true) { lua_pushinteger(a_LuaState, idx); // stk: [Connectors] [idx] lua_gettable(a_LuaState, -2); // stk: [Connectors] [conn] if (!lua_istable(a_LuaState, -1)) { // The connector is not present, we've iterated over all items lua_pop(a_LuaState, 1); // stk: [Connectors] break; } int Type = 0, RelX = 0, RelY = 0, RelZ = 0; eBlockFace Direction = BLOCK_FACE_NONE; if ( !a_LuaState.GetNamedValue("Type", Type) || !a_LuaState.GetNamedValue("RelX", RelX) || !a_LuaState.GetNamedValue("RelY", RelY) || !a_LuaState.GetNamedValue("RelZ", RelZ) || !a_LuaState.GetNamedValue("Direction", Direction) ) { CONDWARNING(a_LogWarnings, "Piece %s in file %s has a malformed Connector at index %d. Skipping the connector.", a_PieceName.c_str(), a_FileName.c_str(), idx); res = false; continue; } a_Prefab->AddConnector(RelX, RelY, RelZ, Direction, Type); lua_pop(a_LuaState, 1); // stk: [Connectors] idx += 1; } return res; } bool cPrefabPiecePool::ReadPieceMetadataCubesetVer1( const AString & a_FileName, cLuaState & a_LuaState, const AString & a_PieceName, cPrefab * a_Prefab, bool a_LogWarnings ) { // Push the Metadata table on top of the Lua stack: auto md = a_LuaState.WalkToValue("Metadata"); if (!md.IsValid()) { return false; } // Get the values: int AddWeightIfSame = 0, DefaultWeight = 100, MoveToGround = 0, ShouldExpandFloor = 0; AString DepthWeight, MergeStrategy, VerticalLimit, VerticalStrategy; a_LuaState.GetNamedValue("AddWeightIfSame", AddWeightIfSame); a_LuaState.GetNamedValue("DefaultWeight", DefaultWeight); a_LuaState.GetNamedValue("DepthWeight", DepthWeight); a_LuaState.GetNamedValue("MergeStrategy", MergeStrategy); a_LuaState.GetNamedValue("MoveToGround", MoveToGround); a_LuaState.GetNamedValue("ShouldExpandFloor", ShouldExpandFloor); a_LuaState.GetNamedValue("VerticalLimit", VerticalLimit); a_LuaState.GetNamedValue("VerticalStrategy", VerticalStrategy); // Apply the values: a_Prefab->SetAddWeightIfSame(AddWeightIfSame); a_Prefab->SetDefaultWeight(DefaultWeight); a_Prefab->ParseDepthWeight(DepthWeight.c_str()); auto msmap = GetMergeStrategyMap(); auto strategy = msmap.find(MergeStrategy); if (strategy == msmap.end()) { CONDWARNING(a_LogWarnings, "Unknown merge strategy (\"%s\") specified for piece %s in file %s. Using msSpongePrint instead.", MergeStrategy.c_str(), a_PieceName.c_str(), a_FileName.c_str() ); a_Prefab->SetMergeStrategy(cBlockArea::msSpongePrint); } else { a_Prefab->SetMergeStrategy(strategy->second); } a_Prefab->SetMoveToGround(MoveToGround != 0); a_Prefab->SetExtendFloor(ShouldExpandFloor != 0); if (!VerticalLimit.empty()) { if (!a_Prefab->SetVerticalLimitFromString(VerticalLimit, a_LogWarnings)) { CONDWARNING(a_LogWarnings, "Unknown VerticalLimit (\"%s\") specified for piece %s in file %s. Using no limit instead.", VerticalLimit.c_str(), a_PieceName.c_str(), a_FileName.c_str() ); } } a_Prefab->SetVerticalStrategyFromString(VerticalStrategy, a_LogWarnings); return true; } void cPrefabPiecePool::ApplyBaseMetadataCubesetVer1( const AString & a_FileName, bool a_LogWarnings ) { // Set the metadata values to defaults: m_MinDensity = 100; m_MaxDensity = 100; m_VillageRoadBlockType = E_BLOCK_GRAVEL; m_VillageRoadBlockMeta = 0; m_VillageWaterRoadBlockType = E_BLOCK_PLANKS; m_VillageWaterRoadBlockMeta = 0; // Read the metadata values: m_IntendedUse = GetMetadata("IntendedUse"); GetStringMapInteger(m_Metadata, "MaxDensity", m_MaxDensity); GetStringMapInteger(m_Metadata, "MinDensity", m_MinDensity); GetStringMapInteger(m_Metadata, "VillageRoadBlockType", m_VillageRoadBlockType); GetStringMapInteger(m_Metadata, "VillageRoadBlockMeta", m_VillageRoadBlockMeta); GetStringMapInteger(m_Metadata, "VillageWaterRoadBlockType", m_VillageWaterRoadBlockType); GetStringMapInteger(m_Metadata, "VillageWaterRoadBlockMeta", m_VillageWaterRoadBlockMeta); // Read the allowed biomes: AString allowedBiomes = GetMetadata("AllowedBiomes"); if (allowedBiomes.empty()) { // All biomes are allowed: for (int b = biFirstBiome; b <= biMaxBiome; b++) { m_AllowedBiomes.insert(static_cast(b)); } for (int b = biFirstVariantBiome; b <= biMaxVariantBiome; b++) { m_AllowedBiomes.insert(static_cast(b)); } } else { auto biomes = StringSplitAndTrim(allowedBiomes, ","); for (const auto & biome: biomes) { EMCSBiome b = StringToBiome(biome); if (b == biInvalidBiome) { CONDWARNING(a_LogWarnings, "Invalid biome (\"%s\") specified in AllowedBiomes in cubeset file %s. Skipping the biome.", biome.c_str(), a_FileName.c_str() ); continue; } m_AllowedBiomes.insert(b); } } } bool cPrefabPiecePool::ReadPoolMetadataCubesetVer1( const AString & a_FileName, cLuaState & a_LuaState, bool a_LogWarnings ) { // Push the Cubeset.Metadata table on top of the Lua stack: auto gp = a_LuaState.WalkToNamedGlobal("Cubeset.Metadata"); if (!gp.IsValid()) { return true; } // Iterate over elements in the table, put them into the m_GeneratorParams map: lua_pushnil(a_LuaState); // Table is at index -2, starting key (nil) at index -1 while (lua_next(a_LuaState, -2) != 0) { // Table at index -3, key at index -2, value at index -1 AString key, val; a_LuaState.GetStackValues(-2, key, val); m_Metadata[key] = val; lua_pop(a_LuaState, 1); // Table at index -2, key at index -1 } return true; } AString cPrefabPiecePool::GetMetadata(const AString & a_ParamName) const { auto itr = m_Metadata.find(a_ParamName); if (itr == m_Metadata.end()) { return AString(); } return itr->second; } void cPrefabPiecePool::AssignGens(int a_Seed, cBiomeGenPtr & a_BiomeGen, cTerrainHeightGenPtr & a_HeightGen, int a_SeaLevel) { // Assign the generator linkage to all starting pieces' VerticalStrategies: for (auto & piece: m_StartingPieces) { auto verticalStrategy = piece->GetVerticalStrategy(); if (verticalStrategy != nullptr) { verticalStrategy->AssignGens(a_Seed, a_BiomeGen, a_HeightGen, a_SeaLevel); } } // for piece - m_StartingPieces[] // Assign the generator linkage to all pieces' VerticalLimits: for (auto & piece: m_AllPieces) { auto verticalLimit = piece->GetVerticalLimit(); if (verticalLimit != nullptr) { verticalLimit->AssignGens(a_Seed, a_BiomeGen, a_HeightGen, a_SeaLevel); } } // for piece - m_AllPieces[] } cPieces cPrefabPiecePool::GetPiecesWithConnector(int a_ConnectorType) { return m_PiecesByConnector[a_ConnectorType]; } cPieces cPrefabPiecePool::GetStartingPieces(void) { if (m_StartingPieces.empty()) { return m_AllPieces; } else { return m_StartingPieces; } } int cPrefabPiecePool::GetPieceWeight(const cPlacedPiece & a_PlacedPiece, const cPiece::cConnector & a_ExistingConnector, const cPiece & a_NewPiece) { return (reinterpret_cast(a_NewPiece)).GetPieceWeight(a_PlacedPiece, a_ExistingConnector); } int cPrefabPiecePool::GetStartingPieceWeight(const cPiece & a_NewPiece) { return (reinterpret_cast(a_NewPiece)).GetDefaultWeight(); } void cPrefabPiecePool::PiecePlaced(const cPiece & a_Piece) { // Do nothing UNUSED(a_Piece); } void cPrefabPiecePool::Reset(void) { // Do nothing }