diff options
Diffstat (limited to 'src/Generating/PrefabPiecePool.cpp')
-rw-r--r-- | src/Generating/PrefabPiecePool.cpp | 494 |
1 files changed, 491 insertions, 3 deletions
diff --git a/src/Generating/PrefabPiecePool.cpp b/src/Generating/PrefabPiecePool.cpp index 895555ef8..e2273aed3 100644 --- a/src/Generating/PrefabPiecePool.cpp +++ b/src/Generating/PrefabPiecePool.cpp @@ -5,6 +5,54 @@ #include "Globals.h" #include "PrefabPiecePool.h" +#include "../Bindings/LuaState.h" +#include "SelfTests.h" +#include "WorldStorage/SchematicFileSerializer.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<AString, cBlockArea::eMergeStrategy> & GetMergeStrategyMap(void) +{ + static std::map<AString, cBlockArea::eMergeStrategy> 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) +{ +} @@ -26,6 +74,15 @@ cPrefabPiecePool::cPrefabPiecePool( +cPrefabPiecePool::cPrefabPiecePool(const AString & a_FileName, bool a_LogWarnings) +{ + LoadFromFile(a_FileName, a_LogWarnings); +} + + + + + cPrefabPiecePool::~cPrefabPiecePool() { Clear(); @@ -83,9 +140,69 @@ void cPrefabPiecePool::AddStartingPieceDefs(const cPrefab::sDef * a_StartingPiec +bool cPrefabPiecePool::LoadFromFile(const AString & a_FileName, bool a_LogWarnings) +{ + // Read the first 4 KiB of the file in order to auto-detect format: + cFile f; + if (!f.Open(a_FileName, cFile::fmRead)) + { + CONDWARNING(a_LogWarnings, "Cannot open file %s for reading", a_FileName.c_str()); + return false; + } + char buf[4096]; + auto len = f.Read(buf, sizeof(buf)); + f.Close(); + AString Header(buf, static_cast<size_t>(len)); + + if (Header.find("CubesetFormatVersion =") != AString::npos) + { + return LoadFromCubesetFile(a_FileName, a_LogWarnings); + } + CONDWARNING(a_LogWarnings, "Cannot load prefabs from file %s, unknown file format", a_FileName.c_str()); + return false; +} + + + + + +bool cPrefabPiecePool::LoadFromCubesetFile(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.LoadFile(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 LoadFromCubesetFileVer1(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 = ((const cPiece *)a_Prefab)->GetConnectors(); + cPiece::cConnectors Connectors = (static_cast<const cPiece *>(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); @@ -95,6 +212,377 @@ void cPrefabPiecePool::AddToPerConnectorMap(cPrefab * a_Prefab) + +bool cPrefabPiecePool::LoadFromCubesetFileVer1(const AString & a_FileName, cLuaState & a_LuaState, bool a_LogWarnings) +{ + // Load the metadata: + ApplyPoolMetadataCubesetVer1(a_FileName, a_LuaState, a_LogWarnings); + + // Push the Cubeset.Pieces global value on the stack: + lua_getglobal(a_LuaState, "_G"); + cLuaState::cStackValue stk(a_LuaState); + auto pieces = a_LuaState.WalkToValue("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; + } + + // 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); + + // Apply the relevant metadata: + if (!ApplyPieceMetadataCubesetVer1(a_FileName, a_LuaState, PieceName, prefab.get(), a_LogWarnings)) + { + return false; + } + + // Add the prefab into the list of pieces: + int IsStartingPiece = 0; + a_LuaState.GetNamedValue("Metadata.IsStarting", IsStartingPiece); + if (IsStartingPiece != 0) + { + m_StartingPieces.push_back(prefab.release()); + } + else + { + auto p = prefab.release(); + m_AllPieces.push_back(p); + AddToPerConnectorMap(p); + } + return true; +} + + + + + +UniquePtr<cPrefab> 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<cPrefab>(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<size_t>(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<unsigned>(BlockDataStr.size()) + ); + return nullptr; + } + + return cpp14::make_unique<cPrefab>(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::ApplyPieceMetadataCubesetVer1( + 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; + 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); + + // 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); + + return true; +} + + + + + +bool cPrefabPiecePool::ApplyPoolMetadataCubesetVer1( + const AString & a_FileName, + cLuaState & a_LuaState, + bool a_LogWarnings +) +{ + // Push the Cubeset.Metadata table on top of the Lua stack: + lua_getglobal(a_LuaState, "_G"); + auto md = a_LuaState.WalkToValue("Cubeset.Metadata"); + if (!md.IsValid()) + { + CONDWARNING(a_LogWarnings, "Cannot load cubeset from file %s: Cubeset.Metadata table is missing", a_FileName.c_str()); + return false; + } + + // 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: + a_LuaState.GetNamedValue("IntendedUse", m_IntendedUse); + a_LuaState.GetNamedValue("MaxDensity", m_MaxDensity); + a_LuaState.GetNamedValue("MinDensity", m_MinDensity); + a_LuaState.GetNamedValue("VillageRoadBlockType", m_VillageRoadBlockType); + a_LuaState.GetNamedValue("VillageRoadBlockMeta", m_VillageRoadBlockMeta); + a_LuaState.GetNamedValue("VillageWaterRoadBlockType", m_VillageWaterRoadBlockType); + a_LuaState.GetNamedValue("VillageWaterRoadBlockMeta", m_VillageWaterRoadBlockMeta); + AString allowedBiomes; + if (a_LuaState.GetNamedValue("AllowedBiomes", allowedBiomes)) + { + 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); + } + } + else + { + // All biomes are allowed: + for (int b = biFirstBiome; b <= biMaxBiome; b++) + { + m_AllowedBiomes.insert(static_cast<EMCSBiome>(b)); + } + for (int b = biFirstVariantBiome; b <= biMaxVariantBiome; b++) + { + m_AllowedBiomes.insert(static_cast<EMCSBiome>(b)); + } + } + return true; +} + + + + + cPieces cPrefabPiecePool::GetPiecesWithConnector(int a_ConnectorType) { return m_PiecesByConnector[a_ConnectorType]; @@ -122,7 +610,7 @@ cPieces cPrefabPiecePool::GetStartingPieces(void) int cPrefabPiecePool::GetPieceWeight(const cPlacedPiece & a_PlacedPiece, const cPiece::cConnector & a_ExistingConnector, const cPiece & a_NewPiece) { - return ((const cPrefab &)a_NewPiece).GetPieceWeight(a_PlacedPiece, a_ExistingConnector); + return (static_cast<const cPrefab &>(a_NewPiece)).GetPieceWeight(a_PlacedPiece, a_ExistingConnector); } @@ -131,7 +619,7 @@ int cPrefabPiecePool::GetPieceWeight(const cPlacedPiece & a_PlacedPiece, const c int cPrefabPiecePool::GetStartingPieceWeight(const cPiece & a_NewPiece) { - return ((const cPrefab &)a_NewPiece).GetDefaultWeight(); + return (static_cast<const cPrefab &>(a_NewPiece)).GetDefaultWeight(); } |