1
0

Merge pull request #2255 from cuberite/LoadablePieces

Loadable pieces
This commit is contained in:
worktycho 2015-06-19 19:41:28 +01:00
commit 33d68572a6
25 changed files with 1785 additions and 76 deletions

View File

@ -197,13 +197,53 @@ if(${SELF_TEST})
add_subdirectory (tests)
endif()
# Put project into solution folders in MSVC:
# Put projects into solution folders in MSVC:
if (MSVC)
set_target_properties(event_core event_extra expat jsoncpp lua luaexpat mbedtls sqlite SQLiteCpp tolualib zlib PROPERTIES FOLDER Lib)
set_target_properties(luaproxy tolua PROPERTIES FOLDER Support)
set_target_properties(
event_core
event_extra
expat
jsoncpp
lua
luaexpat
mbedtls
sqlite
SQLiteCpp
tolualib
zlib
PROPERTIES FOLDER Lib
)
set_target_properties(
luaproxy
tolua
PROPERTIES FOLDER Support
)
if (${SELF_TEST})
set_target_properties(Network PROPERTIES FOLDER Lib)
set_target_properties(arraystocoords-exe coordinates-exe copies-exe copyblocks-exe creatable-exe EchoServer Google-exe ChunkBuffer NameLookup PROPERTIES FOLDER Tests)
set_target_properties(
Network
PROPERTIES FOLDER Lib
)
set_target_properties(
arraystocoords-exe
ChunkBuffer
coordinates-exe
copies-exe
copyblocks-exe
creatable-exe
EchoServer
Google-exe
LoadablePieces
NameLookup
PROPERTIES FOLDER Tests
)
endif()
if(${BUILD_TOOLS})
set_target_properties(
MCADefrag
ProtoProxy
PROPERTIES FOLDER Tools
)
endif()
endif()

View File

@ -0,0 +1,296 @@
<html>
<head>
<title>Cubeset file format</title>
<link rel="stylesheet" type="text/css" href="style.css">
</head>
<body>
<h2>Contents</h2>
<ul>
<li><a href="#abstract">Abstract - what and why</a></li>
<li><a href="#details">Detailed description of the format</a>
<ul>
<li><a href="#csmeta">Cubeset metadata</a></li>
<li><a href="#piece">Individual piece</a></li>
<li><a href="#piecemeta">Piece metadata</a></li>
</ul>
</li>
<li><a href="#example">Example</a></li>
</ul>
<hr>
<a name="abstract"><h2>Abstract - what and why</h2></a>
<p>We're seeing an increased need to store "prefabs" - little areas with predefined block contents, such as village houses or fortress rooms - in collections. We have one collection of village houses for the plains village, one collection for the desert village, one collection for the nether fortress... And there are plans in the future to use even more collections - trees, overworld fortresses, more village types and even custom structures. The point that they have in common is that they need to store not only the prefabs, but also metadata for those prefabs - how often they generate, how they connect together. There's even need for metadata for the entire collection, such as what the accepted biomes are, what block should village roads use, and various other generator parameters. So we need a file format that could store all this information together.</p>
<p>There are some existing formats available to consider first:
<ul>
<li><a href="http://minecraft.gamepedia.com/Schematic_file_format"><b>schematic</b></a> - file format native to MCEdit / Bukkit / WorldEdit communities. Can store the prefab, the block entities and regular entities, and any metadata. Cannot store multiple prefabs. No effort to read or write, there's already code to do that (except for the entities) in the server.</li>
<li><a href="http://dev.bukkit.org/bukkit-plugins/terrain-control/"><b>bob / bo2 / bo3</b></a> - file format created for prefabs in the Terrain Control mod. Can store the prefab and any metadata. Support for block entities and regular entities is unknown. Cannot store multiple prefabs. Medium difficulty for reading and writing, would need new parser and serializer. Unknown (but assumed true) whether the format truly supports any metadata.</li>
<li><a href="https://github.com/cuberite/cuberite/tree/master/src/Generating/Prefabs"><b>cpp</b></a> - export from our Gallery server directly into C++ source files. Can store the prefab and any metadata, block entities and regular entities currently not implemented but could be added. Very difficult for reading, writing already implemented. Only usable when compiling directly into the server. Can store multiple prefabs and metadata for the entire set.</li>
</ul>
Obviously none of these fully satisfy our needs, so we'll need to either extend one of them or create yet another one. Extending the .schematic file would mean that the exporter plugin would need to change most of the export code, which was deemed too unmaintainable. Because the bob format is not implemented at all, it wasn't even considered. The cpp format would have been a great candidate if it weren't so difficult to parse. However, it sparked an idea - something similar in form to the cpp format, but easily parsed. Since we already have the Lua interpreter, why not base the new format in Lua?</p>
<p>
With Lua, we could store any metadata for the prefabs, any additional information related to the entire set of prefabs. There's nothing stopping us from adding more items in a backward- and forward-compatible way. The prefabs can be stored very similar to the cpp format, an array of strings plus a charmap, or they can be stored externally in individual .schematic files and the Lua file would provide just the metadata. The server has already vast amounts of Lua-related support code that can be used for accessing the data. In the end this became the chosen solution. The format has been named "Cubeset" - a set of cube-based prefabs.</p>
<hr/>
<a name="details"><h2>Detailed description of the format</h2></a>
<p>
The Cubeset file has a .cubeset extension. Internally it is a Lua source file that provides a global value, Cubeset, which is a table containing the structured data. The loader checks the file's first 4 KiB to see if there is a "CubesetFormatVersion =" string in it, and if it is, the file is considered a Cubeset file and further loading is attempted. It is therefore crucial that tools producing this file format include this string as early as possible in the file.</p>
<p>
The top-level Cubeset table must contain at least two sub-tables: Metadata and Pieces. The Metadata table contains the metadata relevant to the entire set of prefabs in the file, the Pieces table contains the definitions and metadata for the individual prefabs. It is advised to make the Metadata table the first one, because it contains the signature used to identify the file ("CubesetFormatVersion ="). Apart from these two subtables the server ignores everything else.</p>
<a name="csmeta"><h3>Cubeset metadata</h3></a>
<p>
The Cubeset.Metadata table is used to store metadata for the entire set of prefabs, and also for the format and version identification. It is a regular dictionary-style Lua table; the following elements are recognized:
<table>
<tr><th>Name</th><th>Type</th><th>Content</th></tr>
<tr><td>CubesetFormatVersion</td><td>number</td><td>This is the format identification and at the same time it specifies the version of the file. Currently the file version is 1.</td></tr>
<tr><td>ExportDate</td><td>string</td><td>The date when this file was exported, in the ISO format ("2015-06-16 13:34:03"). Inserted by <a href="https://github.com/madmaxoft/GalExport">GalExport</a> for versioning purposes. Ignored elsewhere.</td></tr>
<tr><td>ExternalSchematic</td><td>boolean</td><td>Flag inserted by <a href="https://github.com/madmaxoft/GalExport">GalExport</a> to indicate that the individual prefabs are stored in separate .schematic files. Ignored elsewhere.</td></tr>
</table>
We expect that the set of values recognized by the server will grow when the format is used for some time. All values are optional, except for the CubesetFormatVersion value which is strictly checked by the server. </p>
<a name="piece"><h3>Individual piece</h3></a>
<p>
The Cubeset.Pieces table is an array containing individual prefabs. Each element describes a single prefab and its associated metadata. The following values are recognized:
<table>
<tr><th>Name</th><th>Type</th><th>Content</th></tr>
<tr><td>OriginData</td><td>table</td><td>Inserted by <a href="https://github.com/madmaxoft/GalExport">GalExport</a> to identify the gallery area from which the prefab is exported. Ignored elsewhere.</td></tr>
<tr><td>Hitbox</td><td>table</td><td>The relative coords of the prefab's hitbox (where the prefab is not allowed to overlap another prefab when generating). Members: MinX, MinY, MinZ, MaxX, MaxY, MaxZ, all numbers.</td></tr>
<tr><td>Connectors</td><td>table</td><td>Array of <a href="#conndef">connector definitions</a>. The table must be present for each prefab, even if the prefab doesn't have any connectors (use an empty table, then).</td></tr>
<tr><td>SchematicFileName</td><td>string</td><td>Name of the .schematic file that contains the block data for the prefab.</td></tr>
<tr><td>Size</td><td>table</td><td>Table containing the dimensions of the prefab, if it is inlined into the BlockData element. Contains three members, x, y, z, each is a number.</td></tr>
<tr><td>BlockData</td><td>table</td><td>Array of strings that are processed to produce the block data for the prefab. Each letter in the string corresponds to one block, the type of the block is translated through the BlockDefinitions table. The blocks are ordered YZX, that is, the X coord varies the most.</td></tr>
<tr><td>BlockDefinitions</td><td>table</td><td>Array of strings that defines the mapping of each letter in BlockData to a specific blocktype. Each string should have the format "Letter: BlockType: BlockMeta".</td></tr>
<tr><td>Metadata</td><td>table</td><td>Dictionary-style table of various per-prefab <a href="#piecemeta">metadata values</a>.</td></tr>
</table>
The prefab can either have the SchematicFileName element, in which case the specified schematic file is loaded as the block data, or it can have the Size, BlockData and BlockDefinitions elements, then the server parses the block data from those. If both data members are included, the SchematicFileName takes precedence and the server loads the data from the schematic file (note that this behavior may change, having both definitions is considered "undefined behavior").</p>
<a name="conndef"><p>
The connector definitions table is an array of tables, each element describing one connector. The following values are recognized:
<table>
<tr><th>Name</th><th>type</th><th>Content</th></tr>
<tr><td>Type</td><td>number</td><td>The connector's type. The piece generator will only connect the connectors of inverse types ('1'-type connector will connect only to '-1'-type connector).</td></tr>
<tr><td>RelX</td><td>number</td><td>X coord of the connector, relative to the prefab's zero point ({0, 0, 0} - the first block in the image).</td></tr>
<tr><td>RelY</td><td>number</td><td>Y coord of the connector, relative to the prefab's zero point ({0, 0, 0} - the first block in the image).</td></tr>
<tr><td>RelZ</td><td>number</td><td>Z coord of the connector, relative to the prefab's zero point ({0, 0, 0} - the first block in the image).</td></tr>
<tr><td>Direction</td><td>number</td><td>The direction in which the connector is facing. Corresponds to the eBlockFace constants:
<table>
<tr><th>Value</th><th>Direction</th></tr>
<tr><td>0</td><td>Y-</td></tr>
<tr><td>1</td><td>Y+</td></tr>
<tr><td>2</td><td>Z-</td></tr>
<tr><td>3</td><td>Z+</td></tr>
<tr><td>4</td><td>X-</td></tr>
<tr><td>5</td><td>X+</td></tr>
</table>
</td></tr>
</table>
If a connector definition is missing any of the fields, the server will not add the connector to the prefab upon loading. If a prefab doesn't have any connectors, it still needs to provide an empty Connectors table.</p></a>
<a name="piecemeta"><h3>Piece metadata</h3></a>
<p>
Each piece contains additional metadata describing its properties. The server ignores metadata that it doesn't understand. The following values are recognized:
<table>
<tr><th>Name</th><th>Type</th><th>IsRequired</th><th>Contents</th></tr>
<tr><td>IsStarting</td><td>number</td><td>Yes</td><td>Zero means that the piece is a regular piece, nonzero means that the piece is a starting piece (the "seed" of the structure). Required even for cubesets that don't represent a piece-generator data (such as trees). </td></tr>
<tr><td>AllowedRotations</td><td>number</td><td>&nbsp;</td><td>Number representing a bitmask for which rotations the piece supports. Defaults to 0 (no rotations). Bit 0 (value 1) represents whether 1 counter-clockwise rotation is allowed, bit 1 (value 2) represents whether 2 rotations (180 degrees) are allowed, bit 2 (value 4) represents whether 1 clockwise rotation is allowed.</td></tr>
<tr><td>AddWeightIfSame</td><td>number</td><td>&nbsp;</td><td>How much weight (chance to generate) should the piece generator add to this piece when the parent piece is the same. It is possible to have negative values, meaning that the piece doesn't like repeating itself. Defaults to 0.</td></tr>
<tr><td>DefaultWeight</td><td>number</td><td>&nbsp;</td><td>How much weight (chance to generate) does the piece have by default, without any modifiers (AddWeightIfSame, DepthWeight). Defaults to 0.</td></tr>
<tr><td>DepthWeight</td><td>string</td><td>&nbsp;</td><td>Override for DefaultWeight for specific depth (in the tree used by the piece generator). String in the format "Depth1:Weight1|Depth2:Weight2|...". Each unlisted depth gets the DefaultWeight. Defaults to empty string (no override).</td></tr>
<tr><td>MergeStrategy</td><td>string</td><td>&nbsp;</td><td>Which merge strategy should be used when drawing the prefab into the world. String representation of one of the cBlockArea:eMergeStrategy constants: "msOverwrite", "msFillAir", "msImprint", "msLake", "msSpongePrint", "msDifference", "msSimpleCompare", "msMask". Defaults to "msSpongePrint".</td></tr>
<tr><td>MoveToGround</td><td>number</td><td>&nbsp;</td><td>Zero means that the piece will stay where generated by the piece generator, nonzero means that the piece will be moved Y-wise so that its first connector will be on the top block of the existing terrain. Useful for village houses. Defaults to 0.</td></tr>
<tr><td>ShouldExpandFloor</td><td>number</td><td>&nbsp;</td><td>Nonzero means that the prefab's lowest slice will be repeated downwards until it hits a solid block, effectively creating a foundation for the piece. Useful for nether fortresses and village houses. Defaults to 0.</td></tr>
</table>
Each value that should be a number also allows a string that represents a number. This makes it easier for automated exporters - they can export all values as strings.</p>
<hr/>
<a name="example"><h2>Example</h2></a>
<p>
The following example defines a cubeset with two pieces. The first piece is inlined into the cubeset file, the second piece uses an external schematic file.</p>
<pre>
Cubeset =
{
Metadata =
{
CubesetFormatVersion = 1,
},
Pieces =
{
-- The following piece was exported from the Gallery server by the GalExport plugin in the "cubeset" format:
{
OriginData =
{
ExportName = "DarkCorridor",
Name = "Nether 3",
GalleryName = "Nether",
GalleryIndex = "3",
ID = "30",
CreatorName = "STR_Warrior",
},
Size =
{
x = 14,
y = 6,
z = 5,
},
Hitbox =
{
MinX = 0,
MinY = 0,
MinZ = 0,
MaxX = 13,
MaxY = 5,
MaxZ = 4,
},
Connectors =
{
{
Type = 1,
RelX = 0,
RelY = 1,
RelZ = 2,
Direction = 4, -- X-
},
{
Type = 1,
RelX = 13,
RelY = 1,
RelZ = 2,
Direction = 5, -- X+
},
{
Type = -1,
RelX = 0,
RelY = 1,
RelZ = 2,
Direction = 4, -- X-
},
{
Type = -1,
RelX = 13,
RelY = 1,
RelZ = 2,
Direction = 5, -- X+
},
},
Metadata =
{
["DefaultWeight"] = "100",
["IsStarting"] = "0",
["AllowedRotations"] = "7",
["MergeStrategy"] = "msSpongePrint",
["DepthWeight"] = "",
["ShouldExpandFloor"] = "1",
["MoveToGround"] = "0",
["AddWeightIfSame"] = "0",
},
BlockDefinitions =
{
".: 0: 0", -- air
"a:112: 0", -- netherbrick
"b:113: 0", -- netherbrickfence
"c:114: 2", -- netherbrickstairs
"d:114: 3", -- netherbrickstairs
"m: 19: 0", -- sponge
},
BlockData =
{
-- Level 0
"aaaaaaaaaaaaaa", -- 0
"aaaaaaaaaaaaaa", -- 1
"aaaaaaaaaaaaaa", -- 2
"aaaaaaaaaaaaaa", -- 3
"aaaaaaaaaaaaaa", -- 4
-- Level 1
"aaaaaaaaaaaaaa", -- 0
"..............", -- 1
"..............", -- 2
"..............", -- 3
"aaaaaaaaaaaaaa", -- 4
-- Level 2
"aabaaaaaaaabaa", -- 0
"..............", -- 1
"..............", -- 2
"..............", -- 3
"aabaaaaaaaabaa", -- 4
-- Level 3
"aabaaaaaaaabaa", -- 0
"..............", -- 1
"..............", -- 2
"..............", -- 3
"aabaaaaaaaabaa", -- 4
-- Level 4
"aabaaaaaaaabaa", -- 0
"..............", -- 1
"..............", -- 2
"..............", -- 3
"aabaaaaaaaabaa", -- 4
-- Level 5
"cccccccccccccc", -- 0
"aaaaaaaaaaaaaa", -- 1
"aaaaaaaaaaaaaa", -- 2
"aaaaaaaaaaaaaa", -- 3
"dddddddddddddd", -- 4
},
}, -- DarkCorridor
-- The following piece was exported from the Gallery server by the GalExport plugin in the "cubesetext" format:
{
OriginData =
{
ExportName = "DoublePlantBed",
Name = "Plains 5",
GalleryName = "Plains",
GalleryIndex = "5",
ID = "20",
CreatorName = "tonibm1999",
},
Size =
{
x = 15,
y = 8,
z = 9,
},
Hitbox =
{
MinX = 0,
MinY = 0,
MinZ = 0,
MaxX = 14,
MaxY = 7,
MaxZ = 8,
},
Connectors =
{
{
Type = -1,
RelX = 7,
RelY = 2,
RelZ = 8,
Direction = 3, -- Z+
},
},
Metadata =
{
["DefaultWeight"] = "100",
["IsStarting"] = "0",
["AllowedRotations"] = "7",
["MergeStrategy"] = "msSpongePrint",
["DepthWeight"] = "",
["ShouldExpandFloor"] = "1",
["MoveToGround"] = "1",
["AddWeightIfSame"] = "0",
},
SchematicFile = "PlainsVillage/20.schematic",
}, -- DoublePlantBed
}
}
</pre>
</body>
</html>

12
docs/style.css Normal file
View File

@ -0,0 +1,12 @@
table
{
border: 1px outset;
border-spacing: 0px;
border-collapse: separate;
}
td, th
{
border: 1px inset
}

View File

@ -98,7 +98,9 @@ local function OutputLuaStateHelpers(a_Package)
f:write("// This file expects to be included form inside the cLuaState class definition\n")
f:write("\n\n\n\n\n")
for _, item in ipairs(types) do
f:write("void Push(" .. item.name .. " * a_Value);\n")
if not(g_HasCustomPushImplementation[item.name]) then
f:write("void Push(" .. item.name .. " * a_Value);\n")
end
end
for _, item in ipairs(types) do
f:write("bool GetStackValue(int a_StackPos, Ptr" .. item.lname .. " & a_ReturnedVal);\n")

View File

@ -211,23 +211,31 @@ void cLuaState::AddPackagePath(const AString & a_PathVariable, const AString & a
bool cLuaState::LoadFile(const AString & a_FileName)
bool cLuaState::LoadFile(const AString & a_FileName, bool a_LogWarnings)
{
ASSERT(IsValid());
// Load the file:
int s = luaL_loadfile(m_LuaState, a_FileName.c_str());
if (ReportErrors(s))
if (s != 0)
{
LOGWARNING("Can't load %s because of an error in file %s", m_SubsystemName.c_str(), a_FileName.c_str());
if (a_LogWarnings)
{
LOGWARNING("Can't load %s because of a load error in file %s: %d (%s)", m_SubsystemName.c_str(), a_FileName.c_str(), s, lua_tostring(m_LuaState, -1));
}
lua_pop(m_LuaState, 1);
return false;
}
// Execute the globals:
s = lua_pcall(m_LuaState, 0, LUA_MULTRET, 0);
if (ReportErrors(s))
if (s != 0)
{
LOGWARNING("Error in %s in file %s", m_SubsystemName.c_str(), a_FileName.c_str());
if (a_LogWarnings)
{
LOGWARNING("Can't load %s because of an initialization error in file %s: %d (%s)", m_SubsystemName.c_str(), a_FileName.c_str(), s, lua_tostring(m_LuaState, -1));
}
lua_pop(m_LuaState, 1);
return false;
}
@ -446,6 +454,18 @@ void cLuaState::Push(const cPlayer * a_Player)
void cLuaState::Push(const cLuaState::cRef & a_Ref)
{
ASSERT(IsValid());
lua_rawgeti(m_LuaState, LUA_REGISTRYINDEX, static_cast<int>(a_Ref));
m_NumCurrentFunctionArgs += 1;
}
void cLuaState::Push(const HTTPRequest * a_Request)
{
ASSERT(IsValid());
@ -765,14 +785,17 @@ bool cLuaState::GetStackValue(int a_StackPos, double & a_ReturnedVal)
bool cLuaState::GetStackValue(int a_StackPos, float & a_ReturnedVal)
bool cLuaState::GetStackValue(int a_StackPos, eBlockFace & a_ReturnedVal)
{
if (lua_isnumber(m_LuaState, a_StackPos))
if (!lua_isnumber(m_LuaState, a_StackPos))
{
a_ReturnedVal = static_cast<float>(tolua_tonumber(m_LuaState, a_StackPos, a_ReturnedVal));
return true;
return false;
}
return false;
a_ReturnedVal = static_cast<eBlockFace>(Clamp(
static_cast<int>(tolua_tonumber(m_LuaState, a_StackPos, a_ReturnedVal)),
static_cast<int>(BLOCK_FACE_MIN), static_cast<int>(BLOCK_FACE_MAX))
);
return true;
}
@ -796,6 +819,46 @@ bool cLuaState::GetStackValue(int a_StackPos, eWeather & a_ReturnedVal)
bool cLuaState::GetStackValue(int a_StackPos, float & a_ReturnedVal)
{
if (lua_isnumber(m_LuaState, a_StackPos))
{
a_ReturnedVal = static_cast<float>(tolua_tonumber(m_LuaState, a_StackPos, a_ReturnedVal));
return true;
}
return false;
}
cLuaState::cStackValue cLuaState::WalkToValue(const AString & a_Name)
{
auto path = StringSplit(a_Name, ".");
lua_pushvalue(m_LuaState, -1); // Copy the stack value into the "working area"
for (const auto & elem: path)
{
// If the value is not a table, bail out (error):
if (!lua_istable(m_LuaState, -1))
{
lua_pop(m_LuaState, 1);
return cStackValue();
}
// Get the next part of the path:
lua_getfield(m_LuaState, -1, elem.c_str());
// Remove the previous value from the stack (keep only the new one):
lua_remove(m_LuaState, -2);
} // for elem - path[]
return std::move(cStackValue(*this));
}
bool cLuaState::CallFunction(int a_NumResults)
{
ASSERT (m_NumCurrentFunctionArgs >= 0); // A function must be pushed to stack first

View File

@ -78,11 +78,14 @@ public:
bool IsValid(void) const {return (m_Ref != LUA_REFNIL); }
/** Allows to use this class wherever an int (i. e. ref) is to be used */
operator int(void) const { return m_Ref; }
explicit operator int(void) const { return m_Ref; }
protected:
cLuaState * m_LuaState;
int m_Ref;
// Remove the copy-constructor:
cRef(const cRef &) = delete;
} ;
@ -98,6 +101,12 @@ public:
{
}
cTableRef(const cRef & a_TableRef, const char * a_FnName) :
m_TableRef(static_cast<int>(a_TableRef)),
m_FnName(a_FnName)
{
}
int GetTableRef(void) const { return m_TableRef; }
const char * GetFnName(void) const { return m_FnName; }
} ;
@ -111,6 +120,61 @@ public:
static const cRet Return; // Use this constant to delimit function args from return values for cLuaState::Call()
/** A RAII class for values pushed onto the Lua stack.
Will pop the value off the stack in the destructor. */
class cStackValue
{
public:
cStackValue(void):
m_LuaState(nullptr)
{
}
cStackValue(cLuaState & a_LuaState):
m_LuaState(a_LuaState)
{
m_StackLen = lua_gettop(a_LuaState);
}
cStackValue(cStackValue && a_Src):
m_LuaState(nullptr),
m_StackLen(-1)
{
std::swap(m_LuaState, a_Src.m_LuaState);
std::swap(m_StackLen, a_Src.m_StackLen);
}
~cStackValue()
{
if (m_LuaState != nullptr)
{
auto top = lua_gettop(m_LuaState);
ASSERT(m_StackLen == top);
lua_pop(m_LuaState, 1);
}
}
void Set(cLuaState & a_LuaState)
{
m_LuaState = a_LuaState;
m_StackLen = lua_gettop(a_LuaState);
}
bool IsValid(void) const
{
return (m_LuaState != nullptr);
}
protected:
lua_State * m_LuaState;
int m_StackLen;
// Remove the copy-constructor:
cStackValue(const cStackValue &) = delete;
};
/** Creates a new instance. The LuaState is not initialized.
a_SubsystemName is used for reporting problems in the console, it is "plugin %s" for plugins,
or "LuaScript" for the cLuaScript template
@ -151,10 +215,9 @@ public:
void AddPackagePath(const AString & a_PathVariable, const AString & a_Path);
/** Loads the specified file
Returns false and logs a warning to the console if not successful (but the LuaState is kept open).
m_SubsystemName is displayed in the warning log message.
*/
bool LoadFile(const AString & a_FileName);
Returns false and optionally logs a warning to the console if not successful (but the LuaState is kept open).
m_SubsystemName is displayed in the warning log message. */
bool LoadFile(const AString & a_FileName, bool a_LogWarnings = true);
/** Returns true if a_FunctionName is a valid Lua function that can be called */
bool HasFunction(const char * a_FunctionName);
@ -169,6 +232,7 @@ public:
void Push(const char * a_Value);
void Push(const cItems & a_Items);
void Push(const cPlayer * a_Player);
void Push(const cRef & a_Ref);
void Push(const HTTPRequest * a_Request);
void Push(const HTTPTemplateRequest * a_Request);
void Push(const Vector3d & a_Vector);
@ -178,22 +242,24 @@ public:
// Push a simple value onto the stack (keep alpha-sorted):
void Push(bool a_Value);
void Push(cEntity * a_Entity);
void Push(cLuaServerHandle * a_ServerHandle);
void Push(cLuaTCPLink * a_TCPLink);
void Push(cLuaUDPEndpoint * a_UDPEndpoint);
void Push(double a_Value);
void Push(int a_Value);
void Push(void * a_Ptr);
void Push(std::chrono::milliseconds a_time);
void Push(cLuaServerHandle * a_ServerHandle);
void Push(cLuaTCPLink * a_TCPLink);
void Push(cLuaUDPEndpoint * a_UDPEndpoint);
// GetStackValue() retrieves the value at a_StackPos, if it is a valid type. If not, a_Value is unchanged.
// Returns whether value was changed
// Enum values are clamped to their allowed range.
// Enum values are checked for their allowed values and fail if the value is not assigned.
bool GetStackValue(int a_StackPos, AString & a_Value);
bool GetStackValue(int a_StackPos, bool & a_Value);
bool GetStackValue(int a_StackPos, cPluginManager::CommandResult & a_Result);
bool GetStackValue(int a_StackPos, cRef & a_Ref);
bool GetStackValue(int a_StackPos, double & a_Value);
bool GetStackValue(int a_StackPos, eBlockFace & a_Value);
bool GetStackValue(int a_StackPos, eWeather & a_Value);
bool GetStackValue(int a_StackPos, float & a_ReturnedVal);
@ -202,21 +268,53 @@ public:
bool GetStackValue(int a_StackPos, T & a_ReturnedVal, typename std::enable_if<std::is_integral<T>::value>::type * unused = nullptr)
{
UNUSED(unused);
if (lua_isnumber(m_LuaState, a_StackPos))
if (!lua_isnumber(m_LuaState, a_StackPos)) // Also accepts strings representing a number: http://pgl.yoyo.org/luai/i/lua_isnumber
{
lua_Number Val = tolua_tonumber(m_LuaState, a_StackPos, a_ReturnedVal);
if (Val > std::numeric_limits<T>::max())
{
return false;
}
if (Val < std::numeric_limits<T>::min())
{
return false;
}
a_ReturnedVal = static_cast<T>(Val);
return true;
return false;
}
return false;
lua_Number Val = lua_tonumber(m_LuaState, a_StackPos);
if (Val > std::numeric_limits<T>::max())
{
return false;
}
if (Val < std::numeric_limits<T>::min())
{
return false;
}
a_ReturnedVal = static_cast<T>(Val);
return true;
}
/** Pushes the named value in the table at the top of the stack.
a_Name may be a path containing multiple table levels, such as "_G.cChatColor.Blue".
If the value is found, it is pushed on top of the stack and the returned cStackValue is valid.
If the value is not found, the stack is unchanged and the returned cStackValue is invalid. */
cStackValue WalkToValue(const AString & a_Name);
/** Retrieves the named value in the table at the top of the Lua stack.
a_Name may be a path containing multiple table levels, such as "_G.cChatColor.Blue".
Returns true if the value was successfully retrieved, false on error. */
template <typename T> bool GetNamedValue(const AString & a_Name, T & a_Value)
{
auto stk = WalkToValue(a_Name);
if (!stk.IsValid())
{
// Name not found
return false;
}
return GetStackValue(-1, a_Value);
}
/** Retrieves the named global value. a_Name may be a path containing multiple table levels, such as "_G.cChatColor.Blue".
Returns true if the value was successfully retrieved, false on error. */
template <typename T> bool GetNamedGlobal(const AString & a_Name, T & a_Value)
{
// Push the globals table onto the stack and make it RAII-removed:
lua_getglobal(m_LuaState, "_G");
cStackValue stk(*this);
// Get the named global:
return GetNamedValue(a_Name, a_Value);
}
// Include the auto-generated Push and GetStackValue() functions:
@ -229,12 +327,12 @@ public:
template <typename FnT, typename... Args>
bool Call(const FnT & a_Function, Args &&... args)
{
if (!PushFunction(a_Function))
if (!PushFunction(std::forward<const FnT &>(a_Function)))
{
// Pushing the function failed
return false;
}
return PushCallPop(args...);
return PushCallPop(std::forward<Args>(args)...);
}
/** Retrieves a list of values from the Lua stack, starting at the specified index. */
@ -343,10 +441,10 @@ protected:
/** Variadic template recursor: More params to push. Push them and recurse. */
template <typename T, typename... Args>
inline bool PushCallPop(T a_Param, Args &&... args)
inline bool PushCallPop(T && a_Param, Args &&... args)
{
Push(a_Param);
return PushCallPop(args...);
Push(std::forward<T>(a_Param));
return PushCallPop(std::forward<Args>(args)...);
}
/** Variadic template terminator: If there's nothing more to push, but return values to collect, call the function and collect the returns. */
@ -363,7 +461,7 @@ protected:
}
// Collect the return values:
GetStackValues(-NumReturns, args...);
GetStackValues(-NumReturns, std::forward<Args>(args)...);
lua_pop(m_LuaState, NumReturns);
// All successful:

View File

@ -427,6 +427,7 @@ void cBlockHandler::NeighborChanged(cChunkInterface & a_ChunkInterface, int a_Bl
void cBlockHandler::ConvertToPickups(cItems & a_Pickups, NIBBLETYPE a_BlockMeta)
{
// Setting the meta to a_BlockMeta keeps most textures. The few other blocks have to override this.

View File

@ -29,16 +29,16 @@ enum
/// Block face constants, used in PlayerDigging and PlayerBlockPlacement packets and bbox collision calc
/** Block face constants, used in PlayerDigging and PlayerBlockPlacement packets and bbox collision calc */
enum eBlockFace
{
BLOCK_FACE_NONE = -1, // Interacting with no block face - swinging the item in the air
BLOCK_FACE_XM = 4, // Interacting with the X- face of the block
BLOCK_FACE_XP = 5, // Interacting with the X+ face of the block
BLOCK_FACE_YM = 0, // Interacting with the Y- face of the block
BLOCK_FACE_YP = 1, // Interacting with the Y+ face of the block
BLOCK_FACE_ZM = 2, // Interacting with the Z- face of the block
BLOCK_FACE_ZP = 3, // Interacting with the Z+ face of the block
BLOCK_FACE_XM = 4, // Interacting with the X- face of the block
BLOCK_FACE_XP = 5, // Interacting with the X+ face of the block
BLOCK_FACE_YM = 0, // Interacting with the Y- face of the block
BLOCK_FACE_YP = 1, // Interacting with the Y+ face of the block
BLOCK_FACE_ZM = 2, // Interacting with the Z- face of the block
BLOCK_FACE_ZP = 3, // Interacting with the Z+ face of the block
// Synonyms using the (deprecated) world directions:
BLOCK_FACE_BOTTOM = BLOCK_FACE_YM, // Interacting with the bottom face of the block
@ -47,6 +47,10 @@ enum eBlockFace
BLOCK_FACE_SOUTH = BLOCK_FACE_ZP, // Interacting with the southern face of the block
BLOCK_FACE_WEST = BLOCK_FACE_XM, // Interacting with the western face of the block
BLOCK_FACE_EAST = BLOCK_FACE_XP, // Interacting with the eastern face of the block
// Bounds, used for range-checking:
BLOCK_FACE_MIN = -1,
BLOCK_FACE_MAX = 5,
} ;

View File

@ -101,5 +101,5 @@ endif()
if(NOT MSVC)
add_library(Generating ${SRCS} ${HDRS})
target_link_libraries(Generating OSSupport Blocks)
target_link_libraries(Generating OSSupport Blocks Bindings)
endif()

View File

@ -166,6 +166,45 @@ cPrefab::cPrefab(const cBlockArea & a_Image, int a_AllowedRotations) :
cPrefab::cPrefab(const cBlockArea & a_Image) :
m_Size(a_Image.GetSize()),
m_AllowedRotations(0),
m_MergeStrategy(cBlockArea::msOverwrite),
m_ShouldExtendFloor(false),
m_DefaultWeight(1),
m_AddWeightIfSame(0),
m_MoveToGround(false)
{
m_HitBox.p1.Set(0, 0, 0);
m_HitBox.p2.Set(m_Size.x - 1, m_Size.y - 1, m_Size.z - 1);
m_BlockArea[0].CopyFrom(a_Image);
}
cPrefab::cPrefab(const AString & a_BlockDefinitions, const AString & a_BlockData, int a_SizeX, int a_SizeY, int a_SizeZ) :
m_Size(a_SizeX, a_SizeY, a_SizeZ),
m_AllowedRotations(0),
m_MergeStrategy(cBlockArea::msOverwrite),
m_ShouldExtendFloor(false),
m_DefaultWeight(1),
m_AddWeightIfSame(0),
m_MoveToGround(false)
{
m_HitBox.p1.Set(0, 0, 0);
m_HitBox.p2.Set(m_Size.x - 1, m_Size.y - 1, m_Size.z - 1);
m_BlockArea[0].Create(m_Size);
CharMap cm;
ParseCharMap(cm, a_BlockDefinitions.c_str());
ParseBlockImage(cm, a_BlockData.c_str());
}
void cPrefab::AddRotatedBlockAreas(void)
{
// 1 CCW rotation:
@ -326,6 +365,16 @@ void cPrefab::AddConnector(int a_RelX, int a_RelY, int a_RelZ, eBlockFace a_Dire
void cPrefab::SetAllowedRotations(int a_AllowedRotations)
{
m_AllowedRotations = a_AllowedRotations;
AddRotatedBlockAreas();
}
void cPrefab::ParseCharMap(CharMap & a_CharMapOut, const char * a_CharMapDef)
{
ASSERT(a_CharMapDef != nullptr);

View File

@ -95,6 +95,13 @@ public:
/** Creates a prefab based on the given BlockArea and allowed rotations. */
cPrefab(const cBlockArea & a_Image, int a_AllowedRotations);
/** Creates a prefab based on the given BlockArea. Allowed rotations can be added later on using SetAllowedRotations(). */
cPrefab(const cBlockArea & a_Image);
/** Creates a prefab based on the specified block data, using the char-to-block map in a_BlockDefinitions.
Allowed rotations can be added later on using SetAllowedRotations(). */
cPrefab(const AString & a_BlockDefinitions, const AString & a_BlockData, int a_SizeX, int a_SizeY, int a_SizeZ);
/** Draws the prefab into the specified chunk, according to the placement stored in the PlacedPiece. */
void Draw(cChunkDesc & a_Dest, const cPlacedPiece * a_Placement) const;
@ -124,6 +131,21 @@ public:
at the coords governed by the connectors. */
bool ShouldMoveToGround(void) const { return m_MoveToGround; }
/** Sets the m_AllowedRotations bitmask and fills the m_BlockArea[] with rotated versions of m_BlockArea[0]. */
void SetAllowedRotations(int a_AllowedRotations);
/** Parses the per-depth weight into m_DepthWeight member. */
void ParseDepthWeight(const char * a_DepthWeightDef);
/** Sets the merge strategy to be used when drawing the piece. */
void SetMergeStrategy(cBlockArea::eMergeStrategy a_MergeStrategy) { m_MergeStrategy = a_MergeStrategy; }
/** Sets the flag whether the prefab should be moved to ground level before being drawn. */
void SetMoveToGround(bool a_MoveToGround) { m_MoveToGround = a_MoveToGround; }
/** Sets the flag whether the lowest layer of the prefab should be repeated downwards until it hits a solid block. */
void SetExtendFloor(bool a_ShouldExtendFloor) { m_ShouldExtendFloor = a_ShouldExtendFloor; }
protected:
/** Packs complete definition of a single block, for per-letter assignment. */
struct sBlockTypeDef
@ -201,9 +223,6 @@ protected:
/** Parses the connectors definition text into m_Connectors member. */
void ParseConnectors(const char * a_ConnectorsDef);
/** Parses the per-depth weight into m_DepthWeight member. */
void ParseDepthWeight(const char * a_DepthWeightDef);
};

View File

@ -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,6 +140,66 @@ 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 = (static_cast<const cPiece *>(a_Prefab))->GetConnectors();
@ -95,6 +212,303 @@ void cPrefabPiecePool::AddToPerConnectorMap(cPrefab * a_Prefab)
bool cPrefabPiecePool::LoadFromCubesetFileVer1(const AString & a_FileName, cLuaState & a_LuaState, bool 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 (!ApplyMetadataCubesetVer1(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::ApplyMetadataCubesetVer1(
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);
}
a_Prefab->SetMoveToGround(MoveToGround != 0);
a_Prefab->SetExtendFloor(ShouldExpandFloor != 0);
return true;
}
cPieces cPrefabPiecePool::GetPiecesWithConnector(int a_ConnectorType)
{
return m_PiecesByConnector[a_ConnectorType];

View File

@ -16,6 +16,13 @@
// fwd:
class cLuaState;
class cPrefabPiecePool :
public cPiecePool
{
@ -34,6 +41,10 @@ public:
const cPrefab::sDef * a_StartingPieceDefs, size_t a_NumStartingPieceDefs
);
/** Creates a pool and loads the contents of the specified file into it.
If a_LogWarnings is true, logs a warning to console when loading fails. */
cPrefabPiecePool(const AString & a_FileName, bool a_LogWarnings);
/** Destroys the pool, freeing all pieces. */
~cPrefabPiecePool();
@ -50,6 +61,20 @@ public:
May be called multiple times with different PieceDefs, will add all such pieces. */
void AddStartingPieceDefs(const cPrefab::sDef * a_StartingPieceDefs, size_t a_NumStartingPieceDefs);
/** Loads the pieces from the specified file. Returns true if successful, false on error.
If a_LogWarnings is true, logs a warning to console when loading fails. */
bool LoadFromFile(const AString & a_FileName, bool a_LogWarnings);
/** Loads the pieces from the specified Cubeset file. Returns true if successful, false on error.
If a_LogWarnings is true, logs a warning to console when loading fails. */
bool LoadFromCubesetFile(const AString & a_FileName, bool a_LogWarnings);
/** Returns the number of regular (non-starting) pieces. */
size_t GetAllPiecesCount(void) const { return m_AllPieces.size(); }
/** Returns the number of starting pieces. */
size_t GetStartingPiecesCount(void) const { return m_StartingPieces.size(); }
protected:
/** The type used to map a connector type to the list of pieces with that connector */
@ -70,7 +95,61 @@ protected:
/** Adds the prefab to the m_PiecesByConnector map for all its connectors. */
void AddToPerConnectorMap(cPrefab * a_Prefab);
/** Loads the pieces from the cubeset file parsed into the specified Lua state.
Returns true on success, false on error.
If a_LogWarnings is true, logs a warning to console when loading fails. */
bool LoadFromCubesetFileVer1(const AString & a_FileName, cLuaState & a_LuaState, bool a_LogWarnings);
/** Loads a single piece from the cubeset file parsed into the specified Lua state.
The piece's definition table is expected to be at the top of the Lua stack.
Returns true on success, false on error.
a_PieceIndex is the index of the piece, in the Pieces table. It is used for logging only.
If a_LogWarnings is true, logs a warning to console when loading fails. */
bool LoadCubesetPieceVer1(const AString & a_FileName, cLuaState & a_LuaState, int a_PieceIndex, bool a_LogWarnings);
/** Loads a single piece's prefab from the cubeset file parsed into the specified Lua state.
The piece's definition table is expected to be at the top of the Lua stack.
Returns the prefab on success, nullptr on failure.
a_PieceName is the identification of the piece, used for logging only.
If a_LogWarnings is true, logs a warning to console when loading fails. */
UniquePtr<cPrefab> LoadPrefabFromCubesetVer1(
const AString & a_FileName,
cLuaState & a_LuaState,
const AString & a_PieceName,
bool a_LogWarnings
);
/** Reads a single piece's connectors from the cubeset file parsed into the specified Lua state.
The piece's definition table is expected to be at the top of the Lua stack.
Returns true on success, false on failure.
The connectors are added into the a_Prefab object.
No Connectors table is considered a failure, empty Connectors table is considered a success.
If any of the connectors are malformed, it is considered a failure, although the rest of the connectors will still load.
a_PieceName is the identification of the piece, used for logging only.
If a_LogWarnings is true, logs a warning to console when loading fails. */
bool ReadConnectorsCubesetVer1(
const AString & a_FileName,
cLuaState & a_LuaState,
const AString & a_PieceName,
cPrefab * a_Prefab,
bool a_LogWarnings
);
/** Reads a single piece's metadata from the cubeset file parsed into the specified Lua state.
The piece's definition table is expected to be at the top of the Lua stack.
Returns true on success, false on failure.
The metadata is applied into the a_Prefab object.
a_PieceName is the identification of the piece, used for logging only.
If a_LogWarnings is true, logs a warning to console when loading fails. */
bool ApplyMetadataCubesetVer1(
const AString & a_FileName,
cLuaState & a_LuaState,
const AString & a_PieceName,
cPrefab * a_Prefab,
bool a_LogWarnings
);
// cPiecePool overrides:
virtual cPieces GetPiecesWithConnector(int a_ConnectorType) override;
virtual cPieces GetStartingPieces(void) override;

View File

@ -261,14 +261,15 @@ template class SizeChecker<UInt8, 1>;
// Common headers (part 1, without macros):
#include "StringUtils.h"
#include "OSSupport/CriticalSection.h"
#include "OSSupport/Event.h"
#include "OSSupport/File.h"
#include "OSSupport/StackTrace.h"
#ifndef TEST_GLOBALS
// Common headers (part 1, without macros):
#include "StringUtils.h"
#include "OSSupport/CriticalSection.h"
#include "OSSupport/Event.h"
#include "OSSupport/File.h"
#include "Logger.h"
#include "OSSupport/StackTrace.h"
#else
// Logging functions
void inline LOGERROR(const char * a_Format, ...) FORMATSTRING(1, 2);
@ -315,6 +316,9 @@ void inline LOG(const char * a_Format, ...)
va_end(argList);
}
#define LOGINFO LOG
#define LOGWARN LOGWARNING
#endif

View File

@ -14,7 +14,8 @@ SET (SRCS
ScoreboardSerializer.cpp
StatSerializer.cpp
WSSAnvil.cpp
WorldStorage.cpp)
WorldStorage.cpp
)
SET (HDRS
EnchantmentSerializer.h
@ -26,7 +27,8 @@ SET (HDRS
ScoreboardSerializer.h
StatSerializer.h
WSSAnvil.h
WorldStorage.h)
WorldStorage.h
)
if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")
set_source_files_properties(EnchantmentSerializer.cpp PROPERTIES COMPILE_FLAGS "-Wno-error=old-style-cast")

View File

@ -238,27 +238,27 @@ bool cSchematicFileSerializer::LoadFromSchematicNBT(cBlockArea & a_BlockArea, cP
}
// Copy the block types and metas:
size_t NumBytes = a_BlockArea.GetBlockCount();
if (a_NBT.GetDataLength(TBlockTypes) < NumBytes)
size_t NumTypeBytes = a_BlockArea.GetBlockCount();
if (a_NBT.GetDataLength(TBlockTypes) < NumTypeBytes)
{
LOG("BlockTypes truncated in the schematic file (exp %d, got %d bytes). Loading partial.",
(int)NumBytes, (int)a_NBT.GetDataLength(TBlockTypes)
LOG("BlockTypes truncated in the schematic file (exp %u, got %u bytes). Loading partial.",
static_cast<unsigned>(NumTypeBytes), static_cast<unsigned>(a_NBT.GetDataLength(TBlockTypes))
);
NumBytes = a_NBT.GetDataLength(TBlockTypes);
NumTypeBytes = a_NBT.GetDataLength(TBlockTypes);
}
memcpy(a_BlockArea.m_BlockTypes, a_NBT.GetData(TBlockTypes), NumBytes);
memcpy(a_BlockArea.m_BlockTypes, a_NBT.GetData(TBlockTypes), NumTypeBytes);
if (AreMetasPresent)
{
size_t NumBytes = a_BlockArea.GetBlockCount();
if (a_NBT.GetDataLength(TBlockMetas) < NumBytes)
size_t NumMetaBytes = a_BlockArea.GetBlockCount();
if (a_NBT.GetDataLength(TBlockMetas) < NumMetaBytes)
{
LOG("BlockMetas truncated in the schematic file (exp %d, got %d bytes). Loading partial.",
(int)NumBytes, (int)a_NBT.GetDataLength(TBlockMetas)
LOG("BlockMetas truncated in the schematic file (exp %u, got %u bytes). Loading partial.",
static_cast<unsigned>(NumMetaBytes), static_cast<unsigned>(a_NBT.GetDataLength(TBlockMetas))
);
NumBytes = a_NBT.GetDataLength(TBlockMetas);
NumMetaBytes = a_NBT.GetDataLength(TBlockMetas);
}
memcpy(a_BlockArea.m_BlockMetas, a_NBT.GetData(TBlockMetas), NumBytes);
memcpy(a_BlockArea.m_BlockMetas, a_NBT.GetData(TBlockMetas), NumMetaBytes);
}
return true;

View File

@ -6,3 +6,4 @@ include_directories(${CMAKE_CURRENT_SOURCE_DIR})
add_subdirectory(ChunkData)
add_subdirectory(Network)
add_subdirectory(LoadablePieces)

View File

@ -0,0 +1,15 @@
// Bindings.h
// Dummy include file needed for LuaState to compile successfully
struct lua_State;
int tolua_AllToLua_open(lua_State * a_LuaState);

View File

@ -0,0 +1,93 @@
cmake_minimum_required (VERSION 2.6)
enable_testing()
include_directories(${CMAKE_SOURCE_DIR}/src/)
include_directories(${CMAKE_SOURCE_DIR}/lib/)
include_directories(${CMAKE_CURRENT_SOURCE_DIR})
add_definitions(-DTEST_GLOBALS=1)
set (SHARED_SRCS
${CMAKE_SOURCE_DIR}/src/BlockArea.cpp
${CMAKE_SOURCE_DIR}/src/Cuboid.cpp
${CMAKE_SOURCE_DIR}/src/ChunkData.cpp
${CMAKE_SOURCE_DIR}/src/StringCompression.cpp
${CMAKE_SOURCE_DIR}/src/StringUtils.cpp
${CMAKE_SOURCE_DIR}/src/Bindings/LuaState.cpp
${CMAKE_SOURCE_DIR}/src/Generating/ChunkDesc.cpp
${CMAKE_SOURCE_DIR}/src/Generating/PieceGenerator.cpp
${CMAKE_SOURCE_DIR}/src/Generating/Prefab.cpp
${CMAKE_SOURCE_DIR}/src/Generating/PrefabPiecePool.cpp
${CMAKE_SOURCE_DIR}/src/Noise/Noise.cpp
${CMAKE_SOURCE_DIR}/src/OSSupport/CriticalSection.cpp
${CMAKE_SOURCE_DIR}/src/OSSupport/Event.cpp
${CMAKE_SOURCE_DIR}/src/OSSupport/File.cpp
${CMAKE_SOURCE_DIR}/src/OSSupport/GZipFile.cpp
${CMAKE_SOURCE_DIR}/src/OSSupport/StackTrace.cpp
${CMAKE_SOURCE_DIR}/src/WorldStorage/FastNBT.cpp
${CMAKE_SOURCE_DIR}/src/WorldStorage/SchematicFileSerializer.cpp
)
set (SHARED_HDRS
${CMAKE_SOURCE_DIR}/src/BlockArea.h
${CMAKE_SOURCE_DIR}/src/Cuboid.h
${CMAKE_SOURCE_DIR}/src/ChunkData.h
${CMAKE_SOURCE_DIR}/src/Globals.h
${CMAKE_SOURCE_DIR}/src/StringCompression.h
${CMAKE_SOURCE_DIR}/src/StringUtils.h
${CMAKE_SOURCE_DIR}/src/Bindings/LuaState.h
${CMAKE_SOURCE_DIR}/src/Generating/ChunkDesc.h
${CMAKE_SOURCE_DIR}/src/Generating/PieceGenerator.h
${CMAKE_SOURCE_DIR}/src/Generating/Prefab.h
${CMAKE_SOURCE_DIR}/src/Generating/PrefabPiecePool.h
${CMAKE_SOURCE_DIR}/src/Noise/Noise.h
${CMAKE_SOURCE_DIR}/src/OSSupport/CriticalSection.h
${CMAKE_SOURCE_DIR}/src/OSSupport/Event.h
${CMAKE_SOURCE_DIR}/src/OSSupport/File.h
${CMAKE_SOURCE_DIR}/src/OSSupport/GZipFile.h
${CMAKE_SOURCE_DIR}/src/OSSupport/StackTrace.h
${CMAKE_SOURCE_DIR}/src/WorldStorage/FastNBT.h
${CMAKE_SOURCE_DIR}/src/WorldStorage/SchematicFileSerializer.h
)
set (SRCS
LoadablePieces.cpp
Stubs.cpp
LuaState_Typedefs.inc
LuaState_Declaration.inc
Bindings.h
)
if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")
add_flags_cxx("-Wno-error=conversion -Wno-error=old-style-cast")
add_flags_cxx("-Wno-error=global-constructors")
endif()
if (MSVC)
# Add the MSVC-specific LeakFinder sources:
list (APPEND SHARED_SRCS ${CMAKE_SOURCE_DIR}/src/LeakFinder.cpp ${CMAKE_SOURCE_DIR}/src/StackWalker.cpp)
list (APPEND SHARED_HDRS ${CMAKE_SOURCE_DIR}/src/LeakFinder.h ${CMAKE_SOURCE_DIR}/src/StackWalker.h)
endif()
source_group("Shared" FILES ${SHARED_SRCS} ${SHARED_HDRS})
source_group("Sources" FILES ${SRCS})
add_executable(LoadablePieces ${SRCS} ${SHARED_SRCS} ${SHARED_HDRS})
target_link_libraries(LoadablePieces tolualib zlib)
add_test(NAME LoadablePieces-test WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} COMMAND LoadablePieces)

View File

@ -0,0 +1,57 @@
// LoadablePieces.cpp
// Implements the LoadablePieces test main entrypoint
#include "Globals.h"
#ifdef _WIN32
#include <direct.h>
#define GetCurrentFolder _getcwd
#else
#include <unistd.h>
#define GetCurrentFolder getcwd
#endif
#include "Generating/PrefabPiecePool.h"
static int DoTest(void)
{
cPrefabPiecePool test;
auto res = test.LoadFromFile("Test.cubeset", true);
if (!res)
{
LOGWARNING("Loading from file \"Test.cubeset\" failed.");
return 1;
}
LOG("Loaded %u regular pieces and %u starting pieces", static_cast<unsigned>(test.GetAllPiecesCount()), static_cast<unsigned>(test.GetStartingPiecesCount()));
// Check that we loaded all the pieces:
testassert(test.GetAllPiecesCount() == 1);
testassert(test.GetStartingPiecesCount() == 1);
return 0;
}
int main(int argc, char * argv[])
{
// Print the current directory for reference:
char folder[FILENAME_MAX];
GetCurrentFolder(folder, sizeof(folder));
LOG("Running cPrefabPiecePool test from folder \"%s\".", folder);
// Run the test:
int res = DoTest();
LOG("cPrefabPiecePool loading test done: %s", (res == 0) ? "success" : "failure");
return res;
}

View File

@ -0,0 +1,4 @@
// LuaState_Declaration.inc
// Dummy include file needed for LuaState to compile successfully

View File

@ -0,0 +1,19 @@
// LuaState_Typedefs.inc
// Dummy include file needed for LuaState to compile successfully
// Forward-declare classes that are used in the API but never called:
struct HTTPRequest;
struct HTTPTemplateRequest;
class cPluginLua;
class cBoundingBox;
template <typename T> class cItemCallback;
class cEntity;

View File

@ -0,0 +1,283 @@
// Stubs.cpp
// Implements stubs of various MCServer methods that are needed for linking but not for runtime
// This is required so that we don't bring in the entire MCServer via dependencies
#include "Globals.h"
#include "BlockInfo.h"
#include "SelfTests.h"
#include "Bindings.h"
#include "Bindings/DeprecatedBindings.h"
#include "Bindings/ManualBindings.h"
#include "BlockEntities/BlockEntity.h"
#include "Blocks/BlockHandler.h"
#include "Generating/ChunkDesc.h"
// fwd:
struct lua_State;
// Prototypes, needed by clang:
extern "C" int luaopen_lsqlite3(lua_State * a_LuaState);
extern "C" int luaopen_lxp(lua_State * a_LuaState);
void cManualBindings::Bind(lua_State * a_LuaState)
{
}
void DeprecatedBindings::Bind(lua_State * a_LuaState)
{
}
int tolua_AllToLua_open(lua_State * a_LuaState)
{
return 0;
}
extern "C" int luaopen_lsqlite3(lua_State * a_LuaState)
{
return 0;
}
extern "C" int luaopen_lxp(lua_State * a_LuaState)
{
return 0;
}
cBlockInfo::~cBlockInfo()
{
}
void cBlockInfo::Initialize(cBlockInfo::cBlockInfoArray & a_BlockInfos)
{
// The piece-loading code uses the handlers for rotations, so we need valid handlers
// Insert dummy handlers:
for (size_t i = 0; i < ARRAYCOUNT(a_BlockInfos); i++)
{
a_BlockInfos[i].m_Handler = new cBlockHandler(static_cast<BLOCKTYPE>(i));
}
}
cBlockHandler::cBlockHandler(BLOCKTYPE a_BlockType)
{
}
bool cBlockHandler::GetPlacementBlockTypeMeta(
cChunkInterface & a_ChunkInterface, cPlayer * a_Player,
int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace,
int a_CursorX, int a_CursorY, int a_CursorZ,
BLOCKTYPE & a_BlockType, NIBBLETYPE & a_BlockMeta
)
{
return true;
}
void cBlockHandler::OnUpdate(cChunkInterface & cChunkInterface, cWorldInterface & a_WorldInterface, cBlockPluginInterface & a_PluginInterface, cChunk & a_Chunk, int a_BlockX, int a_BlockY, int a_BlockZ)
{
}
void cBlockHandler::OnPlacedByPlayer(cChunkInterface & a_ChunkInterface, cWorldInterface & a_WorldInterface, cPlayer * a_Player, const sSetBlock & a_BlockChange)
{
}
void cBlockHandler::OnDestroyedByPlayer(cChunkInterface & a_ChunkInterface, cWorldInterface & a_WorldInterface, cPlayer * a_Player, int a_BlockX, int a_BlockY, int a_BlockZ)
{
}
void cBlockHandler::OnPlaced(cChunkInterface & a_ChunkInterface, cWorldInterface & a_WorldInterface, int a_BlockX, int a_BlockY, int a_BlockZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta)
{
}
void cBlockHandler::OnDestroyed(cChunkInterface & a_ChunkInterface, cWorldInterface & a_WorldInterface, int a_BlockX, int a_BlockY, int a_BlockZ)
{
}
void cBlockHandler::NeighborChanged(cChunkInterface & a_ChunkInterface, int a_BlockX, int a_BlockY, int a_BlockZ)
{
}
void cBlockHandler::ConvertToPickups(cItems & a_Pickups, NIBBLETYPE a_BlockMeta)
{
}
void cBlockHandler::DropBlock(cChunkInterface & a_ChunkInterface, cWorldInterface & a_WorldInterface, cBlockPluginInterface & a_BlockPluginInterface, cEntity * a_Digger, int a_BlockX, int a_BlockY, int a_BlockZ, bool a_CanDrop)
{
}
bool cBlockHandler::CanBeAt(cChunkInterface & a_ChunkInterface, int a_BlockX, int a_BlockY, int a_BlockZ, const cChunk & a_Chunk)
{
return true;
}
bool cBlockHandler::CanDirtGrowGrass(NIBBLETYPE a_Meta)
{
return true;
}
bool cBlockHandler::IsUseable()
{
return false;
}
bool cBlockHandler::IsClickedThrough(void)
{
return false;
}
bool cBlockHandler::DoesIgnoreBuildCollision(void)
{
return (m_BlockType == E_BLOCK_AIR);
}
bool cBlockHandler::DoesDropOnUnsuitable(void)
{
return true;
}
void cBlockHandler::Check(cChunkInterface & a_ChunkInterface, cBlockPluginInterface & a_PluginInterface, int a_RelX, int a_RelY, int a_RelZ, cChunk & a_Chunk)
{
}
cBlockEntity * cBlockEntity::CreateByBlockType(BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta, int a_BlockX, int a_BlockY, int a_BlockZ, cWorld * a_World)
{
return nullptr;
}
cSelfTests::cSelfTests(void):
m_AllowRegistering(true)
{
}
cSelfTests & cSelfTests::Get(void)
{
static cSelfTests singleton;
return singleton;
}
void cSelfTests::Register(cSelfTests::SelfTestFunction a_TestFn, const AString & a_TestName)
{
}

View File

@ -0,0 +1,154 @@
-- Test.cubeset
-- This simple cubeset file is used for testing the cPrefabPiecePool loader.
Cubeset =
{
Metadata =
{
CubesetFormatVersion = 1,
},
Pieces =
{
-- One piece with inline definition:
{
Size =
{
x = 4,
y = 4,
z = 4,
},
Hitbox =
{
MinX = 0,
MinY = 0,
MinZ = 0,
MaxX = 3,
MaxY = 3,
MaxZ = 3,
},
BlockDefinitions =
{
".: 0: 0", -- air
"a: 1: 0", -- stone
"b: 24: 0", -- sandstone
"c: 8: 0", -- water
"d: 85: 0", -- fence
"m: 19: 0", -- sponge
},
BlockData =
{
-- Level 0
"aaaa", -- 0
"aaaa", -- 1
"aaaa", -- 2
"aaaa", -- 3
-- Level 1
"bbbb", -- 0
"bccb", -- 1
"bccb", -- 2
"bbbb", -- 3
-- Level 2
"bbbb", -- 0
"bccb", -- 1
"bccb", -- 2
"bbbb", -- 3
-- Level 3
"bbbb", -- 0
"bccb", -- 1
"bccb", -- 2
"bbbb", -- 3
},
Connectors =
{
{
Type = 2,
RelX = 2,
RelY = 2,
RelZ = 0,
Direction = 2, -- Z-
},
{
Type = 2,
RelX = 0,
RelY = 2,
RelZ = 1,
Direction = 4, -- X-
},
{
Type = 2,
RelX = 1,
RelY = 2,
RelZ = 3,
Direction = 3, -- Z+
},
{
Type = 2,
RelX = 3,
RelY = 2,
RelZ = 2,
Direction = 5, -- X+
},
},
Metadata =
{
["DefaultWeight"] = "100",
["AllowedRotations"] = "7",
["MergeStrategy"] = "msSpongePrint",
["IsStarting"] = "1",
["DepthWeight"] = "",
["ShouldExpandFloor"] = "1",
["MoveToGround"] = "1",
["AddWeightIfSame"] = "0",
},
},
-- One piece with external definition:
{
Hitbox =
{
MinX = 0,
MinY = 0,
MinZ = 0,
MaxX = 3,
MaxY = 3,
MaxZ = 3,
},
SchematicFileName = "Test1.schematic",
Connectors =
{
{
Type = 2,
RelX = 2,
RelY = 2,
RelZ = 0,
Direction = 2, -- Z-
},
},
Metadata =
{
["DefaultWeight"] = "100",
["AllowedRotations"] = "7",
["MergeStrategy"] = "msSpongePrint",
["IsStarting"] = "0",
["DepthWeight"] = "",
["ShouldExpandFloor"] = "1",
["MoveToGround"] = "0",
["AddWeightIfSame"] = "0",
},
},
}, -- Pieces
}

Binary file not shown.