From fe4253834919d6c742c59c701d3d5ce8285f4504 Mon Sep 17 00:00:00 2001 From: Mattes D Date: Sat, 24 Jun 2017 11:58:06 +0200 Subject: cBlockArea supports block entities. (#3795) --- Server/Plugins/APIDump/APIDesc.lua | 1936 +---------------------- Server/Plugins/APIDump/Classes/BlockArea.lua | 2197 ++++++++++++++++++++++++++ Server/Plugins/APIDump/Classes/World.lua | 11 +- src/Bindings/CMakeLists.txt | 1 + src/Bindings/LuaState.cpp | 24 + src/Bindings/LuaState.h | 4 + src/Bindings/ManualBindings.cpp | 373 +---- src/Bindings/ManualBindings.h | 96 +- src/Bindings/ManualBindings_BlockArea.cpp | 998 ++++++++++++ src/BlockArea.cpp | 1213 ++++++++++---- src/BlockArea.h | 141 +- src/BlockEntities/BlockEntity.cpp | 51 +- src/BlockEntities/BlockEntity.h | 8 + src/Chunk.cpp | 58 +- tests/Generating/Stubs.cpp | 26 + tests/LuaThreadStress/Stubs.cpp | 26 + tests/SchematicFileSerializer/Stubs.cpp | 35 + 17 files changed, 4554 insertions(+), 2644 deletions(-) create mode 100644 Server/Plugins/APIDump/Classes/BlockArea.lua create mode 100644 src/Bindings/ManualBindings_BlockArea.cpp diff --git a/Server/Plugins/APIDump/APIDesc.lua b/Server/Plugins/APIDump/APIDesc.lua index 8fff8b9fd..324c7503d 100644 --- a/Server/Plugins/APIDump/APIDesc.lua +++ b/Server/Plugins/APIDump/APIDesc.lua @@ -71,1938 +71,6 @@ return }, --]] - cBlockArea = - { - Desc = [[ - This class is used when multiple adjacent blocks are to be manipulated. Because of chunking - and multithreading, manipulating single blocks using {{cWorld|cWorld:SetBlock}}() is a rather - time-consuming operation (locks for exclusive access need to be obtained, chunk lookup is done - for each block), so whenever you need to manipulate multiple adjacent blocks, it's better to wrap - the operation into a cBlockArea access. cBlockArea is capable of reading / writing across chunk - boundaries, has no chunk lookups for get and set operations and is not subject to multithreading - locking (because it is not shared among threads).

-

- cBlockArea remembers its origin (MinX, MinY, MinZ coords in the Read() call) and therefore supports - absolute as well as relative get / set operations. Despite that, the contents of a cBlockArea can - be written back into the world at any coords.

-

- cBlockArea can hold any combination of the following datatypes:

- Read() and Write() functions have parameters that tell the class which datatypes to read / write. - Note that a datatype that has not been read cannot be written (FIXME).

-

- Typical usage:

- ]], - Functions = - { - Clear = - { - Notes = "Clears the object, resets it to zero size", - }, - constructor = - { - Returns = - { - { - Type = "cBlockArea", - }, - }, - Notes = "Creates a new empty cBlockArea object", - }, - CopyFrom = - { - Params = - { - { - Name = "BlockAreaSrc", - Type = "cBlockArea", - }, - }, - Notes = "Copies contents from BlockAreaSrc into self", - }, - CopyTo = - { - Params = - { - { - Name = "BlockAreaDst", - Type = "cBlockArea", - }, - }, - Notes = "Copies contents from self into BlockAreaDst.", - }, - CountNonAirBlocks = - { - Returns = - { - { - Type = "number", - }, - }, - Notes = "Returns the count of blocks that are not air. Returns 0 if blocktypes not available. Block metas are ignored (if present, air with any meta is still considered air).", - }, - CountSpecificBlocks = - { - { - Params = - { - { - Name = "BlockType", - Type = "number", - }, - }, - Returns = - { - { - Type = "number", - }, - }, - Notes = "Counts the number of occurences of the specified blocktype contained in the area.", - }, - { - Params = - { - { - Name = "BlockType", - Type = "number", - }, - { - Name = "BlockMeta", - Type = "number", - }, - }, - Returns = - { - { - Type = "number", - }, - }, - Notes = "Counts the number of occurrences of the specified blocktype + blockmeta combination contained in the area.", - }, - }, - Create = - { - { - Params = - { - { - Name = "SizeX", - Type = "number", - }, - { - Name = "SizeY", - Type = "number", - }, - { - Name = "SizeZ", - Type = "number", - }, - }, - Notes = "Initializes this BlockArea to an empty area of the specified size and origin of {0, 0, 0}. Datatypes are set to baTypes + baMetas. Any previous contents are lost.", - }, - { - Params = - { - { - Name = "SizeX", - Type = "number", - }, - { - Name = "SizeY", - Type = "number", - }, - { - Name = "SizeZ", - Type = "number", - }, - { - Name = "DataTypes", - Type = "string", - }, - }, - Notes = "Initializes this BlockArea to an empty area of the specified size and origin of {0, 0, 0}. Any previous contents are lost.", - }, - { - Params = - { - { - Name = "Size", - Type = "Vector3i", - }, - }, - Notes = "Creates a new area of the specified size. Datatypes are set to baTypes + baMetas. Origin is set to all zeroes. BlockTypes are set to air, block metas to zero, blocklights to zero and skylights to full light.", - }, - { - Params = - { - { - Name = "Size", - Type = "Vector3i", - }, - { - Name = "DataTypes", - Type = "string", - }, - }, - Notes = "Creates a new area of the specified size and contents. Origin is set to all zeroes. BlockTypes are set to air, block metas to zero, blocklights to zero and skylights to full light.", - }, - }, - Crop = - { - Params = - { - { - Name = "AddMinX", - Type = "number", - }, - { - Name = "SubMaxX", - Type = "number", - }, - { - Name = "AddMinY", - Type = "number", - }, - { - Name = "SubMaxY", - Type = "number", - }, - { - Name = "AddMinZ", - Type = "number", - }, - { - Name = "SubMaxZ", - Type = "number", - }, - }, - Notes = "Crops the specified number of blocks from each border. Modifies the size of this blockarea object.", - }, - DumpToRawFile = - { - Params = - { - { - Name = "FileName", - Type = "string", - }, - }, - Notes = "Dumps the raw data into a file. For debugging purposes only.", - }, - Expand = - { - Params = - { - { - Name = "SubMinX", - Type = "number", - }, - { - Name = "AddMaxX", - Type = "number", - }, - { - Name = "SubMinY", - Type = "number", - }, - { - Name = "AddMaxY", - Type = "number", - }, - { - Name = "SubMinZ", - Type = "number", - }, - { - Name = "AddMaxZ", - Type = "number", - }, - }, - Notes = "Expands the specified number of blocks from each border. Modifies the size of this blockarea object. New blocks created with this operation are filled with zeroes.", - }, - Fill = - { - Params = - { - { - Name = "DataTypes", - Type = "string", - }, - { - Name = "BlockType", - Type = "number", - }, - { - Name = "BlockMeta", - Type = "number", - IsOptional = true, - }, - { - Name = "BlockLight", - Type = "number", - IsOptional = true, - }, - { - Name = "BlockSkyLight", - Type = "number", - IsOptional = true, - }, - }, - Notes = "Fills the entire block area with the same values, specified. Uses the DataTypes param to determine which content types are modified.", - }, - FillRelCuboid = - { - { - Params = - { - { - Name = "RelCuboid", - Type = "cCuboid", - }, - { - Name = "DataTypes", - Type = "string", - }, - { - Name = "BlockType", - Type = "number", - }, - { - Name = "BlockMeta", - Type = "number", - IsOptional = true, - }, - { - Name = "BlockLight", - Type = "number", - IsOptional = true, - }, - { - Name = "BlockSkyLight", - Type = "number", - IsOptional = true, - }, - }, - Notes = "Fills the specified cuboid (in relative coords) with the same values (like Fill() ).", - }, - { - Params = - { - { - Name = "MinRelX", - Type = "number", - }, - { - Name = "MaxRelX", - Type = "number", - }, - { - Name = "MinRelY", - Type = "number", - }, - { - Name = "MaxRelY", - Type = "number", - }, - { - Name = "MinRelZ", - Type = "number", - }, - { - Name = "MaxRelZ", - Type = "number", - }, - { - Name = "DataTypes", - Type = "string", - }, - { - Name = "BlockType", - Type = "number", - }, - { - Name = "BlockMeta", - Type = "number", - IsOptional = true, - }, - { - Name = "BlockLight", - Type = "number", - IsOptional = true, - }, - { - Name = "BlockSkyLight", - Type = "number", - IsOptional = true, - }, - }, - Notes = "Fills the specified cuboid with the same values (like Fill() ).", - }, - }, - GetBlockLight = - { - Params = - { - { - Name = "BlockX", - Type = "number", - }, - { - Name = "BlockY", - Type = "number", - }, - { - Name = "BlockZ", - Type = "number", - }, - }, - Returns = - { - { - Name = "NIBBLETYPE", - Type = "number", - }, - }, - Notes = "Returns the blocklight at the specified absolute coords", - }, - GetBlockMeta = - { - Params = - { - { - Name = "BlockX", - Type = "number", - }, - { - Name = "BlockY", - Type = "number", - }, - { - Name = "BlockZ", - Type = "number", - }, - }, - Returns = - { - { - Name = "NIBBLETYPE", - Type = "number", - }, - }, - Notes = "Returns the block meta at the specified absolute coords", - }, - GetBlockSkyLight = - { - Params = - { - { - Name = "BlockX", - Type = "number", - }, - { - Name = "BlockY", - Type = "number", - }, - { - Name = "BlockZ", - Type = "number", - }, - }, - Returns = - { - { - Name = "NIBBLETYPE", - Type = "number", - }, - }, - Notes = "Returns the skylight at the specified absolute coords", - }, - GetBlockType = - { - Params = - { - { - Name = "BlockX", - Type = "number", - }, - { - Name = "BlockY", - Type = "number", - }, - { - Name = "BlockZ", - Type = "number", - }, - }, - Returns = - { - { - Name = "BLOCKTYPE", - Type = "number", - }, - }, - Notes = "Returns the block type at the specified absolute coords", - }, - GetBlockTypeMeta = - { - Params = - { - { - Name = "BlockX", - Type = "number", - }, - { - Name = "BlockY", - Type = "number", - }, - { - Name = "BlockZ", - Type = "number", - }, - }, - Returns = - { - { - Name = "BLOCKTYPE", - Type = "number", - }, - { - Name = "NIBBLETYPE", - Type = "number", - }, - }, - Notes = "Returns the block type and meta at the specified absolute coords", - }, - GetCoordRange = - { - Returns = - { - { - Name = "MaxX", - Type = "number", - }, - { - Name = "MaxY", - Type = "number", - }, - { - Name = "MaxZ", - Type = "number", - }, - }, - Notes = "Returns the maximum relative coords in all 3 axes. See also GetSize().", - }, - GetDataTypes = - { - Returns = - { - { - Type = "number", - }, - }, - Notes = "Returns the mask of datatypes that the object is currently holding", - }, - GetNonAirCropRelCoords = - { - Params = - { - { - Name = "IgnoredBlockType", - Type = "number", - IsOptional = true, - }, - }, - Returns = - { - { - Name = "MinRelX", - Type = "number", - }, - { - Name = "MinRelY", - Type = "number", - }, - { - Name = "MinRelZ", - Type = "number", - }, - { - Name = "MaxRelX", - Type = "number", - }, - { - Name = "MaxRelY", - Type = "number", - }, - { - Name = "MaxRelZ", - Type = "number", - }, - }, - Notes = "Returns the minimum and maximum coords in each direction for the first block in each direction of type different to IgnoredBlockType (E_BLOCK_AIR by default). If there are no non-ignored blocks within the area, or blocktypes are not present, the returned values are reverse-ranges (MinX <- m_RangeX, MaxX <- 0 etc.). IgnoreBlockType defaults to air.", - }, - GetOrigin = - { - Returns = - { - { - Name = "OriginX", - Type = "number", - }, - { - Name = "OriginY", - Type = "number", - }, - { - Name = "OriginZ", - Type = "number", - }, - }, - Notes = "Returns the origin coords of where the area was read from.", - }, - GetOriginX = - { - Returns = - { - { - Type = "number", - }, - }, - Notes = "Returns the origin x-coord", - }, - GetOriginY = - { - Returns = - { - { - Type = "number", - }, - }, - Notes = "Returns the origin y-coord", - }, - GetOriginZ = - { - Returns = - { - { - Type = "number", - }, - }, - Notes = "Returns the origin z-coord", - }, - GetRelBlockLight = - { - Params = - { - { - Name = "RelBlockX", - Type = "number", - }, - { - Name = "RelBlockY", - Type = "number", - }, - { - Name = "RelBlockZ", - Type = "number", - }, - }, - Returns = - { - { - Name = "NIBBLETYPE", - Type = "number", - }, - }, - Notes = "Returns the blocklight at the specified relative coords", - }, - GetRelBlockMeta = - { - Params = - { - { - Name = "RelBlockX", - Type = "number", - }, - { - Name = "RelBlockY", - Type = "number", - }, - { - Name = "RelBlockZ", - Type = "number", - }, - }, - Returns = - { - { - Name = "NIBBLETYPE", - Type = "number", - }, - }, - Notes = "Returns the block meta at the specified relative coords", - }, - GetRelBlockSkyLight = - { - Params = - { - { - Name = "RelBlockX", - Type = "number", - }, - { - Name = "RelBlockY", - Type = "number", - }, - { - Name = "RelBlockZ", - Type = "number", - }, - }, - Returns = - { - { - Name = "NIBBLETYPE", - Type = "number", - }, - }, - Notes = "Returns the skylight at the specified relative coords", - }, - GetRelBlockType = - { - Params = - { - { - Name = "RelBlockX", - Type = "number", - }, - { - Name = "RelBlockY", - Type = "number", - }, - { - Name = "RelBlockZ", - Type = "number", - }, - }, - Returns = - { - { - Name = "BLOCKTYPE", - Type = "number", - }, - }, - Notes = "Returns the block type at the specified relative coords", - }, - GetRelBlockTypeMeta = - { - Params = - { - { - Name = "RelBlockX", - Type = "number", - }, - { - Name = "RelBlockY", - Type = "number", - }, - { - Name = "RelBlockZ", - Type = "number", - }, - }, - Returns = - { - { - Name = "BLOCKTYPE", - Type = "number", - }, - { - Name = "NIBBLETYPE", - Type = "number", - }, - }, - Notes = "Returns the block type and meta at the specified relative coords", - }, - GetSize = - { - Returns = - { - { - Name = "SizeX", - Type = "number", - }, - { - Name = "SizeY", - Type = "number", - }, - { - Name = "SizeZ", - Type = "number", - }, - }, - Notes = "Returns the size of the area in all 3 axes. See also GetCoordRange().", - }, - GetSizeX = - { - Returns = - { - { - Type = "number", - }, - }, - Notes = "Returns the size of the held data in the x-axis", - }, - GetSizeY = - { - Returns = - { - { - Type = "number", - }, - }, - Notes = "Returns the size of the held data in the y-axis", - }, - GetSizeZ = - { - Returns = - { - { - Type = "number", - }, - }, - Notes = "Returns the size of the held data in the z-axis", - }, - GetVolume = - { - Returns = - { - { - Type = "number", - }, - }, - Notes = "Returns the volume of the area - the total number of blocks stored within.", - }, - GetWEOffset = - { - Returns = - { - { - Type = "Vector3i", - }, - }, - Notes = "Returns the WE offset, a data value sometimes stored in the schematic files. Cuberite doesn't use this value, but provides access to it using this method. The default is {0, 0, 0}.", - }, - HasBlockLights = - { - Returns = - { - { - Type = "boolean", - }, - }, - Notes = "Returns true if current datatypes include blocklight", - }, - HasBlockMetas = - { - Returns = - { - { - Type = "boolean", - }, - }, - Notes = "Returns true if current datatypes include block metas", - }, - HasBlockSkyLights = - { - Returns = - { - { - Type = "boolean", - }, - }, - Notes = "Returns true if current datatypes include skylight", - }, - HasBlockTypes = - { - Returns = - { - { - Type = "boolean", - }, - }, - Notes = "Returns true if current datatypes include block types", - }, - LoadFromSchematicFile = - { - Params = - { - { - Name = "FileName", - Type = "string", - }, - }, - Returns = - { - { Type = "boolean" }, - }, - Notes = "Clears current content and loads new content from the specified schematic file. Returns true if successful. Returns false and logs error if unsuccessful, old content is preserved in such a case.", - }, - LoadFromSchematicString = - { - Params = - { - { - Name = "SchematicData", - Type = "string", - }, - }, - Returns = - { - { Type = "boolean" }, - }, - Notes = "Clears current content and loads new content from the specified string (assumed to contain .schematic data). Returns true if successful. Returns false and logs error if unsuccessful, old content is preserved in such a case.", - }, - Merge = - { - { - Params = - { - { - Name = "BlockAreaSrc", - Type = "cBlockArea", - }, - { - Name = "RelMinCoords", - Type = "number", - }, - { - Name = "Strategy", - Type = "string", - }, - }, - Notes = "Merges BlockAreaSrc into this object at the specified relative coords, using the specified strategy", - }, - { - Params = - { - { - Name = "BlockAreaSrc", - Type = "cBlockArea", - }, - { - Name = "RelX", - Type = "number", - }, - { - Name = "RelY", - Type = "number", - }, - { - Name = "RelZ", - Type = "number", - }, - { - Name = "Strategy", - Type = "string", - }, - }, - Notes = "Merges BlockAreaSrc into this object at the specified relative coords, using the specified strategy", - }, - }, - MirrorXY = - { - Notes = "Mirrors this block area around the XY plane. Modifies blocks' metas (if present) to match (i. e. furnaces facing the opposite direction).", - }, - MirrorXYNoMeta = - { - Notes = "Mirrors this block area around the XY plane. Doesn't modify blocks' metas.", - }, - MirrorXZ = - { - Notes = "Mirrors this block area around the XZ plane. Modifies blocks' metas (if present)", - }, - MirrorXZNoMeta = - { - Notes = "Mirrors this block area around the XZ plane. Doesn't modify blocks' metas.", - }, - MirrorYZ = - { - Notes = "Mirrors this block area around the YZ plane. Modifies blocks' metas (if present)", - }, - MirrorYZNoMeta = - { - Notes = "Mirrors this block area around the YZ plane. Doesn't modify blocks' metas.", - }, - Read = - { - { - Params = - { - { - Name = "World", - Type = "cWorld", - }, - { - Name = "Cuboid", - Type = "cCuboid", - }, - }, - Returns = - { - { - Name = "IsSuccess", - Type = "boolean", - }, - }, - Notes = "Reads the area from World, returns true if successful. baTypes and baMetas are read.", - }, - { - Params = - { - { - Name = "World", - Type = "cWorld", - }, - { - Name = "Cuboid", - Type = "cCuboid", - }, - { - Name = "DataTypes", - Type = "number", - }, - }, - Returns = - { - { - Name = "IsSuccess", - Type = "boolean", - }, - }, - Notes = "Reads the area from World, returns true if successful. DataTypes is the sum of baXXX datatypes to be read", - }, - { - Params = - { - { - Name = "World", - Type = "cWorld", - }, - { - Name = "Point1", - Type = "Vector3i", - }, - { - Name = "Point2", - Type = "Vector3i", - }, - }, - Returns = - { - { - Name = "IsSuccess", - Type = "boolean", - }, - }, - Notes = "Reads the area from World, returns true if successful. baTypes and baMetas are read.", - }, - { - Params = - { - { - Name = "World", - Type = "cWorld", - }, - { - Name = "Point1", - Type = "Vector3i", - }, - { - Name = "Point2", - Type = "Vector3i", - }, - { - Name = "DataTypes", - Type = "number", - }, - }, - Returns = - { - { - Name = "IsSuccess", - Type = "boolean", - }, - }, - Notes = "Reads the area from World, returns true if successful. DataTypes is a sum of baXXX datatypes to be read.", - }, - { - Params = - { - { - Name = "World", - Type = "cWorld", - }, - { - Name = "MinX", - Type = "number", - }, - { - Name = "MaxX", - Type = "number", - }, - { - Name = "MinY", - Type = "number", - }, - { - Name = "MaxY", - Type = "number", - }, - { - Name = "MinZ", - Type = "number", - }, - { - Name = "MaxZ", - Type = "number", - }, - }, - Returns = - { - { - Type = "boolean", - }, - }, - Notes = "Reads the area from World, returns true if successful. baTypes and baMetas are read.", - }, - { - Params = - { - { - Name = "World", - Type = "cWorld", - }, - { - Name = "MinX", - Type = "number", - }, - { - Name = "MaxX", - Type = "number", - }, - { - Name = "MinY", - Type = "number", - }, - { - Name = "MaxY", - Type = "number", - }, - { - Name = "MinZ", - Type = "number", - }, - { - Name = "MaxZ", - Type = "number", - }, - { - Name = "DataTypes", - Type = "number", - }, - }, - Returns = - { - { - Type = "boolean", - }, - }, - Notes = "Reads the area from World, returns true if successful. DataTypes is a sum of baXXX datatypes to read.", - }, - }, - RelLine = - { - { - Params = - { - { - Name = "RelPoint1", - Type = "Vector3i", - }, - { - Name = "RelPoint2", - Type = "Vector3i", - }, - { - Name = "DataTypes", - Type = "number", - }, - { - Name = "BlockType", - Type = "number", - }, - { - Name = "BlockMeta", - Type = "number", - IsOptional = true, - }, - { - Name = "BlockLight", - Type = "number", - IsOptional = true, - }, - { - Name = "BlockSkyLight", - Type = "number", - IsOptional = true, - }, - }, - Notes = "Draws a line between the two specified points. Sets only datatypes specified by DataTypes (baXXX constants).", - }, - { - Params = - { - { - Name = "RelX1", - Type = "number", - }, - { - Name = "RelY1", - Type = "number", - }, - { - Name = "RelZ1", - Type = "number", - }, - { - Name = "RelX2", - Type = "number", - }, - { - Name = "RelY2", - Type = "number", - }, - { - Name = "RelZ2", - Type = "number", - }, - { - Name = "DataTypes", - Type = "string", - }, - { - Name = "BlockType", - Type = "number", - }, - { - Name = "BlockMeta", - Type = "number", - IsOptional = true, - }, - { - Name = "BlockLight", - Type = "number", - IsOptional = true, - }, - { - Name = "BlockSkyLight", - Type = "number", - IsOptional = true, - }, - }, - Notes = "Draws a line between the two specified points. Sets only datatypes specified by DataTypes (baXXX constants).", - }, - }, - RotateCCW = - { - Notes = "Rotates the block area around the Y axis, counter-clockwise (east -> north). Modifies blocks' metas (if present) to match.", - }, - RotateCCWNoMeta = - { - Notes = "Rotates the block area around the Y axis, counter-clockwise (east -> north). Doesn't modify blocks' metas.", - }, - RotateCW = - { - Notes = "Rotates the block area around the Y axis, clockwise (north -> east). Modifies blocks' metas (if present) to match.", - }, - RotateCWNoMeta = - { - Notes = "Rotates the block area around the Y axis, clockwise (north -> east). Doesn't modify blocks' metas.", - }, - SaveToSchematicFile = - { - Params = - { - { - Name = "FileName", - Type = "string", - }, - }, - Returns = - { - { - Type = "boolean", - }, - }, - Notes = "Saves the current contents to a schematic file. Returns true if successful.", - }, - SaveToSchematicString = - { - Returns = - { - { - Type = "string", - }, - }, - Notes = "Saves the current contents to a string (in a .schematic file format). Returns the data if successful, nil if failed.", - }, - SetBlockLight = - { - Params = - { - { - Name = "BlockX", - Type = "number", - }, - { - Name = "BlockY", - Type = "number", - }, - { - Name = "BlockZ", - Type = "number", - }, - { - Name = "BlockLight", - Type = "number", - }, - }, - Notes = "Sets the blocklight at the specified absolute coords", - }, - SetBlockMeta = - { - Params = - { - { - Name = "BlockX", - Type = "number", - }, - { - Name = "BlockY", - Type = "number", - }, - { - Name = "BlockZ", - Type = "number", - }, - { - Name = "BlockMeta", - Type = "number", - }, - }, - Notes = "Sets the block meta at the specified absolute coords", - }, - SetBlockSkyLight = - { - Params = - { - { - Name = "BlockX", - Type = "number", - }, - { - Name = "BlockY", - Type = "number", - }, - { - Name = "BlockZ", - Type = "number", - }, - { - Name = "BlockSkyLight", - Type = "number", - }, - }, - Notes = "Sets the skylight at the specified absolute coords", - }, - SetBlockType = - { - Params = - { - { - Name = "BlockX", - Type = "number", - }, - { - Name = "BlockY", - Type = "number", - }, - { - Name = "BlockZ", - Type = "number", - }, - { - Name = "BlockType", - Type = "number", - }, - }, - Notes = "Sets the block type at the specified absolute coords", - }, - SetBlockTypeMeta = - { - Params = - { - { - Name = "BlockX", - Type = "number", - }, - { - Name = "BlockY", - Type = "number", - }, - { - Name = "BlockZ", - Type = "number", - }, - { - Name = "BlockType", - Type = "number", - }, - { - Name = "BlockMeta", - Type = "number", - }, - }, - Notes = "Sets the block type and meta at the specified absolute coords", - }, - SetOrigin = - { - { - Params = - { - { - Name = "Origin", - Type = "Vector3i", - }, - }, - Notes = "Resets the origin for the absolute coords. Only affects how absolute coords are translated into relative coords.", - }, - { - Params = - { - { - Name = "OriginX", - Type = "number", - }, - { - Name = "OriginY", - Type = "number", - }, - { - Name = "OriginZ", - Type = "number", - }, - }, - Notes = "Resets the origin for the absolute coords. Only affects how absolute coords are translated into relative coords.", - }, - }, - SetRelBlockLight = - { - Params = - { - { - Name = "RelBlockX", - Type = "number", - }, - { - Name = "RelBlockY", - Type = "number", - }, - { - Name = "RelBlockZ", - Type = "number", - }, - { - Name = "BlockLight", - Type = "number", - }, - }, - Notes = "Sets the blocklight at the specified relative coords", - }, - SetRelBlockMeta = - { - Params = - { - { - Name = "RelBlockX", - Type = "number", - }, - { - Name = "RelBlockY", - Type = "number", - }, - { - Name = "RelBlockZ", - Type = "number", - }, - { - Name = "BlockMeta", - Type = "number", - }, - }, - Notes = "Sets the block meta at the specified relative coords", - }, - SetRelBlockSkyLight = - { - Params = - { - { - Name = "RelBlockX", - Type = "number", - }, - { - Name = "RelBlockY", - Type = "number", - }, - { - Name = "RelBlockZ", - Type = "number", - }, - { - Name = "BlockSkyLight", - Type = "number", - }, - }, - Notes = "Sets the skylight at the specified relative coords", - }, - SetRelBlockType = - { - Params = - { - { - Name = "RelBlockX", - Type = "number", - }, - { - Name = "RelBlockY", - Type = "number", - }, - { - Name = "RelBlockZ", - Type = "number", - }, - { - Name = "BlockType", - Type = "number", - }, - }, - Notes = "Sets the block type at the specified relative coords", - }, - SetRelBlockTypeMeta = - { - Params = - { - { - Name = "RelBlockX", - Type = "number", - }, - { - Name = "RelBlockY", - Type = "number", - }, - { - Name = "RelBlockZ", - Type = "number", - }, - { - Name = "BlockType", - Type = "number", - }, - { - Name = "BlockMeta", - Type = "number", - }, - }, - Notes = "Sets the block type and meta at the specified relative coords", - }, - SetWEOffset = - { - { - Params = - { - { - Name = "Offset", - Type = "Vector3i", - }, - }, - Notes = "Sets the WE offset, a data value sometimes stored in the schematic files. Mostly used for WorldEdit. Cuberite doesn't use this value, but provides access to it using this method.", - }, - { - Params = - { - { - Name = "OffsetX", - Type = "number", - }, - { - Name = "OffsetY", - Type = "number", - }, - { - Name = "OffsetZ", - Type = "number", - }, - }, - Notes = "Sets the WE offset, a data value sometimes stored in the schematic files. Mostly used for WorldEdit. Cuberite doesn't use this value, but provides access to it using this method.", - }, - }, - Write = - { - { - Params = - { - { - Name = "World", - Type = "cWorld", - }, - { - Name = "MinPoint", - Type = "Vector3i", - }, - }, - Returns = - { - { - Name = "IsSuccess", - Type = "boolean", - }, - }, - Notes = "Writes the area into World at the specified coords, returns true if successful. baTypes and baMetas are written.", - }, - { - Params = - { - { - Name = "World", - Type = "cWorld", - }, - { - Name = "MinPoint", - Type = "Vector3i", - }, - { - Name = "DataTypes", - Type = "number", - }, - }, - Returns = - { - { - Name = "IsSuccess", - Type = "boolean", - }, - }, - Notes = "Writes the area into World at the specified coords, returns true if successful. DataTypes is the sum of baXXX datatypes to write.", - }, - { - Params = - { - { - Name = "World", - Type = "cWorld", - }, - { - Name = "MinX", - Type = "number", - }, - { - Name = "MinY", - Type = "number", - }, - { - Name = "MinZ", - Type = "number", - }, - }, - Returns = - { - { - Name = "IsSuccess", - Type = "boolean", - }, - }, - Notes = "Writes the area into World at the specified coords, returns true if successful. baTypes and baMetas are written.", - }, - { - Params = - { - { - Name = "World", - Type = "cWorld", - }, - { - Name = "MinX", - Type = "number", - }, - { - Name = "MinY", - Type = "number", - }, - { - Name = "MinZ", - Type = "number", - }, - { - Name = "DataTypes", - Type = "number", - }, - }, - Returns = - { - { - Name = "IsSuccess", - Type = "boolean", - }, - }, - Notes = "Writes the area into World at the specified coords, returns true if successful. DataTypes is the sum of baXXX datatypes to write.", - }, - }, - }, - Constants = - { - baLight = - { - Notes = "Operations should work on block (emissive) light", - }, - baMetas = - { - Notes = "Operations should work on block metas", - }, - baSkyLight = - { - Notes = "Operations should work on skylight", - }, - baTypes = - { - Notes = "Operation should work on block types", - }, - msDifference = - { - Notes = "Block becomes air if 'self' and src are the same. Otherwise it becomes the src block.", - }, - msFillAir = - { - Notes = "'self' is overwritten by Src only where 'self' has air blocks", - }, - msImprint = - { - Notes = "Src overwrites 'self' anywhere where 'self' has non-air blocks", - }, - msLake = - { - Notes = "Special mode for merging lake images", - }, - msMask = - { - Notes = "The blocks that are exactly the same are kept in 'self', all differing blocks are replaced by air", - }, - msOverwrite = - { - Notes = "Src overwrites anything in 'self'", - }, - msSimpleCompare = - { - Notes = "The blocks that are exactly the same are replaced with air, all differing blocks are replaced by stone", - }, - msSpongePrint = - { - Notes = "Similar to msImprint, sponge block doesn't overwrite anything, all other blocks overwrite everything", - }, - }, - ConstantGroups = - { - BATypes = - { - Include = "ba.*", - TextBefore = [[ - The following constants are used to signalize the datatype to read or write: - ]], - }, - eMergeStrategy = - { - Include = "ms.*", - TextAfter = "See below for a detailed explanation of the individual merge strategies.", - TextBefore = [[ - The Merge() function can use different strategies to combine the source and destination blocks. - The following constants are used: - ]], - }, - }, - AdditionalInfo = - { - { - Header = "Merge strategies", - Contents = [[ -

The strategy parameter specifies how individual blocks are combined together, using the table below. -

- - - - - - - - - - - - - - - - - - - - - - -
area blockresult
this Src msOverwrite msFillAir msImprint
air air air air air
A air air A A
air B B B B
A B B A B
A A A A A
- -

- So to sum up: -

    -
  1. msOverwrite completely overwrites all blocks with the Src's blocks
  2. -
  3. msFillAir overwrites only those blocks that were air
  4. -
  5. msImprint overwrites with only those blocks that are non-air
  6. -
-

- -

Special strategies

-

For each strategy, evaluate the table rows from top downwards, the first match wins.

- -

- msDifference - changes all the blocks which are the same to air. Otherwise the source block gets placed. -

- - - - - - - -
area block Notes
* B B The blocks are different so we use block B
B B Air The blocks are the same so we get air.
- - -

- msLake - used for merging areas with lava and water lakes, in the appropriate generator. -

- - - - - - - - - - - - - - - - - - - - - - - - - - - -
area block Notes
self Src result
A sponge A Sponge is the NOP block
* air air Air always gets hollowed out, even under the oceans
water * water Water is never overwritten
lava * lava Lava is never overwritten
* water water Water always overwrites anything
* lava lava Lava always overwrites anything
dirt stone stone Stone overwrites dirt
grass stone stone ... and grass
mycelium stone stone ... and mycelium
A stone A ... but nothing else
A * A Everything else is left as it is
- -

- msSpongePrint - used for most prefab-generators to merge the prefabs. Similar to - msImprint, but uses the sponge block as the NOP block instead, so that the prefabs may carve out air - pockets, too. -

- - - - - - - - - -
area block Notes
self Src result
A sponge A Sponge is the NOP block
* B B Everything else overwrites anything
- -

- msMask - the blocks that are the same in the other area are kept, all the - differing blocks are replaced with air. Meta is used in the comparison, too, two blocks of the - same type but different meta are considered different and thus replaced with air. -

- - - - - - - - - -
area block Notes
self Src result
A A A Same blocks are kept
A non-A air Differing blocks are replaced with air
- -

- msDifference - the blocks that are the same in both areas are replaced with air, all the - differing blocks are kept from the first area. Meta is used in the comparison, too, two blocks of the - same type but different meta are considered different. -

- - - - - - - - - -
area block Notes
self Src result
A A air Same blocks are replaced with air
A non-A A Differing blocks are kept from 'self'
- -

- msSimpleCompare - the blocks that are the same in both areas are replaced with air, all the - differing blocks are replaced with stone. Meta is used in the comparison, too, two blocks of the - same type but different meta are considered different. -

- - - - - - - - - -
area block Notes
self Src result
A A air Same blocks are replaced with air
A non-A stone Differing blocks are replaced with stone
-]], - }, - }, - }, cBlockInfo = { Desc = [[ @@ -7979,7 +6047,7 @@ These ItemGrids are available in the API and can be manipulated by the plugins, }, }, Notes = "Returns current item in shield slot.", - }, + }, GetInventoryGrid = { Returns = @@ -8197,7 +6265,7 @@ These ItemGrids are available in the API and can be manipulated by the plugins, }, }, Notes = "Sets the shield slot content", - }, + }, SetInventorySlot = { Params = diff --git a/Server/Plugins/APIDump/Classes/BlockArea.lua b/Server/Plugins/APIDump/Classes/BlockArea.lua new file mode 100644 index 000000000..af6422aa5 --- /dev/null +++ b/Server/Plugins/APIDump/Classes/BlockArea.lua @@ -0,0 +1,2197 @@ +return +{ + cBlockArea = + { + Desc = [[ + This class is used when multiple adjacent blocks are to be manipulated. Because of chunking + and multithreading, manipulating single blocks using {{cWorld|cWorld:SetBlock}}() is a rather + time-consuming operation (locks for exclusive access need to be obtained, chunk lookup is done + for each block), so whenever you need to manipulate multiple adjacent blocks, it's better to wrap + the operation into a cBlockArea access. cBlockArea is capable of reading / writing across chunk + boundaries, has no chunk lookups for get and set operations and is not subject to multithreading + locking (because it is not shared among threads).

+

+ cBlockArea remembers its origin (MinX, MinY, MinZ coords in the Read() call) and therefore supports + absolute as well as relative get / set operations. Despite that, the contents of a cBlockArea can + be written back into the world at any coords. Most functions in this class come in pair, one that + works with the absolute coords (what the coords would have been in the original world the area was read + from) and one (usually with "Rel" in their name) that work on the relative coords (those range from + zero to Size - 1). Also note that most functions will raise an error if an out-of-range coord is + supplied to them.

+

+ cBlockArea can hold any combination of the following datatypes:

+ Read() and Write() functions have parameters that tell the class which datatypes to read / write. + Note that a datatype that has not been read cannot be written.

+

Block entities stored inside a cBlockArea object have their position set to the relative position + within the area.

+

+ Typical usage:

+ ]], + Functions = + { + Clear = + { + Notes = "Clears the object, resets it to zero size", + }, + constructor = + { + Returns = + { + { + Type = "cBlockArea", + }, + }, + Notes = "Creates a new empty cBlockArea object", + }, + CopyFrom = + { + Params = + { + { + Name = "BlockAreaSrc", + Type = "cBlockArea", + }, + }, + Notes = "Copies contents from BlockAreaSrc into self", + }, + CopyTo = + { + Params = + { + { + Name = "BlockAreaDst", + Type = "cBlockArea", + }, + }, + Notes = "Copies contents from self into BlockAreaDst.", + }, + CountNonAirBlocks = + { + Returns = + { + { + Type = "number", + }, + }, + Notes = "Returns the count of blocks that are not air. Returns 0 if blocktypes not available. Block metas are ignored (if present, air with any meta is still considered air).", + }, + CountSpecificBlocks = + { + { + Params = + { + { + Name = "BlockType", + Type = "number", + }, + }, + Returns = + { + { + Type = "number", + }, + }, + Notes = "Counts the number of occurences of the specified blocktype contained in the area.", + }, + { + Params = + { + { + Name = "BlockType", + Type = "number", + }, + { + Name = "BlockMeta", + Type = "number", + }, + }, + Returns = + { + { + Type = "number", + }, + }, + Notes = "Counts the number of occurrences of the specified blocktype + blockmeta combination contained in the area.", + }, + }, + Create = + { + { + Params = + { + { + Name = "SizeX", + Type = "number", + }, + { + Name = "SizeY", + Type = "number", + }, + { + Name = "SizeZ", + Type = "number", + }, + }, + Notes = "Initializes this BlockArea to an empty area of the specified size and origin of {0, 0, 0}. Datatypes are set to baTypes + baMetas. Any previous contents are lost.", + }, + { + Params = + { + { + Name = "SizeX", + Type = "number", + }, + { + Name = "SizeY", + Type = "number", + }, + { + Name = "SizeZ", + Type = "number", + }, + { + Name = "DataTypes", + Type = "string", + }, + }, + Notes = "Initializes this BlockArea to an empty area of the specified size and origin of {0, 0, 0}. Any previous contents are lost.", + }, + { + Params = + { + { + Name = "Size", + Type = "Vector3i", + }, + }, + Notes = "Creates a new area of the specified size. Datatypes are set to baTypes + baMetas. Origin is set to all zeroes. BlockTypes are set to air, block metas to zero, blocklights to zero and skylights to full light.", + }, + { + Params = + { + { + Name = "Size", + Type = "Vector3i", + }, + { + Name = "DataTypes", + Type = "string", + }, + }, + Notes = "Creates a new area of the specified size and contents. Origin is set to all zeroes. BlockTypes are set to air, block metas to zero, blocklights to zero and skylights to full light.", + }, + }, + Crop = + { + Params = + { + { + Name = "AddMinX", + Type = "number", + }, + { + Name = "SubMaxX", + Type = "number", + }, + { + Name = "AddMinY", + Type = "number", + }, + { + Name = "SubMaxY", + Type = "number", + }, + { + Name = "AddMinZ", + Type = "number", + }, + { + Name = "SubMaxZ", + Type = "number", + }, + }, + Notes = "Crops the specified number of blocks from each border. Modifies the size of this blockarea object.", + }, -- Crop + + DoWithBlockEntityAt = + { + { + Params = + { + { + Name = "BlockX", + Type = "number", + }, + { + Name = "BlockY", + Type = "number", + }, + { + Name = "BlockZ", + Type = "number", + }, + { + Name = "Callback", + Type = "function", + }, + }, + Returns = + { + Name = "HasCalled", + Type = "boolean", + }, + Notes = "Calls the specified callback with the block entity at the specified absolute coords. The CallbackFunction has the following signature:
function Callback({{cBlockEntity|BlockEntity}})
Returns false if there's no block entity at the specified coords. Returns the value that the callback has returned otherwise.", + }, + { + Params = + { + { + Name = "Coords", + Type = "Vector3i", + }, + { + Name = "Callback", + Type = "function", + }, + }, + Returns = + { + Name = "HasCalled", + Type = "boolean", + }, + Notes = "Calls the specified callback with the block entity at the specified absolute coords. The CallbackFunction has the following signature:
function Callback({{cBlockEntity|BlockEntity}})
Returns false if there's no block entity at the specified coords. Returns the value that the callback has returned otherwise.", + }, + }, -- DoWithBlockEntityAt + + DoWithBlockEntityRelAt = + { + { + Params = + { + { + Name = "RelX", + Type = "number", + }, + { + Name = "RelY", + Type = "number", + }, + { + Name = "RelZ", + Type = "number", + }, + { + Name = "Callback", + Type = "function", + }, + }, + Returns = + { + Name = "HasCalled", + Type = "boolean", + }, + Notes = "Calls the specified callback with the block entity at the specified relative coords. The CallbackFunction has the following signature:
function Callback({{cBlockEntity|BlockEntity}})
Returns false if there's no block entity at the specified coords. Returns the value that the callback has returned otherwise.", + }, + { + Params = + { + { + Name = "RelCoords", + Type = "Vector3i", + }, + { + Name = "Callback", + Type = "function", + }, + }, + Returns = + { + Name = "HasCalled", + Type = "boolean", + }, + Notes = "Calls the specified callback with the block entity at the specified relative coords. The CallbackFunction has the following signature:
function Callback({{cBlockEntity|BlockEntity}})
Returns false if there's no block entity at the specified coords. Returns the value that the callback has returned otherwise.", + }, + }, -- DoWithBlockEntityRelAt + + DumpToRawFile = + { + Params = + { + { + Name = "FileName", + Type = "string", + }, + }, + Notes = "Dumps the raw data into a file. For debugging purposes only.", + }, + + Expand = + { + Params = + { + { + Name = "SubMinX", + Type = "number", + }, + { + Name = "AddMaxX", + Type = "number", + }, + { + Name = "SubMinY", + Type = "number", + }, + { + Name = "AddMaxY", + Type = "number", + }, + { + Name = "SubMinZ", + Type = "number", + }, + { + Name = "AddMaxZ", + Type = "number", + }, + }, + Notes = "Expands the specified number of blocks from each border. Modifies the size of this blockarea object. New blocks created with this operation are filled with zeroes.", + }, + + Fill = + { + Params = + { + { + Name = "DataTypes", + Type = "string", + }, + { + Name = "BlockType", + Type = "number", + }, + { + Name = "BlockMeta", + Type = "number", + IsOptional = true, + }, + { + Name = "BlockLight", + Type = "number", + IsOptional = true, + }, + { + Name = "BlockSkyLight", + Type = "number", + IsOptional = true, + }, + }, + Notes = "Fills the entire block area with the same values, specified. Uses the DataTypes param to determine which content types are modified.", + }, + + FillRelCuboid = + { + { + Params = + { + { + Name = "RelCuboid", + Type = "cCuboid", + }, + { + Name = "DataTypes", + Type = "string", + }, + { + Name = "BlockType", + Type = "number", + }, + { + Name = "BlockMeta", + Type = "number", + IsOptional = true, + }, + { + Name = "BlockLight", + Type = "number", + IsOptional = true, + }, + { + Name = "BlockSkyLight", + Type = "number", + IsOptional = true, + }, + }, + Notes = "Fills the specified cuboid (in relative coords) with the same values (like Fill() ).", + }, + { + Params = + { + { + Name = "MinRelX", + Type = "number", + }, + { + Name = "MaxRelX", + Type = "number", + }, + { + Name = "MinRelY", + Type = "number", + }, + { + Name = "MaxRelY", + Type = "number", + }, + { + Name = "MinRelZ", + Type = "number", + }, + { + Name = "MaxRelZ", + Type = "number", + }, + { + Name = "DataTypes", + Type = "string", + }, + { + Name = "BlockType", + Type = "number", + }, + { + Name = "BlockMeta", + Type = "number", + IsOptional = true, + }, + { + Name = "BlockLight", + Type = "number", + IsOptional = true, + }, + { + Name = "BlockSkyLight", + Type = "number", + IsOptional = true, + }, + }, + Notes = "Fills the specified cuboid with the same values (like Fill() ).", + }, + }, + + ForEachBlockEntity = + { + Params = + { + { + Name = "Coords", + Type = "Vector3i", + }, + { + Name = "Callback", + Type = "function", + }, + }, + Returns = + { + Name = "HasProcessedAll", + Type = "boolean", + }, + Notes = "Calls the specified callback with the block entity for each block entity contained in the object. Returns true if all block entities have been processed (including when there are zero block entities), or false if the callback has aborted the enumeration by returning true. The CallbackFunction has the following signature:
function Callback({{cBlockEntity|BlockEntity}})
The callback should return false or no value to continue with the next block entity, or true to abort the enumeration.", + }, -- ForEachBlockEntity + + GetBlockLight = + { + Params = + { + { + Name = "BlockX", + Type = "number", + }, + { + Name = "BlockY", + Type = "number", + }, + { + Name = "BlockZ", + Type = "number", + }, + }, + Returns = + { + { + Name = "BlockLight", + Type = "number", + }, + }, + Notes = "Returns the blocklight (emissive light) at the specified absolute coords", + }, + + GetBlockMeta = + { + Params = + { + { + Name = "BlockX", + Type = "number", + }, + { + Name = "BlockY", + Type = "number", + }, + { + Name = "BlockZ", + Type = "number", + }, + }, + Returns = + { + { + Name = "BlockMeta", + Type = "number", + }, + }, + Notes = "Returns the block meta at the specified absolute coords", + }, + + GetBlockSkyLight = + { + Params = + { + { + Name = "BlockX", + Type = "number", + }, + { + Name = "BlockY", + Type = "number", + }, + { + Name = "BlockZ", + Type = "number", + }, + }, + Returns = + { + { + Name = "BlockSkyLight", + Type = "number", + }, + }, + Notes = "Returns the skylight at the specified absolute coords", + }, + + GetBlockType = + { + Params = + { + { + Name = "BlockX", + Type = "number", + }, + { + Name = "BlockY", + Type = "number", + }, + { + Name = "BlockZ", + Type = "number", + }, + }, + Returns = + { + { + Name = "BLOCKTYPE", + Type = "number", + }, + }, + Notes = "Returns the block type at the specified absolute coords", + }, + + GetBlockTypeMeta = + { + Params = + { + { + Name = "BlockX", + Type = "number", + }, + { + Name = "BlockY", + Type = "number", + }, + { + Name = "BlockZ", + Type = "number", + }, + }, + Returns = + { + { + Name = "BLOCKTYPE", + Type = "number", + }, + { + Name = "NIBBLETYPE", + Type = "number", + }, + }, + Notes = "Returns the block type and meta at the specified absolute coords", + }, + + GetCoordRange = + { + Returns = + { + { + Name = "MaxX", + Type = "number", + }, + { + Name = "MaxY", + Type = "number", + }, + { + Name = "MaxZ", + Type = "number", + }, + }, + Notes = "Returns the maximum relative coords in all 3 axes. See also GetSize().", + }, + + GetDataTypes = + { + Returns = + { + { + Type = "number", + }, + }, + Notes = "Returns the mask of datatypes (ba* constants added together) that the object is currently holding.", + }, + + GetNonAirCropRelCoords = + { + Params = + { + { + Name = "IgnoredBlockType", + Type = "number", + IsOptional = true, + }, + }, + Returns = + { + { + Name = "MinRelX", + Type = "number", + }, + { + Name = "MinRelY", + Type = "number", + }, + { + Name = "MinRelZ", + Type = "number", + }, + { + Name = "MaxRelX", + Type = "number", + }, + { + Name = "MaxRelY", + Type = "number", + }, + { + Name = "MaxRelZ", + Type = "number", + }, + }, + Notes = "Returns the minimum and maximum coords in each direction for the first block in each direction of type different to IgnoredBlockType (E_BLOCK_AIR by default). If there are no non-ignored blocks within the area, or blocktypes are not present, the returned values are reverse-ranges (MinX <- m_RangeX, MaxX <- 0 etc.). IgnoreBlockType defaults to air.", + }, + + GetOrigin = + { + Returns = + { + { + Name = "OriginX", + Type = "number", + }, + { + Name = "OriginY", + Type = "number", + }, + { + Name = "OriginZ", + Type = "number", + }, + }, + Notes = "Returns the origin coords of where the area was read from.", + }, + + GetOriginX = + { + Returns = + { + { + Type = "number", + }, + }, + Notes = "Returns the origin x-coord", + }, + + GetOriginY = + { + Returns = + { + { + Type = "number", + }, + }, + Notes = "Returns the origin y-coord", + }, + + GetOriginZ = + { + Returns = + { + { + Type = "number", + }, + }, + Notes = "Returns the origin z-coord", + }, + + GetRelBlockLight = + { + Params = + { + { + Name = "RelBlockX", + Type = "number", + }, + { + Name = "RelBlockY", + Type = "number", + }, + { + Name = "RelBlockZ", + Type = "number", + }, + }, + Returns = + { + { + Name = "NIBBLETYPE", + Type = "number", + }, + }, + Notes = "Returns the blocklight at the specified relative coords", + }, + + GetRelBlockMeta = + { + Params = + { + { + Name = "RelBlockX", + Type = "number", + }, + { + Name = "RelBlockY", + Type = "number", + }, + { + Name = "RelBlockZ", + Type = "number", + }, + }, + Returns = + { + { + Name = "NIBBLETYPE", + Type = "number", + }, + }, + Notes = "Returns the block meta at the specified relative coords", + }, + + GetRelBlockSkyLight = + { + Params = + { + { + Name = "RelBlockX", + Type = "number", + }, + { + Name = "RelBlockY", + Type = "number", + }, + { + Name = "RelBlockZ", + Type = "number", + }, + }, + Returns = + { + { + Name = "NIBBLETYPE", + Type = "number", + }, + }, + Notes = "Returns the skylight at the specified relative coords", + }, + + + GetRelBlockType = + { + Params = + { + { + Name = "RelBlockX", + Type = "number", + }, + { + Name = "RelBlockY", + Type = "number", + }, + { + Name = "RelBlockZ", + Type = "number", + }, + }, + Returns = + { + { + Name = "BLOCKTYPE", + Type = "number", + }, + }, + Notes = "Returns the block type at the specified relative coords", + }, + + GetRelBlockTypeMeta = + { + Params = + { + { + Name = "RelBlockX", + Type = "number", + }, + { + Name = "RelBlockY", + Type = "number", + }, + { + Name = "RelBlockZ", + Type = "number", + }, + }, + Returns = + { + { + Name = "BLOCKTYPE", + Type = "number", + }, + { + Name = "NIBBLETYPE", + Type = "number", + }, + }, + Notes = "Returns the block type and meta at the specified relative coords", + }, + + GetSize = + { + Returns = + { + { + Name = "SizeX", + Type = "number", + }, + { + Name = "SizeY", + Type = "number", + }, + { + Name = "SizeZ", + Type = "number", + }, + }, + Notes = "Returns the size of the area in all 3 axes. See also GetCoordRange().", + }, + GetSizeX = + { + Returns = + { + { + Type = "number", + }, + }, + Notes = "Returns the size of the held data in the x-axis", + }, + GetSizeY = + { + Returns = + { + { + Type = "number", + }, + }, + Notes = "Returns the size of the held data in the y-axis", + }, + GetSizeZ = + { + Returns = + { + { + Type = "number", + }, + }, + Notes = "Returns the size of the held data in the z-axis", + }, + GetVolume = + { + Returns = + { + { + Type = "number", + }, + }, + Notes = "Returns the volume of the area - the total number of blocks stored within.", + }, + GetWEOffset = + { + Returns = + { + { + Type = "Vector3i", + }, + }, + Notes = "Returns the WE offset, a data value sometimes stored in the schematic files. Cuberite doesn't use this value, but provides access to it using this method. The default is {0, 0, 0}.", + }, + HasBlockEntities = + { + Returns = + { + { + Type = "boolean", + }, + }, + Notes = "Returns true if current datatypes include block entities.", + }, + HasBlockLights = + { + Returns = + { + { + Type = "boolean", + }, + }, + Notes = "Returns true if current datatypes include blocklight", + }, + HasBlockMetas = + { + Returns = + { + { + Type = "boolean", + }, + }, + Notes = "Returns true if current datatypes include block metas", + }, + HasBlockSkyLights = + { + Returns = + { + { + Type = "boolean", + }, + }, + Notes = "Returns true if current datatypes include skylight", + }, + HasBlockTypes = + { + Returns = + { + { + Type = "boolean", + }, + }, + Notes = "Returns true if current datatypes include block types", + }, + + IsValidCoords = + { + { + Params = + { + { + Name = "BlockX", + Type = "number", + }, + { + Name = "BlockY", + Type = "number", + }, + { + Name = "BlockZ", + Type = "number", + }, + }, + Returns = + { + Type = "boolean", + }, + Notes = "Returns true if the specified absolute coords are within the area.", + }, + { + Params = + { + { + Name = "Coords", + Type = "Vector3i", + }, + }, + Returns = + { + Type = "boolean", + }, + Notes = "Returns true if the specified absolute coords are within the area.", + }, + }, -- IsValidCoords + + IsValidDataTypeCombination = + { + Params = + { + { + Name = "DataTypes", + Type = "number", + }, + }, + Returns = + { + Type = "boolean", + }, + Notes = "Returns true if the specified combination of datatypes (ba* constants added together) is valid. Most combinations are valid, but for example baBlockEntities without baTypes is an invalid combination.", + }, -- IsValidDataTypeCombination + + IsValidRelCoords = + { + { + Params = + { + { + Name = "RelBlockX", + Type = "number", + }, + { + Name = "RelBlockY", + Type = "number", + }, + { + Name = "RelBlockZ", + Type = "number", + }, + }, + Returns = + { + Type = "boolean", + }, + Notes = "Returns true if the specified relative coords are within the area.", + }, + { + Params = + { + { + Name = "RelCoords", + Type = "Vector3i", + }, + }, + Returns = + { + Type = "boolean", + }, + Notes = "Returns true if the specified relative coords are within the area.", + }, + }, -- IsValidRelCoords + + LoadFromSchematicFile = + { + Params = + { + { + Name = "FileName", + Type = "string", + }, + }, + Returns = + { + { Type = "boolean" }, + }, + Notes = "Clears current content and loads new content from the specified schematic file. Returns true if successful. Returns false and logs error if unsuccessful, old content is preserved in such a case.", + }, + LoadFromSchematicString = + { + Params = + { + { + Name = "SchematicData", + Type = "string", + }, + }, + Returns = + { + { Type = "boolean" }, + }, + Notes = "Clears current content and loads new content from the specified string (assumed to contain .schematic data). Returns true if successful. Returns false and logs error if unsuccessful, old content is preserved in such a case.", + }, + Merge = + { + { + Params = + { + { + Name = "BlockAreaSrc", + Type = "cBlockArea", + }, + { + Name = "RelMinCoords", + Type = "number", + }, + { + Name = "Strategy", + Type = "string", + }, + }, + Notes = "Merges BlockAreaSrc into this object at the specified relative coords, using the specified strategy", + }, + { + Params = + { + { + Name = "BlockAreaSrc", + Type = "cBlockArea", + }, + { + Name = "RelX", + Type = "number", + }, + { + Name = "RelY", + Type = "number", + }, + { + Name = "RelZ", + Type = "number", + }, + { + Name = "Strategy", + Type = "string", + }, + }, + Notes = "Merges BlockAreaSrc into this object at the specified relative coords, using the specified strategy", + }, + }, + MirrorXY = + { + Notes = "Mirrors this block area around the XY plane. Modifies blocks' metas (if present) to match (i. e. furnaces facing the opposite direction).", + }, + MirrorXYNoMeta = + { + Notes = "Mirrors this block area around the XY plane. Doesn't modify blocks' metas.", + }, + MirrorXZ = + { + Notes = "Mirrors this block area around the XZ plane. Modifies blocks' metas (if present)", + }, + MirrorXZNoMeta = + { + Notes = "Mirrors this block area around the XZ plane. Doesn't modify blocks' metas.", + }, + MirrorYZ = + { + Notes = "Mirrors this block area around the YZ plane. Modifies blocks' metas (if present)", + }, + MirrorYZNoMeta = + { + Notes = "Mirrors this block area around the YZ plane. Doesn't modify blocks' metas.", + }, + Read = + { + { + Params = + { + { + Name = "World", + Type = "cWorld", + }, + { + Name = "Cuboid", + Type = "cCuboid", + }, + }, + Returns = + { + { + Name = "IsSuccess", + Type = "boolean", + }, + }, + Notes = "Reads the area from World, returns true if successful. baTypes and baMetas are read.", + }, + { + Params = + { + { + Name = "World", + Type = "cWorld", + }, + { + Name = "Cuboid", + Type = "cCuboid", + }, + { + Name = "DataTypes", + Type = "number", + }, + }, + Returns = + { + { + Name = "IsSuccess", + Type = "boolean", + }, + }, + Notes = "Reads the area from World, returns true if successful. DataTypes is the sum of baXXX datatypes to be read", + }, + { + Params = + { + { + Name = "World", + Type = "cWorld", + }, + { + Name = "Point1", + Type = "Vector3i", + }, + { + Name = "Point2", + Type = "Vector3i", + }, + }, + Returns = + { + { + Name = "IsSuccess", + Type = "boolean", + }, + }, + Notes = "Reads the area from World, returns true if successful. baTypes and baMetas are read.", + }, + { + Params = + { + { + Name = "World", + Type = "cWorld", + }, + { + Name = "Point1", + Type = "Vector3i", + }, + { + Name = "Point2", + Type = "Vector3i", + }, + { + Name = "DataTypes", + Type = "number", + }, + }, + Returns = + { + { + Name = "IsSuccess", + Type = "boolean", + }, + }, + Notes = "Reads the area from World, returns true if successful. DataTypes is a sum of baXXX datatypes to be read.", + }, + { + Params = + { + { + Name = "World", + Type = "cWorld", + }, + { + Name = "MinX", + Type = "number", + }, + { + Name = "MaxX", + Type = "number", + }, + { + Name = "MinY", + Type = "number", + }, + { + Name = "MaxY", + Type = "number", + }, + { + Name = "MinZ", + Type = "number", + }, + { + Name = "MaxZ", + Type = "number", + }, + }, + Returns = + { + { + Type = "boolean", + }, + }, + Notes = "Reads the area from World, returns true if successful. baTypes and baMetas are read.", + }, + { + Params = + { + { + Name = "World", + Type = "cWorld", + }, + { + Name = "MinX", + Type = "number", + }, + { + Name = "MaxX", + Type = "number", + }, + { + Name = "MinY", + Type = "number", + }, + { + Name = "MaxY", + Type = "number", + }, + { + Name = "MinZ", + Type = "number", + }, + { + Name = "MaxZ", + Type = "number", + }, + { + Name = "DataTypes", + Type = "number", + }, + }, + Returns = + { + { + Type = "boolean", + }, + }, + Notes = "Reads the area from World, returns true if successful. DataTypes is a sum of baXXX datatypes to read.", + }, + }, + RelLine = + { + { + Params = + { + { + Name = "RelPoint1", + Type = "Vector3i", + }, + { + Name = "RelPoint2", + Type = "Vector3i", + }, + { + Name = "DataTypes", + Type = "number", + }, + { + Name = "BlockType", + Type = "number", + }, + { + Name = "BlockMeta", + Type = "number", + IsOptional = true, + }, + { + Name = "BlockLight", + Type = "number", + IsOptional = true, + }, + { + Name = "BlockSkyLight", + Type = "number", + IsOptional = true, + }, + }, + Notes = "Draws a line between the two specified points. Sets only datatypes specified by DataTypes (baXXX constants).", + }, + { + Params = + { + { + Name = "RelX1", + Type = "number", + }, + { + Name = "RelY1", + Type = "number", + }, + { + Name = "RelZ1", + Type = "number", + }, + { + Name = "RelX2", + Type = "number", + }, + { + Name = "RelY2", + Type = "number", + }, + { + Name = "RelZ2", + Type = "number", + }, + { + Name = "DataTypes", + Type = "string", + }, + { + Name = "BlockType", + Type = "number", + }, + { + Name = "BlockMeta", + Type = "number", + IsOptional = true, + }, + { + Name = "BlockLight", + Type = "number", + IsOptional = true, + }, + { + Name = "BlockSkyLight", + Type = "number", + IsOptional = true, + }, + }, + Notes = "Draws a line between the two specified points. Sets only datatypes specified by DataTypes (baXXX constants).", + }, + }, + RotateCCW = + { + Notes = "Rotates the block area around the Y axis, counter-clockwise (east -> north). Modifies blocks' metas (if present) to match.", + }, + RotateCCWNoMeta = + { + Notes = "Rotates the block area around the Y axis, counter-clockwise (east -> north). Doesn't modify blocks' metas.", + }, + RotateCW = + { + Notes = "Rotates the block area around the Y axis, clockwise (north -> east). Modifies blocks' metas (if present) to match.", + }, + RotateCWNoMeta = + { + Notes = "Rotates the block area around the Y axis, clockwise (north -> east). Doesn't modify blocks' metas.", + }, + SaveToSchematicFile = + { + Params = + { + { + Name = "FileName", + Type = "string", + }, + }, + Returns = + { + { + Type = "boolean", + }, + }, + Notes = "Saves the current contents to a schematic file. Returns true if successful.", + }, + SaveToSchematicString = + { + Returns = + { + { + Type = "string", + }, + }, + Notes = "Saves the current contents to a string (in a .schematic file format). Returns the data if successful, nil if failed.", + }, + SetBlockLight = + { + Params = + { + { + Name = "BlockX", + Type = "number", + }, + { + Name = "BlockY", + Type = "number", + }, + { + Name = "BlockZ", + Type = "number", + }, + { + Name = "BlockLight", + Type = "number", + }, + }, + Notes = "Sets the blocklight at the specified absolute coords", + }, + SetBlockMeta = + { + Params = + { + { + Name = "BlockX", + Type = "number", + }, + { + Name = "BlockY", + Type = "number", + }, + { + Name = "BlockZ", + Type = "number", + }, + { + Name = "BlockMeta", + Type = "number", + }, + }, + Notes = "Sets the block meta at the specified absolute coords", + }, + SetBlockSkyLight = + { + Params = + { + { + Name = "BlockX", + Type = "number", + }, + { + Name = "BlockY", + Type = "number", + }, + { + Name = "BlockZ", + Type = "number", + }, + { + Name = "BlockSkyLight", + Type = "number", + }, + }, + Notes = "Sets the skylight at the specified absolute coords", + }, + SetBlockType = + { + Params = + { + { + Name = "BlockX", + Type = "number", + }, + { + Name = "BlockY", + Type = "number", + }, + { + Name = "BlockZ", + Type = "number", + }, + { + Name = "BlockType", + Type = "number", + }, + }, + Notes = "Sets the block type at the specified absolute coords", + }, + SetBlockTypeMeta = + { + Params = + { + { + Name = "BlockX", + Type = "number", + }, + { + Name = "BlockY", + Type = "number", + }, + { + Name = "BlockZ", + Type = "number", + }, + { + Name = "BlockType", + Type = "number", + }, + { + Name = "BlockMeta", + Type = "number", + }, + }, + Notes = "Sets the block type and meta at the specified absolute coords", + }, + SetOrigin = + { + { + Params = + { + { + Name = "Origin", + Type = "Vector3i", + }, + }, + Notes = "Resets the origin for the absolute coords. Only affects how absolute coords are translated into relative coords.", + }, + { + Params = + { + { + Name = "OriginX", + Type = "number", + }, + { + Name = "OriginY", + Type = "number", + }, + { + Name = "OriginZ", + Type = "number", + }, + }, + Notes = "Resets the origin for the absolute coords. Only affects how absolute coords are translated into relative coords.", + }, + }, + SetRelBlockLight = + { + Params = + { + { + Name = "RelBlockX", + Type = "number", + }, + { + Name = "RelBlockY", + Type = "number", + }, + { + Name = "RelBlockZ", + Type = "number", + }, + { + Name = "BlockLight", + Type = "number", + }, + }, + Notes = "Sets the blocklight at the specified relative coords", + }, + SetRelBlockMeta = + { + Params = + { + { + Name = "RelBlockX", + Type = "number", + }, + { + Name = "RelBlockY", + Type = "number", + }, + { + Name = "RelBlockZ", + Type = "number", + }, + { + Name = "BlockMeta", + Type = "number", + }, + }, + Notes = "Sets the block meta at the specified relative coords", + }, + SetRelBlockSkyLight = + { + Params = + { + { + Name = "RelBlockX", + Type = "number", + }, + { + Name = "RelBlockY", + Type = "number", + }, + { + Name = "RelBlockZ", + Type = "number", + }, + { + Name = "BlockSkyLight", + Type = "number", + }, + }, + Notes = "Sets the skylight at the specified relative coords", + }, + SetRelBlockType = + { + Params = + { + { + Name = "RelBlockX", + Type = "number", + }, + { + Name = "RelBlockY", + Type = "number", + }, + { + Name = "RelBlockZ", + Type = "number", + }, + { + Name = "BlockType", + Type = "number", + }, + }, + Notes = "Sets the block type at the specified relative coords", + }, + SetRelBlockTypeMeta = + { + Params = + { + { + Name = "RelBlockX", + Type = "number", + }, + { + Name = "RelBlockY", + Type = "number", + }, + { + Name = "RelBlockZ", + Type = "number", + }, + { + Name = "BlockType", + Type = "number", + }, + { + Name = "BlockMeta", + Type = "number", + }, + }, + Notes = "Sets the block type and meta at the specified relative coords", + }, + SetWEOffset = + { + { + Params = + { + { + Name = "Offset", + Type = "Vector3i", + }, + }, + Notes = "Sets the WE offset, a data value sometimes stored in the schematic files. Mostly used for WorldEdit. Cuberite doesn't use this value, but provides access to it using this method.", + }, + { + Params = + { + { + Name = "OffsetX", + Type = "number", + }, + { + Name = "OffsetY", + Type = "number", + }, + { + Name = "OffsetZ", + Type = "number", + }, + }, + Notes = "Sets the WE offset, a data value sometimes stored in the schematic files. Mostly used for WorldEdit. Cuberite doesn't use this value, but provides access to it using this method.", + }, + }, + Write = + { + { + Params = + { + { + Name = "World", + Type = "cWorld", + }, + { + Name = "MinPoint", + Type = "Vector3i", + }, + }, + Returns = + { + { + Name = "IsSuccess", + Type = "boolean", + }, + }, + Notes = "Writes the area into World at the specified coords, returns true if successful. baTypes and baMetas are written.", + }, + { + Params = + { + { + Name = "World", + Type = "cWorld", + }, + { + Name = "MinPoint", + Type = "Vector3i", + }, + { + Name = "DataTypes", + Type = "number", + }, + }, + Returns = + { + { + Name = "IsSuccess", + Type = "boolean", + }, + }, + Notes = "Writes the area into World at the specified coords, returns true if successful. DataTypes is the sum of baXXX datatypes to write.", + }, + { + Params = + { + { + Name = "World", + Type = "cWorld", + }, + { + Name = "MinX", + Type = "number", + }, + { + Name = "MinY", + Type = "number", + }, + { + Name = "MinZ", + Type = "number", + }, + }, + Returns = + { + { + Name = "IsSuccess", + Type = "boolean", + }, + }, + Notes = "Writes the area into World at the specified coords, returns true if successful. baTypes and baMetas are written.", + }, + { + Params = + { + { + Name = "World", + Type = "cWorld", + }, + { + Name = "MinX", + Type = "number", + }, + { + Name = "MinY", + Type = "number", + }, + { + Name = "MinZ", + Type = "number", + }, + { + Name = "DataTypes", + Type = "number", + }, + }, + Returns = + { + { + Name = "IsSuccess", + Type = "boolean", + }, + }, + Notes = "Writes the area into World at the specified coords, returns true if successful. DataTypes is the sum of baXXX datatypes to write.", + }, + }, + }, + Constants = + { + baBlockEntities = + { + Notes = "Operations should work on block entities. Note that this flag is invalid without baTypes.", + }, + baLight = + { + Notes = "Operations should work on block (emissive) light", + }, + baMetas = + { + Notes = "Operations should work on block metas", + }, + baSkyLight = + { + Notes = "Operations should work on skylight", + }, + baTypes = + { + Notes = "Operation should work on block types", + }, + msDifference = + { + Notes = "Block becomes air if 'self' and src are the same. Otherwise it becomes the src block.", + }, + msFillAir = + { + Notes = "'self' is overwritten by Src only where 'self' has air blocks", + }, + msImprint = + { + Notes = "Src overwrites 'self' anywhere where 'self' has non-air blocks", + }, + msLake = + { + Notes = "Special mode for merging lake images", + }, + msMask = + { + Notes = "The blocks that are exactly the same are kept in 'self', all differing blocks are replaced by air", + }, + msOverwrite = + { + Notes = "Src overwrites anything in 'self'", + }, + msSimpleCompare = + { + Notes = "The blocks that are exactly the same are replaced with air, all differing blocks are replaced by stone", + }, + msSpongePrint = + { + Notes = "Similar to msImprint, sponge block doesn't overwrite anything, all other blocks overwrite everything", + }, + }, + ConstantGroups = + { + BATypes = + { + Include = "ba.*", + TextBefore = [[ + The following constants are used to signalize the datatype to read or write: + ]], + }, + eMergeStrategy = + { + Include = "ms.*", + TextAfter = "See below for a detailed explanation of the individual merge strategies.", + TextBefore = [[ + The Merge() function can use different strategies to combine the source and destination blocks. + The following constants are used: + ]], + }, + }, + AdditionalInfo = + { + { + Header = "Merge strategies", + Contents = [[ +

The strategy parameter specifies how individual blocks are combined together, using the table below. +

+ + + + + + + + + + + + + + + + + + + + + + +
area blockresult
this Src msOverwrite msFillAir msImprint
air air air air air
A air air A A
air B B B B
A B B A B
A A A A A
+ +

+ So to sum up: +

    +
  1. msOverwrite completely overwrites all blocks with the Src's blocks
  2. +
  3. msFillAir overwrites only those blocks that were air
  4. +
  5. msImprint overwrites with only those blocks that are non-air
  6. +
+

+ +

Special strategies

+

For each strategy, evaluate the table rows from top downwards, the first match wins.

+ +

+ msDifference - changes all the blocks which are the same to air. Otherwise the source block gets placed. +

+ + + + + + + +
area block Notes
* B B The blocks are different so we use block B
B B Air The blocks are the same so we get air.
+ + +

+ msLake - used for merging areas with lava and water lakes, in the appropriate generator. +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
area block Notes
self Src result
A sponge A Sponge is the NOP block
* air air Air always gets hollowed out, even under the oceans
water * water Water is never overwritten
lava * lava Lava is never overwritten
* water water Water always overwrites anything
* lava lava Lava always overwrites anything
dirt stone stone Stone overwrites dirt
grass stone stone ... and grass
mycelium stone stone ... and mycelium
A stone A ... but nothing else
A * A Everything else is left as it is
+ +

+ msSpongePrint - used for most prefab-generators to merge the prefabs. Similar to + msImprint, but uses the sponge block as the NOP block instead, so that the prefabs may carve out air + pockets, too. +

+ + + + + + + + + +
area block Notes
self Src result
A sponge A Sponge is the NOP block
* B B Everything else overwrites anything
+ +

+ msMask - the blocks that are the same in the other area are kept, all the + differing blocks are replaced with air. Meta is used in the comparison, too, two blocks of the + same type but different meta are considered different and thus replaced with air. +

+ + + + + + + + + +
area block Notes
self Src result
A A A Same blocks are kept
A non-A air Differing blocks are replaced with air
+ +

+ msDifference - the blocks that are the same in both areas are replaced with air, all the + differing blocks are kept from the first area. Meta is used in the comparison, too, two blocks of the + same type but different meta are considered different. +

+ + + + + + + + + +
area block Notes
self Src result
A A air Same blocks are replaced with air
A non-A A Differing blocks are kept from 'self'
+ +

+ msSimpleCompare - the blocks that are the same in both areas are replaced with air, all the + differing blocks are replaced with stone. Meta is used in the comparison, too, two blocks of the + same type but different meta are considered different. +

+ + + + + + + + + +
area block Notes
self Src result
A A air Same blocks are replaced with air
A non-A stone Differing blocks are replaced with stone
+]], + }, + }, + }, +} diff --git a/Server/Plugins/APIDump/Classes/World.lua b/Server/Plugins/APIDump/Classes/World.lua index 3ba2d82ca..1614886fd 100644 --- a/Server/Plugins/APIDump/Classes/World.lua +++ b/Server/Plugins/APIDump/Classes/World.lua @@ -532,7 +532,7 @@ function OnAllChunksAvailable() All return values from the callbacks are i Type = "boolean", }, }, - Notes = "If there is a block entity at the specified coords, calls the CallbackFunction with the {{cBlockEntity}} parameter representing the block entity. The CallbackFunction has the following signature:
function Callback({{cBlockEntity|BlockEntity}})
The function returns false if there is no block entity, or if there is, it returns the bool value that the callback has returned. Use {{tolua}}.cast() to cast the Callback's BlockEntity parameter to the correct {{cBlockEntity}} descendant.", + Notes = "If there is a block entity at the specified coords, calls the CallbackFunction with the {{cBlockEntity}} parameter representing the block entity. The CallbackFunction has the following signature:
function Callback({{cBlockEntity|BlockEntity}})
The function returns false if there is no block entity, or if there is, it returns the bool value that the callback has returned.", }, DoWithBrewingstandAt = { @@ -978,7 +978,7 @@ function OnAllChunksAvailable() All return values from the callbacks are i Type = "boolean", }, }, - Notes = "Calls the specified callback for each block entity in the chunk. Returns true if all block entities in the chunk have been processed (including when there are zero block entities), or false if the callback has aborted the enumeration by returning true. The CallbackFunction has the following signature:
function Callback({{cBlockEntity|BlockEntity}})
The callback should return false or no value to continue with the next block entity, or true to abort the enumeration. Use {{tolua}}.cast() to cast the Callback's BlockEntity parameter to the correct {{cBlockEntity}} descendant.", + Notes = "Calls the specified callback for each block entity in the chunk. Returns true if all block entities in the chunk have been processed (including when there are zero block entities), or false if the callback has aborted the enumeration by returning true. The CallbackFunction has the following signature:
function Callback({{cBlockEntity|BlockEntity}})
The callback should return false or no value to continue with the next block entity, or true to abort the enumeration.", }, ForEachBrewingstandInChunk = { @@ -3364,10 +3364,9 @@ World:ForEachEntity( return; end - -- Get the cMonster out of cEntity, now that we know the entity represents one. - local Monster = tolua.cast(a_Entity, "cMonster"); - if (Monster:GetMobType() == mtSpider) then - Monster:TeleportToCoords(Monster:GetPosX(), Monster:GetPosY() + 100, Monster:GetPosZ()); + -- Now that we know the entity represents a mob, we can use cMonster functions: + if (a_Entity:GetMobType() == mtSpider) then + a_Entity:TeleportToCoords(a_Entity:GetPosX(), a_Entity:GetPosY() + 100, a_Entity:GetPosZ()); end end ); diff --git a/src/Bindings/CMakeLists.txt b/src/Bindings/CMakeLists.txt index 2614ef9b2..747f22157 100644 --- a/src/Bindings/CMakeLists.txt +++ b/src/Bindings/CMakeLists.txt @@ -16,6 +16,7 @@ SET (SRCS LuaUDPEndpoint.cpp LuaWindow.cpp ManualBindings.cpp + ManualBindings_BlockArea.cpp ManualBindings_Network.cpp ManualBindings_RankManager.cpp ManualBindings_World.cpp diff --git a/src/Bindings/LuaState.cpp b/src/Bindings/LuaState.cpp index 947e337fc..d18b6efcd 100644 --- a/src/Bindings/LuaState.cpp +++ b/src/Bindings/LuaState.cpp @@ -1762,6 +1762,30 @@ bool cLuaState::CheckParamEnd(int a_Param) +bool cLuaState::CheckParamSelf(const char * a_SelfClassName) +{ + tolua_Error tolua_err; + if (tolua_isusertype(m_LuaState, 1, a_SelfClassName, 0, &tolua_err) && !lua_isnil(m_LuaState, 1)) + { + return true; + } + + // Not the correct parameter + lua_Debug entry; + VERIFY(lua_getstack(m_LuaState, 0, &entry)); + VERIFY(lua_getinfo (m_LuaState, "n", &entry)); + AString ErrMsg = Printf( + "Error in function '%s'. The 'self' parameter is not of the expected type, \"instance of %s\". Make sure you're using the correct calling convention (obj:fn() instead of obj.fn()).", + (entry.name != nullptr) ? entry.name : "", a_SelfClassName + ); + tolua_error(m_LuaState, ErrMsg.c_str(), &tolua_err); + return false; +} + + + + + bool cLuaState::IsParamUserType(int a_Param, AString a_UserType) { ASSERT(IsValid()); diff --git a/src/Bindings/LuaState.h b/src/Bindings/LuaState.h index 4320ce40e..674c7a240 100644 --- a/src/Bindings/LuaState.h +++ b/src/Bindings/LuaState.h @@ -788,6 +788,10 @@ public: /** Returns true if the specified parameter on the stack is nil (indicating an end-of-parameters) */ bool CheckParamEnd(int a_Param); + /** Returns true if the first parameter is an instance of the expected class name. + Returns false and logs a special warning ("wrong calling convention") if not. */ + bool CheckParamSelf(const char * a_SelfClassName); + bool IsParamUserType(int a_Param, AString a_UserType); bool IsParamNumber(int a_Param); diff --git a/src/Bindings/ManualBindings.cpp b/src/Bindings/ManualBindings.cpp index d8bd8b023..2c2de6296 100644 --- a/src/Bindings/ManualBindings.cpp +++ b/src/Bindings/ManualBindings.cpp @@ -31,7 +31,6 @@ #include "../BlockEntities/FlowerPotEntity.h" #include "../Generating/ChunkDesc.h" #include "../LineBlockTracer.h" -#include "../WorldStorage/SchematicFileSerializer.h" #include "../CompositeChat.h" #include "../StringCompression.h" #include "../CommandOutput.h" @@ -133,6 +132,46 @@ int cManualBindings::lua_do_error(lua_State * L, const char * a_pFormat, ...) +int cManualBindings::ApiParamError(lua_State * a_LuaState, const char * a_MsgFormat, ...) +{ + // Retrieve current function name + lua_Debug entry; + VERIFY(lua_getstack(a_LuaState, 0, &entry)); + VERIFY(lua_getinfo(a_LuaState, "n", &entry)); + + // Compose the error message: + va_list argp; + va_start(argp, a_MsgFormat); + AString msg; + + #ifdef __clang__ + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wformat-nonliteral" + #endif + + AppendVPrintf(msg, a_MsgFormat, argp); + + #ifdef __clang__ + #pragma clang diagnostic pop + #endif + + va_end(argp); + AString errorMsg = Printf("%s: %s", (entry.name != nullptr) ? entry.name : "", msg.c_str()); + + // Log everything into the console: + LOGWARNING("%s", errorMsg.c_str()); + // cLuaState::LogStackTrace(a_LuaState); // Do NOT log stack trace, it is already output as part of the Lua error handling + cLuaState::LogStackValues(a_LuaState, "Parameters on the stack"); + + // Raise Lua error: + lua_pushstring(a_LuaState, errorMsg.c_str()); + return lua_error(a_LuaState); +} + + + + + // Lua bound functions with special return types static int tolua_Clamp(lua_State * tolua_S) { @@ -3199,324 +3238,6 @@ static int tolua_cHopperEntity_GetOutputBlockPos(lua_State * tolua_S) -static int tolua_cBlockArea_GetBlockTypeMeta(lua_State * tolua_S) -{ - // function cBlockArea::GetBlockTypeMeta() - // Exported manually because tolua generates extra input params for the outputs - - cLuaState L(tolua_S); - if ( - !L.CheckParamUserType(1, "cBlockArea") || - !L.CheckParamNumber (2, 4) - ) - { - return 0; - } - - cBlockArea * self = reinterpret_cast(tolua_tousertype(tolua_S, 1, nullptr)); - if (self == nullptr) - { - tolua_error(tolua_S, "invalid 'self' in function 'cBlockArea:GetRelBlockTypeMeta'", nullptr); - return 0; - } - int BlockX = static_cast(tolua_tonumber(tolua_S, 2, 0)); - int BlockY = static_cast(tolua_tonumber(tolua_S, 3, 0)); - int BlockZ = static_cast(tolua_tonumber(tolua_S, 4, 0)); - BLOCKTYPE BlockType; - NIBBLETYPE BlockMeta; - self->GetBlockTypeMeta(BlockX, BlockY, BlockZ, BlockType, BlockMeta); - tolua_pushnumber(tolua_S, BlockType); - tolua_pushnumber(tolua_S, BlockMeta); - return 2; -} - - - - - -static int tolua_cBlockArea_GetOrigin(lua_State * tolua_S) -{ - // function cBlockArea::GetOrigin() - // Returns all three coords of the origin point - // Exported manually because there's no direct C++ equivalent, - // plus tolua would generate extra input params for the outputs - - cLuaState L(tolua_S); - if (!L.CheckParamUserType(1, "cBlockArea")) - { - return 0; - } - - cBlockArea * self = reinterpret_cast(tolua_tousertype(tolua_S, 1, nullptr)); - if (self == nullptr) - { - tolua_error(tolua_S, "invalid 'self' in function 'cBlockArea:GetOrigin'", nullptr); - return 0; - } - - // Push the three origin coords: - lua_pushnumber(tolua_S, self->GetOriginX()); - lua_pushnumber(tolua_S, self->GetOriginY()); - lua_pushnumber(tolua_S, self->GetOriginZ()); - return 3; -} - - - - - -static int tolua_cBlockArea_GetNonAirCropRelCoords(lua_State * tolua_S) -{ - // function cBlockArea::GetNonAirCropRelCoords() - // Exported manually because tolua would generate extra input params for the outputs - - cLuaState L(tolua_S); - if (!L.CheckParamUserType(1, "cBlockArea")) - { - return 0; - } - - cBlockArea * self = nullptr; - BLOCKTYPE IgnoreBlockType = E_BLOCK_AIR; - L.GetStackValues(1, self, IgnoreBlockType); - if (self == nullptr) - { - tolua_error(tolua_S, "invalid 'self' in function 'cBlockArea:GetNonAirCropRelCoords'", nullptr); - return 0; - } - - // Calculate the crop coords: - int MinRelX, MinRelY, MinRelZ, MaxRelX, MaxRelY, MaxRelZ; - self->GetNonAirCropRelCoords(MinRelX, MinRelY, MinRelZ, MaxRelX, MaxRelY, MaxRelZ, IgnoreBlockType); - - // Push the six crop coords: - L.Push(MinRelX, MinRelY, MinRelZ, MaxRelX, MaxRelY, MaxRelZ); - return 6; -} - - - - - -static int tolua_cBlockArea_GetRelBlockTypeMeta(lua_State * tolua_S) -{ - // function cBlockArea::GetRelBlockTypeMeta() - // Exported manually because tolua generates extra input params for the outputs - - cLuaState L(tolua_S); - if ( - !L.CheckParamUserType(1, "cBlockArea") || - !L.CheckParamNumber (2, 4) - ) - { - return 0; - } - - cBlockArea * self = reinterpret_cast(tolua_tousertype(tolua_S, 1, nullptr)); - if (self == nullptr) - { - tolua_error(tolua_S, "invalid 'self' in function 'cBlockArea:GetRelBlockTypeMeta'", nullptr); - return 0; - } - int BlockX = static_cast(tolua_tonumber(tolua_S, 2, 0)); - int BlockY = static_cast(tolua_tonumber(tolua_S, 3, 0)); - int BlockZ = static_cast(tolua_tonumber(tolua_S, 4, 0)); - BLOCKTYPE BlockType; - NIBBLETYPE BlockMeta; - self->GetRelBlockTypeMeta(BlockX, BlockY, BlockZ, BlockType, BlockMeta); - tolua_pushnumber(tolua_S, BlockType); - tolua_pushnumber(tolua_S, BlockMeta); - return 2; -} - - - - - -static int tolua_cBlockArea_GetSize(lua_State * tolua_S) -{ - // function cBlockArea::GetSize() - // Returns all three sizes of the area - // Exported manually because there's no direct C++ equivalent, - // plus tolua would generate extra input params for the outputs - - cLuaState L(tolua_S); - if (!L.CheckParamUserType(1, "cBlockArea")) - { - return 0; - } - - cBlockArea * self = reinterpret_cast(tolua_tousertype(tolua_S, 1, nullptr)); - if (self == nullptr) - { - tolua_error(tolua_S, "invalid 'self' in function 'cBlockArea:GetSize'", nullptr); - return 0; - } - - // Push the three origin coords: - lua_pushnumber(tolua_S, self->GetSizeX()); - lua_pushnumber(tolua_S, self->GetSizeY()); - lua_pushnumber(tolua_S, self->GetSizeZ()); - return 3; -} - - - - - -static int tolua_cBlockArea_GetCoordRange(lua_State * tolua_S) -{ - // function cBlockArea::GetCoordRange() - // Returns all three sizes of the area, miuns one, so that they represent the maximum coord value - // Exported manually because there's no direct C++ equivalent, - // plus tolua would generate extra input params for the outputs - - cLuaState L(tolua_S); - if (!L.CheckParamUserType(1, "cBlockArea")) - { - return 0; - } - - cBlockArea * self = reinterpret_cast(tolua_tousertype(tolua_S, 1, nullptr)); - if (self == nullptr) - { - tolua_error(tolua_S, "invalid 'self' in function 'cBlockArea:GetSize'", nullptr); - return 0; - } - - // Push the three origin coords: - lua_pushnumber(tolua_S, self->GetSizeX() - 1); - lua_pushnumber(tolua_S, self->GetSizeY() - 1); - lua_pushnumber(tolua_S, self->GetSizeZ() - 1); - return 3; -} - - - - - -static int tolua_cBlockArea_LoadFromSchematicFile(lua_State * tolua_S) -{ - // function cBlockArea::LoadFromSchematicFile - // Exported manually because function has been moved to SchematicFileSerializer.cpp - cLuaState L(tolua_S); - if ( - !L.CheckParamUserType(1, "cBlockArea") || - !L.CheckParamString (2) || - !L.CheckParamEnd (3) - ) - { - return 0; - } - cBlockArea * self = reinterpret_cast(tolua_tousertype(tolua_S, 1, nullptr)); - if (self == nullptr) - { - tolua_error(tolua_S, "invalid 'self' in function 'cBlockArea::LoadFromSchematicFile'", nullptr); - return 0; - } - - AString Filename = tolua_tostring(tolua_S, 2, 0); - bool res = cSchematicFileSerializer::LoadFromSchematicFile(*self, Filename); - tolua_pushboolean(tolua_S, res); - return 1; -} - - - - - -static int tolua_cBlockArea_LoadFromSchematicString(lua_State * tolua_S) -{ - // function cBlockArea::LoadFromSchematicString - // Exported manually because function has been moved to SchematicFileSerializer.cpp - cLuaState L(tolua_S); - if ( - !L.CheckParamUserType(1, "cBlockArea") || - !L.CheckParamString (2) || - !L.CheckParamEnd (3) - ) - { - return 0; - } - cBlockArea * self = reinterpret_cast(tolua_tousertype(tolua_S, 1, nullptr)); - if (self == nullptr) - { - tolua_error(tolua_S, "invalid 'self' in function 'cBlockArea::LoadFromSchematicFile'", nullptr); - return 0; - } - - AString Data; - L.GetStackValue(2, Data); - bool res = cSchematicFileSerializer::LoadFromSchematicString(*self, Data); - tolua_pushboolean(tolua_S, res); - return 1; -} - - - - - -static int tolua_cBlockArea_SaveToSchematicFile(lua_State * tolua_S) -{ - // function cBlockArea::SaveToSchematicFile - // Exported manually because function has been moved to SchematicFileSerializer.cpp - cLuaState L(tolua_S); - if ( - !L.CheckParamUserType(1, "cBlockArea") || - !L.CheckParamString (2) || - !L.CheckParamEnd (3) - ) - { - return 0; - } - cBlockArea * self = reinterpret_cast(tolua_tousertype(tolua_S, 1, nullptr)); - if (self == nullptr) - { - tolua_error(tolua_S, "invalid 'self' in function 'cBlockArea::SaveToSchematicFile'", nullptr); - return 0; - } - AString Filename = tolua_tostring(tolua_S, 2, 0); - bool res = cSchematicFileSerializer::SaveToSchematicFile(*self, Filename); - tolua_pushboolean(tolua_S, res); - return 1; -} - - - - - -static int tolua_cBlockArea_SaveToSchematicString(lua_State * tolua_S) -{ - // function cBlockArea::SaveToSchematicString - // Exported manually because function has been moved to SchematicFileSerializer.cpp - cLuaState L(tolua_S); - if ( - !L.CheckParamUserType(1, "cBlockArea") || - !L.CheckParamEnd (2) - ) - { - return 0; - } - cBlockArea * self = reinterpret_cast(tolua_tousertype(tolua_S, 1, nullptr)); - if (self == nullptr) - { - tolua_error(tolua_S, "invalid 'self' in function 'cBlockArea::SaveToSchematicFile'", nullptr); - return 0; - } - - AString Data; - if (cSchematicFileSerializer::SaveToSchematicString(*self, Data)) - { - L.Push(Data); - return 1; - } - return 0; -} - - - - - static int tolua_cBoundingBox_CalcLineIntersection(lua_State * a_LuaState) { /* Function signatures: @@ -4031,19 +3752,6 @@ void cManualBindings::Bind(lua_State * tolua_S) tolua_function(tolua_S, "Base64Decode", tolua_Base64Decode); tolua_function(tolua_S, "md5", tolua_md5_obsolete); // OBSOLETE, use cCryptoHash.md5() instead - tolua_beginmodule(tolua_S, "cBlockArea"); - tolua_function(tolua_S, "GetBlockTypeMeta", tolua_cBlockArea_GetBlockTypeMeta); - tolua_function(tolua_S, "GetCoordRange", tolua_cBlockArea_GetCoordRange); - tolua_function(tolua_S, "GetOrigin", tolua_cBlockArea_GetOrigin); - tolua_function(tolua_S, "GetNonAirCropRelCoords", tolua_cBlockArea_GetNonAirCropRelCoords); - tolua_function(tolua_S, "GetRelBlockTypeMeta", tolua_cBlockArea_GetRelBlockTypeMeta); - tolua_function(tolua_S, "GetSize", tolua_cBlockArea_GetSize); - tolua_function(tolua_S, "LoadFromSchematicFile", tolua_cBlockArea_LoadFromSchematicFile); - tolua_function(tolua_S, "LoadFromSchematicString", tolua_cBlockArea_LoadFromSchematicString); - tolua_function(tolua_S, "SaveToSchematicFile", tolua_cBlockArea_SaveToSchematicFile); - tolua_function(tolua_S, "SaveToSchematicString", tolua_cBlockArea_SaveToSchematicString); - tolua_endmodule(tolua_S); - tolua_beginmodule(tolua_S, "cBoundingBox"); tolua_function(tolua_S, "CalcLineIntersection", tolua_cBoundingBox_CalcLineIntersection); tolua_function(tolua_S, "Intersect", tolua_cBoundingBox_Intersect); @@ -4228,6 +3936,7 @@ void cManualBindings::Bind(lua_State * tolua_S) BindNetwork(tolua_S); BindRankManager(tolua_S); BindWorld(tolua_S); + BindBlockArea(tolua_S); tolua_endmodule(tolua_S); } diff --git a/src/Bindings/ManualBindings.h b/src/Bindings/ManualBindings.h index 98e3e88ef..889e33664 100644 --- a/src/Bindings/ManualBindings.h +++ b/src/Bindings/ManualBindings.h @@ -41,6 +41,10 @@ protected: Implemented in ManualBindings_World.cpp. */ static void BindWorld(lua_State * tolua_S); + /** Binds the manually implemented cBlockArea API functions to tlua_S. + Implemented in ManualBindings_BlockArea.cpp. */ + static void BindBlockArea(lua_State * tolua_S); + public: // Helper functions: @@ -48,6 +52,11 @@ public: static int tolua_do_error(lua_State * L, const char * a_pMsg, tolua_Error * a_pToLuaError); static int lua_do_error(lua_State * L, const char * a_pFormat, ...); + /** Formats and prints the message, prefixed with the current function name, then logs the stack contents and raises a Lua error. + To be used for bindings when they detect bad parameters. + Doesn't return, but a dummy return type is provided so that Lua API functions may do "return ApiParamError(...)". */ + static int ApiParamError(lua_State * a_LuaState, const char * a_MsgFormat, ...); + /** Binds the DoWith(ItemName) functions of regular classes. */ template < @@ -243,10 +252,11 @@ public: + /** Template for the bindings for the DoWithXYZAt(X, Y, Z) functions that don't need to check their coords. */ template < - class Ty1, - class Ty2, - bool (Ty1::*DoWithFn)(int, int, int, cItemCallback &) + class SELF, + class ITEM, + bool (SELF::*DoWithFn)(int, int, int, cItemCallback &) > static int DoWithXYZ(lua_State * tolua_S) { @@ -262,7 +272,7 @@ public: } // Get parameters: - Ty1 * Self = nullptr; + SELF * Self = nullptr; int BlockX = 0; int BlockY = 0; int BlockZ = 0; @@ -277,7 +287,7 @@ public: return lua_do_error(tolua_S, "Error in function call '#funcname#': Expected a valid callback function for parameter #5"); } - class cLuaCallback : public cItemCallback + class cLuaCallback : public cItemCallback { public: cLuaCallback(cLuaState & a_LuaState, cLuaState::cRef & a_FnRef): @@ -287,7 +297,81 @@ public: } private: - virtual bool Item(Ty2 * a_Item) override + virtual bool Item(ITEM * a_Item) override + { + bool ret = false; + m_LuaState.Call(m_FnRef, a_Item, cLuaState::Return, ret); + return ret; + } + cLuaState & m_LuaState; + cLuaState::cRef & m_FnRef; + } Callback(L, FnRef); + + // Call the DoWith function: + bool res = (Self->*DoWithFn)(BlockX, BlockY, BlockZ, Callback); + + // Push the result as the return value: + L.Push(res); + return 1; + } + + + + + + /** Template for the bindings for the DoWithXYZAt(X, Y, Z) functions that need to check their coords. */ + template < + class SELF, + class ITEM, + bool (SELF::*DoWithFn)(int, int, int, cItemCallback &), + bool (SELF::*CoordCheckFn)(int, int, int) const + > + static int DoWithXYZ(lua_State * tolua_S) + { + // Check params: + cLuaState L(tolua_S); + if ( + !L.CheckParamNumber(2, 4) || + !L.CheckParamFunction(5) || + !L.CheckParamEnd(6) + ) + { + return 0; + } + + // Get parameters: + SELF * Self = nullptr; + int BlockX = 0; + int BlockY = 0; + int BlockZ = 0; + cLuaState::cRef FnRef; + L.GetStackValues(1, Self, BlockX, BlockY, BlockZ, FnRef); + if (Self == nullptr) + { + return lua_do_error(tolua_S, "Error in function call '#funcname#': Invalid 'self'"); + } + if (!FnRef.IsValid()) + { + return lua_do_error(tolua_S, "Error in function call '#funcname#': Expected a valid callback function for parameter #5"); + } + if (!(Self->*CoordCheckFn)(BlockX, BlockY, BlockZ)) + { + return lua_do_error(tolua_S, Printf("Error in function call '#funcname#': The provided coordinates ({%d, %d, %d}) are not valid", + BlockX, BlockY, BlockZ + ).c_str()); + } + + class cLuaCallback : public cItemCallback + { + public: + cLuaCallback(cLuaState & a_LuaState, cLuaState::cRef & a_FnRef): + m_LuaState(a_LuaState), + m_FnRef(a_FnRef) + { + } + + private: + virtual bool Item(ITEM * a_Item) override { bool ret = false; m_LuaState.Call(m_FnRef, a_Item, cLuaState::Return, ret); diff --git a/src/Bindings/ManualBindings_BlockArea.cpp b/src/Bindings/ManualBindings_BlockArea.cpp new file mode 100644 index 000000000..14462c6e9 --- /dev/null +++ b/src/Bindings/ManualBindings_BlockArea.cpp @@ -0,0 +1,998 @@ +// ManualBindings_BlockArea.cpp + +// Implements the manual bindings for functions in the cBlockArea class + +#include "Globals.h" +#include "tolua++/include/tolua++.h" +#include "../BlockArea.h" +#include "../World.h" +#include "ManualBindings.h" +#include "LuaState.h" +#include "PluginLua.h" +#include "../WorldStorage/SchematicFileSerializer.h" + + + + + + +/** Reads params that together form a Cuboid. +These can be: + - 6 numbers (MinX, MaxX, MinY, MaxY, MinZ, MaxZ) + - 2 Vector3-s (Min, Max) + - cCuboid +Returns the index of the first parameter following the Cuboid spec. +Raises an Api error if the params don't specify a Cuboid. +*/ +static int readCuboidOverloadParams(cLuaState & a_LuaState, int a_StartParam, cCuboid & a_Cuboid) +{ + if (a_LuaState.IsParamNumber(a_StartParam)) + { + // Assume the 6-number version: + if (!a_LuaState.GetStackValues(a_StartParam, a_Cuboid.p1.x, a_Cuboid.p2.x, a_Cuboid.p1.y, a_Cuboid.p2.y, a_Cuboid.p1.z, a_Cuboid.p2.z)) + { + return cManualBindings::ApiParamError(a_LuaState, "Cannot read the bounds parameters, expected 6 numbers."); + } + return a_StartParam + 6; + } + else if (a_LuaState.IsParamUserType(a_StartParam, "cCuboid")) + { + // Assume the cCuboid version: + cCuboid * c; + if (!a_LuaState.GetStackValues(a_StartParam, c)) + { + return cManualBindings::ApiParamError(a_LuaState, "Cannot read the bounds parameter, expected a cCuboid instance."); + } + a_Cuboid = *c; + return a_StartParam + 1; + } + else + { + // Assume the 2-Vector3i version: + Vector3i * p1; + Vector3i * p2; + if (!a_LuaState.GetStackValues(a_StartParam, p1, p2)) + { + return cManualBindings::ApiParamError(a_LuaState, "Cannot read the bounds parameter, expected two Vector3i instances."); + } + a_Cuboid.p1 = *p1; + a_Cuboid.p2 = *p2; + return a_StartParam + 2; + } +} + + + + + +/** Reads params that together form a Vector3i. +These can be: + - 3 numbers (x, y, z) + - Vector3i +Returns the index of the first parameter following the Vector3i spec. +Raises an Api error if the params don't specify a Vector3i. +*/ +static int readVector3iOverloadParams(cLuaState & a_LuaState, int a_StartParam, Vector3i & a_Coords, const char * a_ParamName) +{ + if (a_LuaState.IsParamNumber(a_StartParam)) + { + // Assume the 3-number version: + if (!a_LuaState.GetStackValues(a_StartParam, a_Coords.x, a_Coords.y, a_Coords.z)) + { + return cManualBindings::ApiParamError(a_LuaState, "Cannot read the %s, expected 3 numbers.", a_ParamName); + } + return a_StartParam + 3; + } + else + { + // Assume the Vector3i version: + Vector3i * c; + if (!a_LuaState.GetStackValues(a_StartParam, c)) + { + return cManualBindings::ApiParamError(a_LuaState, "Cannot read the %s, expected a Vector3i instance.", a_ParamName); + } + a_Coords = *c; + return a_StartParam + 1; + } +} + + + + + +/** Binding for the cBlockArea::Create() functions. Supports two overloads and one default parameter. */ +static int tolua_cBlockArea_Create(lua_State * a_LuaState) +{ + cLuaState L(a_LuaState); + if (!L.CheckParamSelf("cBlockArea")) + { + return 0; + } + cBlockArea * self = nullptr; + if (!L.GetStackValues(1, self)) + { + return cManualBindings::ApiParamError(a_LuaState, "Cannot read self."); + } + if (self == nullptr) + { + return cManualBindings::ApiParamError(a_LuaState, "Invalid 'self', must not be nil."); + } + + int dataTypes = cBlockArea::baTypes | cBlockArea::baMetas | cBlockArea::baBlockEntities; + Vector3i size; + auto dataTypesIdx = readVector3iOverloadParams(L, 2, size, "size"); + L.GetStackValue(dataTypesIdx, dataTypes); + if (!cBlockArea::IsValidDataTypeCombination(dataTypes)) + { + return cManualBindings::ApiParamError(a_LuaState, "Invalid combination of baDataTypes specified (%d).", dataTypes); + } + + // Create the area: + if ((size.x <= 0) || (size.y <= 0) || (size.z <= 0)) + { + return cManualBindings::ApiParamError(a_LuaState, "Invalid sizes, must be greater than zero, got {%d, %d, %d}", size.x, size.y, size.z); + } + ASSERT(self != nullptr); + self->Create(size, dataTypes); + return 0; +} + + + + + +/** Bindings for the cBlockArea:FillRelCuboid() functions. Supports coord overloads and one default parameter. */ +static int tolua_cBlockArea_FillRelCuboid(lua_State * a_LuaState) +{ + // Check the common params: + cLuaState L(a_LuaState); + if (!L.CheckParamSelf("cBlockArea")) + { + return 0; + } + + // Get the common params: + cBlockArea * self = nullptr; + if (!L.GetStackValues(1, self)) + { + return cManualBindings::ApiParamError(a_LuaState, "Cannot read self."); + } + if (self == nullptr) + { + return cManualBindings::ApiParamError(a_LuaState, "Invalid 'self', must not be nil."); + } + + // Check and get the overloaded params: + cCuboid bounds; + auto nextIdx = readCuboidOverloadParams(L, 2, bounds); + int dataTypes = cBlockArea::baTypes | cBlockArea::baMetas | cBlockArea::baBlockEntities; + BLOCKTYPE blockType; + NIBBLETYPE blockMeta = 0, blockLight = 0, blockSkyLight = 0x0f; + if (!L.GetStackValues(nextIdx, dataTypes, blockType)) + { + return cManualBindings::ApiParamError(a_LuaState, "Cannot read the datatypes or block type params"); + } + L.GetStackValues(nextIdx + 2, blockMeta, blockLight, blockSkyLight); // These values are optional + if (!cBlockArea::IsValidDataTypeCombination(dataTypes)) + { + return cManualBindings::ApiParamError(a_LuaState, "Invalid baDataTypes combination (%d).", dataTypes); + } + + // Check the coords, shift if needed: + bounds.Sort(); + bounds.ClampX(0, self->GetSizeX()); + bounds.ClampY(0, self->GetSizeY()); + bounds.ClampZ(0, self->GetSizeZ()); + + // Do the actual Fill: + self->FillRelCuboid(bounds, dataTypes, blockType, blockMeta, blockLight, blockSkyLight); + return 0; +} + + + + + +static int tolua_cBlockArea_GetBlockTypeMeta(lua_State * a_LuaState) +{ + // function cBlockArea::GetBlockTypeMeta() + + cLuaState L(a_LuaState); + if (!L.CheckParamSelf("cBlockArea")) + { + return 0; + } + + cBlockArea * self; + if (!L.GetStackValues(1, self)) + { + return cManualBindings::ApiParamError(a_LuaState, "Cannot read 'self'"); + } + if (self == nullptr) + { + return cManualBindings::ApiParamError(a_LuaState, "Invalid 'self', must not be nil."); + } + + Vector3i coords; + readVector3iOverloadParams(L, 2, coords, "coords"); + if (!self->IsValidCoords(coords)) + { + return cManualBindings::ApiParamError(a_LuaState, "Coords ({%d, %d, %d}) out of range ({%d, %d, %d} - {%d, %d, %d}).", + coords.x, coords.y, coords.z, + self->GetOriginX(), self->GetOriginY(), self->GetOriginZ(), + self->GetOriginX() + self->GetSizeX() - 1, self->GetOriginY() + self->GetSizeY() - 1, self->GetOriginZ() + self->GetSizeZ() - 1 + ); + } + BLOCKTYPE blockType; + NIBBLETYPE blockMeta; + self->GetBlockTypeMeta(coords.x, coords.y, coords.z, blockType, blockMeta); + L.Push(blockType, blockMeta); + return 2; +} + + + + + +static int tolua_cBlockArea_GetCoordRange(lua_State * a_LuaState) +{ + // function cBlockArea::GetCoordRange() + // Returns all three sizes of the area, each minus one, so that they represent the maximum coord value + // Exported manually because there's no direct C++ equivalent, + // plus tolua would generate extra input params for the outputs + + cLuaState L(a_LuaState); + if (!L.CheckParamSelf("cBlockArea")) + { + return 0; + } + + cBlockArea * self; + if (!L.GetStackValues(1, self)) + { + return cManualBindings::ApiParamError(a_LuaState, "Cannot read the 'self' parameter."); + } + if (self == nullptr) + { + return cManualBindings::ApiParamError(a_LuaState, "Invalid 'self', must not be nil."); + } + + L.Push(self->GetSizeX() - 1, self->GetSizeY() - 1, self->GetSizeZ() - 1); + return 3; +} + + + + + +static int tolua_cBlockArea_GetNonAirCropRelCoords(lua_State * a_LuaState) +{ + // function cBlockArea::GetNonAirCropRelCoords() + // Exported manually because tolua would generate extra input params for the outputs + + cLuaState L(a_LuaState); + if (!L.CheckParamSelf("cBlockArea")) + { + return 0; + } + + cBlockArea * self = nullptr; + BLOCKTYPE ignoreBlockType = E_BLOCK_AIR; + if (!L.GetStackValues(1, self, ignoreBlockType)) + { + return cManualBindings::ApiParamError(a_LuaState, "Cannot read params"); + } + if (self == nullptr) + { + return cManualBindings::ApiParamError(a_LuaState, "Invalid 'self', must not be nil"); + } + if (!self->HasBlockTypes()) + { + return cManualBindings::ApiParamError(a_LuaState, "The area doesn't contain baTypes datatype"); + } + + // Calculate the crop coords: + int minRelX, minRelY, minRelZ, maxRelX, maxRelY, maxRelZ; + self->GetNonAirCropRelCoords(minRelX, minRelY, minRelZ, maxRelX, maxRelY, maxRelZ, ignoreBlockType); + + // Push the six crop coords: + L.Push(minRelX, minRelY, minRelZ, maxRelX, maxRelY, maxRelZ); + return 6; +} + + + + + +static int tolua_cBlockArea_GetOrigin(lua_State * a_LuaState) +{ + // function cBlockArea::GetOrigin() + // Returns all three coords of the origin point + // Exported manually because there's no direct C++ equivalent, + // plus tolua would generate extra input params for the outputs + + cLuaState L(a_LuaState); + if (!L.CheckParamSelf("cBlockArea")) + { + return 0; + } + + cBlockArea * self; + if (!L.GetStackValues(1, self)) + { + return cManualBindings::ApiParamError(a_LuaState, "Cannot read the 'self' parameter."); + } + if (self == nullptr) + { + return cManualBindings::ApiParamError(a_LuaState, "Invalid 'self', must not be nil."); + } + + // Push the three origin coords: + L.Push(self->GetOriginX(), self->GetOriginY(), self->GetOriginZ()); + return 3; +} + + + + + +static int tolua_cBlockArea_GetRelBlockTypeMeta(lua_State * a_LuaState) +{ + // function cBlockArea::GetRelBlockTypeMeta() + // Exported manually because tolua generates extra input params for the outputs + + cLuaState L(a_LuaState); + if (!L.CheckParamSelf("cBlockArea")) + { + return 0; + } + + cBlockArea * self; + if (!L.GetStackValues(1, self)) + { + return cManualBindings::ApiParamError(a_LuaState, "Cannot read the 'self' parameter."); + } + if (self == nullptr) + { + return cManualBindings::ApiParamError(a_LuaState, "Invalid 'self', must not be nil."); + } + if (!self->HasBlockTypes()) + { + return cManualBindings::ApiParamError(a_LuaState, "The area doesn't contain baTypes datatype"); + } + if (!self->HasBlockMetas()) + { + return cManualBindings::ApiParamError(a_LuaState, "The area doesn't contain baMetas datatype"); + } + + Vector3i coords; + readVector3iOverloadParams(L, 2, coords, "coords"); + if (!self->IsValidRelCoords(coords)) + { + return cManualBindings::ApiParamError(a_LuaState, "The coords ({%d, %d, %d}) are out of range (max {%d, %d, %d}).", + coords.x, coords.y, coords.z, + self->GetSizeX() - 1, self->GetSizeY() - 1, self->GetSizeZ() - 1 + ); + } + BLOCKTYPE blockType; + NIBBLETYPE blockMeta; + self->GetRelBlockTypeMeta(coords.x, coords.y, coords.z, blockType, blockMeta); + L.Push(blockType, blockMeta); + return 2; +} + + + + + +static int tolua_cBlockArea_GetSize(lua_State * a_LuaState) +{ + // function cBlockArea::GetSize() + // Returns all three sizes of the area + // Exported manually because there's no direct C++ equivalent, + // plus tolua would generate extra input params for the outputs + + cLuaState L(a_LuaState); + if (!L.CheckParamSelf("cBlockArea")) + { + return 0; + } + + cBlockArea * self; + if (!L.GetStackValues(1, self)) + { + return cManualBindings::ApiParamError(a_LuaState, "Cannot read the 'self' parameter."); + } + if (self == nullptr) + { + return cManualBindings::ApiParamError(a_LuaState, "Invalid 'self', must not be nil."); + } + + L.Push(self->GetSizeX(), self->GetSizeY(), self->GetSizeZ()); + return 3; +} + + + + + +static int tolua_cBlockArea_LoadFromSchematicFile(lua_State * a_LuaState) +{ + // function cBlockArea::LoadFromSchematicFile + // Exported manually because function has been moved to SchematicFileSerializer.cpp + cLuaState L(a_LuaState); + if ( + !L.CheckParamSelf("cBlockArea") || + !L.CheckParamString(2) || + !L.CheckParamEnd(3) + ) + { + return 0; + } + cBlockArea * self; + AString fileName; + if (!L.GetStackValues(1, self, fileName)) + { + return cManualBindings::ApiParamError(a_LuaState, "Cannot read the parameters."); + } + if (self == nullptr) + { + return cManualBindings::ApiParamError(a_LuaState, "Invalid 'self', must not be nil."); + } + + L.Push(cSchematicFileSerializer::LoadFromSchematicFile(*self, fileName)); + return 1; +} + + + + + +static int tolua_cBlockArea_LoadFromSchematicString(lua_State * a_LuaState) +{ + // function cBlockArea::LoadFromSchematicString + // Exported manually because function has been moved to SchematicFileSerializer.cpp + cLuaState L(a_LuaState); + if ( + !L.CheckParamSelf("cBlockArea") || + !L.CheckParamString(2) || + !L.CheckParamEnd(3) + ) + { + return 0; + } + cBlockArea * self; + AString data; + if (!L.GetStackValues(1, self, data)) + { + return cManualBindings::ApiParamError(a_LuaState, "Cannot read the parameters."); + } + if (self == nullptr) + { + return cManualBindings::ApiParamError(a_LuaState, "Invalid 'self', must not be nil."); + } + + L.Push(cSchematicFileSerializer::LoadFromSchematicString(*self, data)); + return 1; +} + + + + + +/** Bindings for the cBlockArea:Read() functions. Supports three overloads and one default parameter. */ +static int tolua_cBlockArea_Read(lua_State * a_LuaState) +{ + // Check the common params: + cLuaState L(a_LuaState); + if ( + !L.CheckParamSelf("cBlockArea") || + !L.CheckParamUserType(2, "cWorld") + ) + { + return 0; + } + + // Get the common params: + cBlockArea * self = nullptr; + cWorld * world = nullptr; + if (!L.GetStackValues(1, self, world)) + { + return cManualBindings::ApiParamError(a_LuaState, "Cannot read self or world."); + } + if (world == nullptr) + { + return cManualBindings::ApiParamError(a_LuaState, "Invalid world instance. The world must be not nil."); + } + + // Check and get the overloaded params: + cCuboid bounds; + int dataTypes = cBlockArea::baTypes | cBlockArea::baMetas | cBlockArea::baBlockEntities; + auto dataTypesIdx = readCuboidOverloadParams(L, 3, bounds); + L.GetStackValues(dataTypesIdx, dataTypes); + if (!cBlockArea::IsValidDataTypeCombination(dataTypes)) + { + return cManualBindings::ApiParamError(a_LuaState, "Invalid baDataTypes combination (%d).", dataTypes); + } + + // Check the coords, shift if needed: + bounds.Sort(); + if (bounds.p1.y < 0) + { + LOGWARNING("cBlockArea:Read(): MinBlockY less than zero, adjusting to zero. Coords: {%d, %d, %d} - {%d, %d, %d}", + bounds.p1.x, bounds.p1.y, bounds.p1.z, bounds.p2.x, bounds.p2.y, bounds.p2.z + ); + L.LogStackTrace(); + bounds.p1.y = 0; + } + else if (bounds.p1.y >= cChunkDef::Height) + { + LOGWARNING("cBlockArea:Read(): MinBlockY more than chunk height, adjusting to chunk height. Coords: {%d, %d, %d} - {%d, %d, %d}", + bounds.p1.x, bounds.p1.y, bounds.p1.z, bounds.p2.x, bounds.p2.y, bounds.p2.z + ); + L.LogStackTrace(); + bounds.p1.y = cChunkDef::Height - 1; + } + if (bounds.p2.y < 0) + { + LOGWARNING("cBlockArea:Read(): MaxBlockY less than zero, adjusting to zero. Coords: {%d, %d, %d} - {%d, %d, %d}", + bounds.p1.x, bounds.p1.y, bounds.p1.z, bounds.p2.x, bounds.p2.y, bounds.p2.z + ); + L.LogStackTrace(); + bounds.p2.y = 0; + } + else if (bounds.p2.y > cChunkDef::Height) + { + LOGWARNING("cBlockArea:Read(): MaxBlockY more than chunk height, adjusting to chunk height. Coords: {%d, %d, %d} - {%d, %d, %d}", + bounds.p1.x, bounds.p1.y, bounds.p1.z, bounds.p2.x, bounds.p2.y, bounds.p2.z + ); + L.LogStackTrace(); + bounds.p2.y = cChunkDef::Height; + } + + // Do the actual read: + L.Push(self->Read(*world, bounds, dataTypes)); + return 1; +} + + + + + +/** Bindings for the cBlockArea:RelLine() functions. Supports two overloads and one default parameter. +Also supports "bastard overloads" (Vector3i, x, y, z), but we don't advertise those. */ +static int tolua_cBlockArea_RelLine(lua_State * a_LuaState) +{ + // Check the common params: + cLuaState L(a_LuaState); + if (!L.CheckParamSelf("cBlockArea")) + { + return 0; + } + + // Get the common params: + cBlockArea * self = nullptr; + if (!L.GetStackValues(1, self)) + { + return cManualBindings::ApiParamError(a_LuaState, "Cannot read self."); + } + + // Check and get the overloaded params: + Vector3i p1; + auto idx = readVector3iOverloadParams(L, 2, p1, "start coords"); + Vector3i p2; + idx = readVector3iOverloadParams(L, idx, p2, "end coords"); + int dataTypes = cBlockArea::baTypes | cBlockArea::baMetas | cBlockArea::baBlockEntities; + BLOCKTYPE blockType; + NIBBLETYPE blockMeta, blockLight, blockSkyLight; + L.GetStackValues(idx, dataTypes, blockType, blockMeta, blockLight, blockSkyLight); + if (!cBlockArea::IsValidDataTypeCombination(dataTypes)) + { + return cManualBindings::ApiParamError(a_LuaState, "Invalid baDataTypes combination (%d).", dataTypes); + } + + // Draw the line: + self->RelLine(p1, p2, dataTypes, blockType, blockMeta, blockLight, blockSkyLight); + return 0; +} + + + + + +static int tolua_cBlockArea_SaveToSchematicFile(lua_State * a_LuaState) +{ + // function cBlockArea::SaveToSchematicFile + // Exported manually because function has been moved to SchematicFileSerializer.cpp + cLuaState L(a_LuaState); + if ( + !L.CheckParamSelf("cBlockArea") || + !L.CheckParamString(2) || + !L.CheckParamEnd(3) + ) + { + return 0; + } + cBlockArea * self; + AString fileName; + if (!L.GetStackValues(1, self, fileName)) + { + return cManualBindings::ApiParamError(a_LuaState, "Cannot read the parameters."); + } + if (self == nullptr) + { + return cManualBindings::ApiParamError(a_LuaState, "Invalid 'self', must not be nil."); + } + + L.Push(cSchematicFileSerializer::SaveToSchematicFile(*self, fileName)); + return 1; +} + + + + + +static int tolua_cBlockArea_SaveToSchematicString(lua_State * a_LuaState) +{ + // function cBlockArea::SaveToSchematicString + // Exported manually because function has been moved to SchematicFileSerializer.cpp + cLuaState L(a_LuaState); + if ( + !L.CheckParamSelf("cBlockArea") || + !L.CheckParamEnd(2) + ) + { + return 0; + } + cBlockArea * self; + if (!L.GetStackValues(1, self)) + { + return cManualBindings::ApiParamError(a_LuaState, "Cannot read the 'self' parameter."); + } + if (self == nullptr) + { + return cManualBindings::ApiParamError(a_LuaState, "Invalid 'self', must not be nil."); + } + + AString data; + if (cSchematicFileSerializer::SaveToSchematicString(*self, data)) + { + L.Push(data); + return 1; + } + return 0; +} + + + + + +/** Bindings for the cBlockArea:Write() functions. Supports two overloads and one default parameter. */ +static int tolua_cBlockArea_Write(lua_State * a_LuaState) +{ + // Check the common params: + cLuaState L(a_LuaState); + if ( + !L.CheckParamSelf("cBlockArea") || + !L.CheckParamUserType(2, "cWorld") + ) + { + return 0; + } + + // Get the common params: + cBlockArea * self = nullptr; + cWorld * world = nullptr; + if (!L.GetStackValues(1, self, world)) + { + return cManualBindings::ApiParamError(a_LuaState, "Cannot read self or world."); + } + if (world == nullptr) + { + return cManualBindings::ApiParamError(a_LuaState, "Invalid world instance. The world must be not nil."); + } + + // Check and get the overloaded params: + Vector3i coords; + int dataTypes = cBlockArea::baTypes | cBlockArea::baMetas | cBlockArea::baBlockEntities; + auto dataTypesIdx = readVector3iOverloadParams(L, 3, coords, "coords"); + L.GetStackValues(dataTypesIdx, dataTypes); + + // Check the dataType parameter validity: + if (!cBlockArea::IsValidDataTypeCombination(dataTypes)) + { + return cManualBindings::ApiParamError(a_LuaState, "Invalid datatype combination (%d).", dataTypes); + } + if ((self->GetDataTypes() & dataTypes) != dataTypes) + { + return cManualBindings::ApiParamError(a_LuaState, "Requesting datatypes not present in the cBlockArea. Got only 0x%02x, requested 0x%02x", + self->GetDataTypes(), dataTypes + ); + } + + // Check and adjust the coord params: + // TODO: Should we report this as a failure? Because the result is definitely not what the plugin assumed + // ... Or should we silently clone-crop-write the cBlockArea so that the API call does what would be naturally expected? + // ... Or should we change the cBlockArea::Write() to allow out-of-range Y coords and do the cropping there? + // ... NOTE: We already support auto-crop in cBlockArea::Merge() itself + if (coords.y < 0) + { + LOGWARNING("cBlockArea:Write(): MinBlockY less than zero, adjusting to zero"); + L.LogStackTrace(); + coords.y = 0; + } + else if (coords.y > cChunkDef::Height - self->GetSizeY()) + { + LOGWARNING("cBlockArea:Write(): MinBlockY + m_SizeY more than chunk height, adjusting to chunk height"); + L.LogStackTrace(); + coords.y = cChunkDef::Height - self->GetSizeY(); + } + + // Do the actual write: + L.Push(self->Write(*world, coords, dataTypes)); + return 1; +} + + + + + +/** Templated bindings for the GetBlock___() functions. +DataType is either BLOCKTYPE or NIBBLETYPE. +DataTypeFlag is the ba___ constant used for the datatype being queried. +Fn is the getter function. +Also supports the Vector3i overloads (TODO: document these (?)). */ +template < + typename DataType, + int DataTypeFlag, + DataType (cBlockArea::*Fn)(int, int, int) const +> +static int GetBlock(lua_State * a_LuaState) +{ + // Check the common params: + cLuaState L(a_LuaState); + if (!L.CheckParamSelf("cBlockArea")) + { + return 0; + } + + // Read the common params: + cBlockArea * self; + if (!L.GetStackValues(1, self)) + { + return cManualBindings::ApiParamError(a_LuaState, "Cannot read the 'self' param."); + } + + // Check the datatype's presence: + if ((self->GetDataTypes() & DataTypeFlag) == 0) + { + return cManualBindings::ApiParamError(a_LuaState, "The area doesn't contain the datatype (%d).", DataTypeFlag); + } + + // Read the overloaded params: + Vector3i coords; + readVector3iOverloadParams(L, 2, coords, "coords"); + if (!self->IsValidCoords(coords)) + { + return cManualBindings::ApiParamError(a_LuaState, "The coords ({%d, %d, %d}) are out of range ({%d, %d, %d} - {%d, %d, %d}).", + coords.x, coords.y, coords.z, + self->GetOriginX(), self->GetOriginY(), self->GetOriginZ(), + self->GetOriginX() + self->GetSizeX() - 1, self->GetOriginY() + self->GetSizeY() - 1, self->GetOriginZ() + self->GetSizeZ() - 1 + ); + } + + // Get the block info: + L.Push((self->*Fn)(coords.x, coords.y, coords.z)); + return 1; +} + + + + + +/** Templated bindings for the GetRelBlock___() functions. +DataType is either BLOCKTYPE or NIBBLETYPE. +DataTypeFlag is the ba___ constant used for the datatype being queried. +Fn is the getter function. +Also supports the Vector3i overloads (TODO: document these (?)). */ +template < + typename DataType, + int DataTypeFlag, + DataType (cBlockArea::*Fn)(int, int, int) const +> +static int GetRelBlock(lua_State * a_LuaState) +{ + // Check the common params: + cLuaState L(a_LuaState); + if (!L.CheckParamSelf("cBlockArea")) + { + return 0; + } + + // Read the common params: + cBlockArea * self; + if (!L.GetStackValues(1, self)) + { + return cManualBindings::ApiParamError(a_LuaState, "Cannot read the 'self' param."); + } + + // Check the datatype's presence: + if ((self->GetDataTypes() & DataTypeFlag) == 0) + { + return cManualBindings::ApiParamError(a_LuaState, "The area doesn't contain the datatype (%d).", DataTypeFlag); + } + + // Read the overloaded params: + Vector3i coords; + readVector3iOverloadParams(L, 2, coords, "coords"); + if (!self->IsValidRelCoords(coords)) + { + return cManualBindings::ApiParamError(a_LuaState, "The coords ({%d, %d, %d}) are out of range ({%d, %d, %d}).", + coords.x, coords.y, coords.z, + self->GetSizeX(), self->GetSizeY(), self->GetSizeZ() + ); + } + + // Get the block info: + L.Push((self->*Fn)(coords.x, coords.y, coords.z)); + return 0; +} + + + + + +/** Templated bindings for the SetBlock___() functions. +DataType is either BLOCKTYPE or NIBBLETYPE. +DataTypeFlag is the ba___ constant used for the datatypebeing manipulated. +Fn is the setter function. +Also supports the Vector3i overloads (TODO: document these (?)). */ +template < + typename DataType, + int DataTypeFlag, + void (cBlockArea::*Fn)(int, int, int, DataType) +> +static int SetBlock(lua_State * a_LuaState) +{ + // Check the common params: + cLuaState L(a_LuaState); + if (!L.CheckParamSelf("cBlockArea")) + { + return 0; + } + + // Read the common params: + cBlockArea * self; + if (!L.GetStackValues(1, self)) + { + return cManualBindings::ApiParamError(a_LuaState, "Cannot read the 'self' param."); + } + + // Check the datatype's presence: + if ((self->GetDataTypes() & DataTypeFlag) == 0) + { + return cManualBindings::ApiParamError(a_LuaState, "The area doesn't contain the datatype (%d).", DataTypeFlag); + } + + // Read the overloaded params: + Vector3i coords; + auto idx = readVector3iOverloadParams(L, 2, coords, "coords"); + if (!self->IsValidCoords(coords)) + { + return cManualBindings::ApiParamError(a_LuaState, "The coords ({%d, %d, %d}) are out of range ({%d, %d, %d} - {%d, %d, %d}).", + coords.x, coords.y, coords.z, + self->GetOriginX(), self->GetOriginY(), self->GetOriginZ(), + self->GetOriginX() + self->GetSizeX() - 1, self->GetOriginY() + self->GetSizeY() - 1, self->GetOriginZ() + self->GetSizeZ() - 1 + ); + } + DataType data; + L.GetStackValues(idx, data); + + // Set the block info: + (self->*Fn)(coords.x, coords.y, coords.z, data); + return 0; +} + + + + + +/** Templated bindings for the SetRelBlock___() functions. +DataType is either BLOCKTYPE or NIBBLETYPE. +DataTypeFlag is the ba___ constant used for the datatypebeing manipulated. +Fn is the setter function. +Also supports the Vector3i overloads (TODO: document these (?)). */ +template < + typename DataType, + int DataTypeFlag, + void (cBlockArea::*Fn)(int, int, int, DataType) +> +static int SetRelBlock(lua_State * a_LuaState) +{ + // Check the common params: + cLuaState L(a_LuaState); + if (!L.CheckParamSelf("cBlockArea")) + { + return 0; + } + + // Read the common params: + cBlockArea * self; + if (!L.GetStackValues(1, self)) + { + return cManualBindings::ApiParamError(a_LuaState, "Cannot read the 'self' param."); + } + + // Check the datatype's presence: + if ((self->GetDataTypes() & DataTypeFlag) == 0) + { + return cManualBindings::ApiParamError(a_LuaState, "The area doesn't contain the datatype (%d).", DataTypeFlag); + } + + // Read the overloaded params: + Vector3i coords; + auto idx = readVector3iOverloadParams(L, 2, coords, "coords"); + if (!self->IsValidRelCoords(coords)) + { + return cManualBindings::ApiParamError(a_LuaState, "The coords ({%d, %d, %d}) are out of range ({%d, %d, %d}).", + coords.x, coords.y, coords.z, + self->GetSizeX(), self->GetSizeY(), self->GetSizeZ() + ); + } + DataType data; + L.GetStackValues(idx, data); + + // Set the block info: + (self->*Fn)(coords.x, coords.y, coords.z, data); + return 0; +} + + + + + +void cManualBindings::BindBlockArea(lua_State * a_LuaState) +{ + tolua_beginmodule(a_LuaState, nullptr); + tolua_beginmodule(a_LuaState, "cBlockArea"); + tolua_function(a_LuaState, "Create", tolua_cBlockArea_Create); + tolua_function(a_LuaState, "DoWithBlockEntityAt", DoWithXYZ); + tolua_function(a_LuaState, "DoWithBlockEntityRelAt", DoWithXYZ); + tolua_function(a_LuaState, "FillRelCuboid", tolua_cBlockArea_FillRelCuboid); + tolua_function(a_LuaState, "ForEachBlockEntity", ForEach< cBlockArea, cBlockEntity, &cBlockArea::ForEachBlockEntity>); + tolua_function(a_LuaState, "GetBlockLight", GetBlock); + tolua_function(a_LuaState, "GetBlockMeta", GetBlock); + tolua_function(a_LuaState, "GetBlockSkyLight", GetBlock); + tolua_function(a_LuaState, "GetBlockType", GetBlock); + tolua_function(a_LuaState, "GetBlockTypeMeta", tolua_cBlockArea_GetBlockTypeMeta); + tolua_function(a_LuaState, "GetCoordRange", tolua_cBlockArea_GetCoordRange); + tolua_function(a_LuaState, "GetNonAirCropRelCoords", tolua_cBlockArea_GetNonAirCropRelCoords); + tolua_function(a_LuaState, "GetOrigin", tolua_cBlockArea_GetOrigin); + tolua_function(a_LuaState, "GetRelBlockLight", GetRelBlock); + tolua_function(a_LuaState, "GetRelBlockMeta", GetRelBlock); + tolua_function(a_LuaState, "GetRelBlockSkyLight", GetRelBlock); + tolua_function(a_LuaState, "GetRelBlockType", GetRelBlock); + tolua_function(a_LuaState, "GetRelBlockTypeMeta", tolua_cBlockArea_GetRelBlockTypeMeta); + tolua_function(a_LuaState, "GetSize", tolua_cBlockArea_GetSize); + tolua_function(a_LuaState, "LoadFromSchematicFile", tolua_cBlockArea_LoadFromSchematicFile); + tolua_function(a_LuaState, "LoadFromSchematicString", tolua_cBlockArea_LoadFromSchematicString); + tolua_function(a_LuaState, "Read", tolua_cBlockArea_Read); + tolua_function(a_LuaState, "RelLine", tolua_cBlockArea_RelLine); + tolua_function(a_LuaState, "SaveToSchematicFile", tolua_cBlockArea_SaveToSchematicFile); + tolua_function(a_LuaState, "SaveToSchematicString", tolua_cBlockArea_SaveToSchematicString); + tolua_function(a_LuaState, "SetBlockType", SetBlock); + tolua_function(a_LuaState, "SetBlockMeta", SetBlock); + tolua_function(a_LuaState, "SetBlockLight", SetBlock); + tolua_function(a_LuaState, "SetBlockSkyLight", SetBlock); + tolua_function(a_LuaState, "SetRelBlockType", SetRelBlock); + tolua_function(a_LuaState, "SetRelBlockMeta", SetRelBlock); + tolua_function(a_LuaState, "SetRelBlockLight", SetRelBlock); + tolua_function(a_LuaState, "SetRelBlockSkyLight", SetRelBlock); + tolua_function(a_LuaState, "Write", tolua_cBlockArea_Write); + tolua_endmodule(a_LuaState); + tolua_endmodule(a_LuaState); +} + + + + diff --git a/src/BlockArea.cpp b/src/BlockArea.cpp index bca5544f9..a35c391fa 100644 --- a/src/BlockArea.cpp +++ b/src/BlockArea.cpp @@ -14,6 +14,7 @@ #include "Blocks/BlockHandler.h" #include "Cuboid.h" #include "ChunkData.h" +#include "BlockEntities/BlockEntity.h" @@ -317,12 +318,37 @@ cBlockArea::~cBlockArea() +bool cBlockArea::IsValidDataTypeCombination(int a_DataTypes) +{ + // BlockEntities require that BlockTypes be present, too + if ((a_DataTypes & baBlockEntities) != 0) + { + if ((a_DataTypes & baTypes) == 0) + { + return false; + } + } + + // All other combinations are considered valid + return true; +} + + + + + void cBlockArea::Clear(void) { delete[] m_BlockTypes; m_BlockTypes = nullptr; delete[] m_BlockMetas; m_BlockMetas = nullptr; delete[] m_BlockLight; m_BlockLight = nullptr; delete[] m_BlockSkyLight; m_BlockSkyLight = nullptr; + if (m_BlockEntities != nullptr) + { + ClearBlockEntities(*m_BlockEntities); + m_BlockEntities.reset(); + } + m_BlockEntities.reset(); m_Origin.Set(0, 0, 0); m_Size.Set(0, 0, 0); } @@ -333,13 +359,10 @@ void cBlockArea::Clear(void) void cBlockArea::Create(int a_SizeX, int a_SizeY, int a_SizeZ, int a_DataTypes) { - if ((a_SizeX < 0) || (a_SizeY < 0) || (a_SizeZ < 0)) - { - LOGWARNING("Creating a cBlockArea with a negative size! Call to Create ignored. (%d, %d, %d)", - a_SizeX, a_SizeY, a_SizeZ - ); - return; - } + ASSERT(a_SizeX > 0); + ASSERT(a_SizeY > 0); + ASSERT(a_SizeZ > 0); + ASSERT(IsValidDataTypeCombination(a_DataTypes)); // Warn if the height is too much, but proceed with the creation: if (a_SizeY > cChunkDef::Height) @@ -381,6 +404,10 @@ void cBlockArea::Create(int a_SizeX, int a_SizeY, int a_SizeZ, int a_DataTypes) m_BlockSkyLight[i] = 0x0f; } } + if ((a_DataTypes & baBlockEntities) != 0) + { + m_BlockEntities = cpp14::make_unique(); + } m_Size.Set(a_SizeX, a_SizeY, a_SizeZ); m_Origin.Set(0, 0, 0); } @@ -434,57 +461,60 @@ void cBlockArea::SetOrigin(const Vector3i & a_Origin) +bool cBlockArea::IsValidRelCoords(int a_RelX, int a_RelY, int a_RelZ) const +{ + return ( + (a_RelX >= 0) && (a_RelX < m_Size.x) && + (a_RelY >= 0) && (a_RelY < m_Size.y) && + (a_RelZ >= 0) && (a_RelZ < m_Size.z) + ); +} + + + + + +bool cBlockArea::IsValidRelCoords(const Vector3i & a_RelCoords) const +{ + return IsValidRelCoords(a_RelCoords.x, a_RelCoords.y, a_RelCoords.z); +} + + + + + +bool cBlockArea::IsValidCoords(int a_BlockX, int a_BlockY, int a_BlockZ) const +{ + return IsValidRelCoords(a_BlockX - m_Origin.x, a_BlockY - m_Origin.y, a_BlockZ - m_Origin.z); +} + + + + + +bool cBlockArea::IsValidCoords(const Vector3i & a_Coords) const +{ + return IsValidRelCoords(a_Coords - m_Origin); +} + + + + + bool cBlockArea::Read(cForEachChunkProvider & a_ForEachChunkProvider, int a_MinBlockX, int a_MaxBlockX, int a_MinBlockY, int a_MaxBlockY, int a_MinBlockZ, int a_MaxBlockZ, int a_DataTypes) { - // Normalize the coords: - if (a_MinBlockX > a_MaxBlockX) - { - std::swap(a_MinBlockX, a_MaxBlockX); - } - if (a_MinBlockY > a_MaxBlockY) - { - std::swap(a_MinBlockY, a_MaxBlockY); - } - if (a_MinBlockZ > a_MaxBlockZ) - { - std::swap(a_MinBlockZ, a_MaxBlockZ); - } + ASSERT(IsValidDataTypeCombination(a_DataTypes)); + ASSERT(cChunkDef::IsValidHeight(a_MinBlockY)); + ASSERT(cChunkDef::IsValidHeight(a_MaxBlockY)); + ASSERT(a_MinBlockX <= a_MaxBlockX); + ASSERT(a_MinBlockY <= a_MaxBlockX); + ASSERT(a_MinBlockZ <= a_MaxBlockZ); // Include the Max coords: a_MaxBlockX += 1; a_MaxBlockY += 1; a_MaxBlockZ += 1; - // Check coords validity: - if (a_MinBlockY < 0) - { - LOGWARNING("%s: MinBlockY less than zero, adjusting to zero. Coords: {%d, %d, %d} - {%d, %d, %d}", - __FUNCTION__, a_MinBlockX, a_MinBlockY, a_MinBlockZ, a_MaxBlockX, a_MaxBlockY, a_MaxBlockZ - ); - a_MinBlockY = 0; - } - else if (a_MinBlockY >= cChunkDef::Height) - { - LOGWARNING("%s: MinBlockY more than chunk height, adjusting to chunk height. Coords: {%d, %d, %d} - {%d, %d, %d}", - __FUNCTION__, a_MinBlockX, a_MinBlockY, a_MinBlockZ, a_MaxBlockX, a_MaxBlockY, a_MaxBlockZ - ); - a_MinBlockY = cChunkDef::Height - 1; - } - if (a_MaxBlockY < 0) - { - LOGWARNING("%s: MaxBlockY less than zero, adjusting to zero. Coords: {%d, %d, %d} - {%d, %d, %d}", - __FUNCTION__, a_MinBlockX, a_MinBlockY, a_MinBlockZ, a_MaxBlockX, a_MaxBlockY, a_MaxBlockZ - ); - a_MaxBlockY = 0; - } - else if (a_MaxBlockY > cChunkDef::Height) - { - LOGWARNING("%s: MaxBlockY more than chunk height, adjusting to chunk height. Coords: {%d, %d, %d} - {%d, %d, %d}", - __FUNCTION__, a_MinBlockX, a_MinBlockY, a_MinBlockZ, a_MaxBlockX, a_MaxBlockY, a_MaxBlockZ - ); - a_MaxBlockY = cChunkDef::Height; - } - // Allocate the needed memory: Clear(); if (!SetSize(a_MaxBlockX - a_MinBlockX, a_MaxBlockY - a_MinBlockY, a_MaxBlockZ - a_MinBlockZ, a_DataTypes)) @@ -547,19 +577,8 @@ bool cBlockArea::Read(cForEachChunkProvider & a_ForEachChunkProvider, const Vect bool cBlockArea::Write(cForEachChunkProvider & a_ForEachChunkProvider, int a_MinBlockX, int a_MinBlockY, int a_MinBlockZ, int a_DataTypes) { ASSERT((a_DataTypes & GetDataTypes()) == a_DataTypes); // Are you requesting only the data that I have? - a_DataTypes = a_DataTypes & GetDataTypes(); // For release builds, silently cut off the datatypes that I don't have - - // Check coords validity: - if (a_MinBlockY < 0) - { - LOGWARNING("%s: MinBlockY less than zero, adjusting to zero", __FUNCTION__); - a_MinBlockY = 0; - } - else if (a_MinBlockY > cChunkDef::Height - m_Size.y) - { - LOGWARNING("%s: MinBlockY + m_SizeY more than chunk height, adjusting to chunk height", __FUNCTION__); - a_MinBlockY = std::max(cChunkDef::Height - m_Size.y, 0); - } + ASSERT(cChunkDef::IsValidHeight(a_MinBlockY)); + ASSERT(cChunkDef::IsValidHeight(a_MinBlockY + m_Size.y - 1)); return a_ForEachChunkProvider.WriteBlockArea(*this, a_MinBlockX, a_MinBlockY, a_MinBlockZ, a_DataTypes); } @@ -609,6 +628,15 @@ void cBlockArea::CopyTo(cBlockArea & a_Into) const { memcpy(a_Into.m_BlockSkyLight, m_BlockSkyLight, BlockCount * sizeof(NIBBLETYPE)); } + if (HasBlockEntities()) + { + ClearBlockEntities(*(a_Into.m_BlockEntities)); + for (const auto & keyPair: *m_BlockEntities) + { + const auto & pos = keyPair.second->GetPos(); + a_Into.m_BlockEntities->insert({keyPair.first, keyPair.second->Clone(pos.x, pos.y, pos.z)}); + } + } } @@ -705,6 +733,40 @@ void cBlockArea::Crop(int a_AddMinX, int a_SubMaxX, int a_AddMinY, int a_SubMaxY { CropNibbles(m_BlockSkyLight, a_AddMinX, a_SubMaxX, a_AddMinY, a_SubMaxY, a_AddMinZ, a_SubMaxZ); } + + auto maxX = m_Size.x - a_SubMaxX; + auto maxY = m_Size.y - a_SubMaxY; + auto maxZ = m_Size.z - a_SubMaxZ; + + // Move and crop block Entities: + cBlockEntities oldBE; + std::swap(oldBE, *m_BlockEntities); + for (const auto & keyPair: oldBE) + { + auto & be = keyPair.second; + auto posX = be->GetPosX(); + auto posY = be->GetPosY(); + auto posZ = be->GetPosZ(); + if ( + (posX < a_AddMinX) || (posX >= maxX) || + (posY < a_AddMinY) || (posY >= maxY) || + (posZ < a_AddMinZ) || (posZ >= maxZ) + ) + { + // The block entity is out of new coord range, remove it: + delete be; + } + else + { + // The block entity is within the new coords, recalculate its coords to match the new area: + posX -= a_AddMinX; + posY -= a_AddMinY; + posZ -= a_AddMinZ; + be->SetPos(posX, posY, posZ); + m_BlockEntities->insert({MakeIndex(posX, posY, posZ), std::move(be)}); + } + } + m_Origin.Move(a_AddMinX, a_AddMinY, a_AddMinZ); m_Size.x -= a_AddMinX + a_SubMaxX; m_Size.y -= a_AddMinY + a_SubMaxY; @@ -733,6 +795,20 @@ void cBlockArea::Expand(int a_SubMinX, int a_AddMaxX, int a_SubMinY, int a_AddMa { ExpandNibbles(m_BlockSkyLight, a_SubMinX, a_AddMaxX, a_SubMinY, a_AddMaxY, a_SubMinZ, a_AddMaxZ); } + + // Move block entities: + cBlockEntities oldBE; + std::swap(oldBE, *m_BlockEntities); + for (const auto & keyPair: oldBE) + { + auto & be = keyPair.second; + auto posX = be->GetPosX() + a_SubMinX; + auto posY = be->GetPosY() + a_SubMinY; + auto posZ = be->GetPosZ() + a_SubMinZ; + be->SetPos(posX, posY, posZ); + m_BlockEntities->insert({MakeIndex(posX, posY, posZ), std::move(be)}); + } + m_Origin.Move(-a_SubMinX, -a_SubMinY, -a_SubMinZ); m_Size.x += a_SubMinX + a_AddMaxX; m_Size.y += a_SubMinY + a_AddMaxY; @@ -813,6 +889,12 @@ void cBlockArea::Fill(int a_DataTypes, BLOCKTYPE a_BlockType, NIBBLETYPE a_Block m_BlockSkyLight[i] = a_BlockSkyLight; } } + + // If the area contains block entities, remove those not matching and replace with whatever block entity block was filled + if (HasBlockEntities() && ((a_DataTypes & baTypes) != 0)) + { + RescanBlockEntities(); + } } @@ -860,6 +942,12 @@ void cBlockArea::FillRelCuboid(int a_MinRelX, int a_MaxRelX, int a_MinRelY, int m_BlockSkyLight[MakeIndex(x, y, z)] = a_BlockSkyLight; } // for x, z, y } + + // If the area contains block entities, remove those in the affected cuboid and replace with whatever block entity block was filled: + if (HasBlockEntities() && ((a_DataTypes & baTypes) != 0)) + { + RescanBlockEntities(); + } } @@ -1054,6 +1142,23 @@ void cBlockArea::RotateCCW(void) delete[] NewTypes; NewTypes = nullptr; delete[] NewMetas; NewMetas = nullptr; + // Rotate the BlockEntities: + if (HasBlockEntities()) + { + cBlockEntities oldBE; + std::swap(oldBE, *m_BlockEntities); + for (const auto & keyPair: oldBE) + { + auto & be = keyPair.second; + auto newX = be->GetPosZ(); + auto newY = be->GetPosY(); + auto newZ = m_Size.x - be->GetPosX() - 1; + auto newIdx = newX + newZ * m_Size.z + newY * m_Size.x * m_Size.z; + be->SetPos(newX, newY, newZ); + m_BlockEntities->insert({newIdx, std::move(be)}); + } + } + std::swap(m_Size.x, m_Size.z); } @@ -1099,6 +1204,23 @@ void cBlockArea::RotateCW(void) delete[] NewTypes; NewTypes = nullptr; delete[] NewMetas; NewMetas = nullptr; + // Rotate the BlockEntities: + if (HasBlockEntities()) + { + cBlockEntities oldBE; + std::swap(oldBE, *m_BlockEntities); + for (const auto & keyPair: oldBE) + { + auto & be = keyPair.second; + auto newX = m_Size.z - be->GetPosZ() - 1; + auto newY = be->GetPosY(); + auto newZ = be->GetPosX(); + auto newIdx = newX + newZ * m_Size.z + newY * m_Size.x * m_Size.z; + be->SetPos(newX, newY, newZ); + m_BlockEntities->insert({newIdx, std::move(be)}); + } + } + std::swap(m_Size.x, m_Size.z); } @@ -1140,6 +1262,23 @@ void cBlockArea::MirrorXY(void) } // for x } // for z } // for y + + // Mirror the BlockEntities: + if (HasBlockEntities()) + { + cBlockEntities oldBE; + std::swap(oldBE, *m_BlockEntities); + for (const auto & keyPair: oldBE) + { + auto & be = keyPair.second; + auto newX = be->GetPosX(); + auto newY = be->GetPosY(); + auto newZ = MaxZ - be->GetPosZ(); + auto newIdx = MakeIndex(newX, newY, newZ); + be->SetPos(newX, newY, newZ); + m_BlockEntities->insert({newIdx, std::move(be)}); + } + } } @@ -1180,6 +1319,23 @@ void cBlockArea::MirrorXZ(void) } // for x } // for z } // for y + + // Mirror the BlockEntities: + if (HasBlockEntities()) + { + cBlockEntities oldBE; + std::swap(oldBE, *m_BlockEntities); + for (const auto & keyPair: oldBE) + { + auto & be = keyPair.second; + auto newX = be->GetPosX(); + auto newY = MaxY - be->GetPosY(); + auto newZ = be->GetPosZ(); + auto newIdx = MakeIndex(newX, newY, newZ); + be->SetPos(newX, newY, newZ); + m_BlockEntities->insert({newIdx, std::move(be)}); + } + } } @@ -1220,6 +1376,23 @@ void cBlockArea::MirrorYZ(void) } // for x } // for z } // for y + + // Mirror the BlockEntities: + if (HasBlockEntities()) + { + cBlockEntities oldBE; + std::swap(oldBE, *m_BlockEntities); + for (const auto & keyPair: oldBE) + { + auto & be = keyPair.second; + auto newX = MaxX - be->GetPosX(); + auto newY = be->GetPosY(); + auto newZ = be->GetPosZ(); + auto newIdx = MakeIndex(newX, newY, newZ); + be->SetPos(newX, newY, newZ); + m_BlockEntities->insert({newIdx, std::move(be)}); + } + } } @@ -1264,6 +1437,24 @@ void cBlockArea::RotateCCWNoMeta(void) std::swap(m_BlockMetas, NewMetas); delete[] NewMetas; NewMetas = nullptr; } + + // Rotate the BlockEntities: + if (HasBlockEntities()) + { + cBlockEntities oldBE; + std::swap(oldBE, *m_BlockEntities); + for (const auto & keyPair: oldBE) + { + auto & be = keyPair.second; + auto newX = be->GetPosZ(); + auto newY = be->GetPosY(); + auto newZ = m_Size.x - be->GetPosX() - 1; + auto newIdx = newX + newZ * m_Size.z + newY * m_Size.x * m_Size.z; + be->SetPos(newX, newY, newZ); + m_BlockEntities->insert({newIdx, std::move(be)}); + } + } + std::swap(m_Size.x, m_Size.z); } @@ -1309,6 +1500,24 @@ void cBlockArea::RotateCWNoMeta(void) std::swap(m_BlockMetas, NewMetas); delete[] NewMetas; NewMetas = nullptr; } + + // Rotate the BlockEntities: + if (HasBlockEntities()) + { + cBlockEntities oldBE; + std::swap(oldBE, *m_BlockEntities); + for (const auto & keyPair: oldBE) + { + auto & be = keyPair.second; + auto newX = m_Size.z - be->GetPosZ() - 1; + auto newY = be->GetPosY(); + auto newZ = be->GetPosX(); + auto newIdx = newX + newZ * m_Size.z + newY * m_Size.x * m_Size.z; + be->SetPos(newX, newY, newZ); + m_BlockEntities->insert({newIdx, std::move(be)}); + } + } + std::swap(m_Size.x, m_Size.z); } @@ -1347,6 +1556,23 @@ void cBlockArea::MirrorXYNoMeta(void) } // for z } // for y } // if (HasBlockMetas) + + // Mirror the BlockEntities: + if (HasBlockEntities()) + { + cBlockEntities oldBE; + std::swap(oldBE, *m_BlockEntities); + for (const auto & keyPair: oldBE) + { + auto & be = keyPair.second; + auto newX = be->GetPosX(); + auto newY = be->GetPosY(); + auto newZ = MaxZ - be->GetPosZ(); + auto newIdx = MakeIndex(newX, newY, newZ); + be->SetPos(newX, newY, newZ); + m_BlockEntities->insert({newIdx, std::move(be)}); + } + } } @@ -1384,6 +1610,23 @@ void cBlockArea::MirrorXZNoMeta(void) } // for z } // for y } // if (HasBlockMetas) + + // Mirror the BlockEntities: + if (HasBlockEntities()) + { + cBlockEntities oldBE; + std::swap(oldBE, *m_BlockEntities); + for (const auto & keyPair: oldBE) + { + auto & be = keyPair.second; + auto newX = be->GetPosX(); + auto newY = MaxY - be->GetPosY(); + auto newZ = be->GetPosZ(); + auto newIdx = MakeIndex(newX, newY, newZ); + be->SetPos(newX, newY, newZ); + m_BlockEntities->insert({newIdx, std::move(be)}); + } + } } @@ -1421,6 +1664,23 @@ void cBlockArea::MirrorYZNoMeta(void) } // for z } // for y } // if (HasBlockMetas) + + // Mirror the BlockEntities: + if (HasBlockEntities()) + { + cBlockEntities oldBE; + std::swap(oldBE, *m_BlockEntities); + for (const auto & keyPair: oldBE) + { + auto & be = keyPair.second; + auto newX = MaxX - be->GetPosX(); + auto newY = be->GetPosY(); + auto newZ = be->GetPosZ(); + auto newIdx = MakeIndex(newX, newY, newZ); + be->SetPos(newX, newY, newZ); + m_BlockEntities->insert({newIdx, std::move(be)}); + } + } } @@ -1429,12 +1689,29 @@ void cBlockArea::MirrorYZNoMeta(void) void cBlockArea::SetRelBlockType(int a_RelX, int a_RelY, int a_RelZ, BLOCKTYPE a_BlockType) { - if (m_BlockTypes == nullptr) + ASSERT(m_BlockTypes != nullptr); + auto idx = MakeIndex(a_RelX, a_RelY, a_RelZ); + m_BlockTypes[idx] = a_BlockType; + + // Update the block entities, if appropriate: + if (HasBlockEntities()) { - LOGWARNING("cBlockArea: BlockTypes have not been read!"); - return; + auto itr = m_BlockEntities->find(idx); + if (itr != m_BlockEntities->end()) + { + if (itr->second->GetBlockType() == a_BlockType) + { + // The block entity is for the same block type, keep the current one + return; + } + m_BlockEntities->erase(itr); + } + if (cBlockEntity::IsBlockEntityBlockType(a_BlockType)) + { + NIBBLETYPE meta = HasBlockMetas() ? m_BlockMetas[idx] : 0; + m_BlockEntities->insert({idx, cBlockEntity::CreateByBlockType(a_BlockType, meta, a_RelX, a_RelY, a_RelZ)}); + } } - m_BlockTypes[MakeIndex(a_RelX, a_RelY, a_RelZ)] = a_BlockType; } @@ -1609,6 +1886,25 @@ void cBlockArea::SetRelBlockTypeMeta(int a_RelX, int a_RelY, int a_RelZ, B { m_BlockMetas[idx] = a_BlockMeta; } + + // Update the block entities, if appropriate: + if (HasBlockEntities()) + { + auto itr = m_BlockEntities->find(idx); + if (itr != m_BlockEntities->end()) + { + if (itr->second->GetBlockType() == a_BlockType) + { + // The block entity is for the same block type, keep the current one + return; + } + m_BlockEntities->erase(itr); + } + if (cBlockEntity::IsBlockEntityBlockType(a_BlockType)) + { + m_BlockEntities->insert({idx, cBlockEntity::CreateByBlockType(a_BlockType, a_BlockMeta, a_RelX, a_RelY, a_RelZ)}); + } + } } @@ -1828,6 +2124,10 @@ int cBlockArea::GetDataTypes(void) const { res |= baSkyLight; } + if (m_BlockEntities != nullptr) + { + res |= baBlockEntities; + } return res; } @@ -1838,8 +2138,9 @@ int cBlockArea::GetDataTypes(void) const bool cBlockArea::SetSize(int a_SizeX, int a_SizeY, int a_SizeZ, int a_DataTypes) { ASSERT(m_BlockTypes == nullptr); // Has been cleared + ASSERT(IsValidDataTypeCombination(a_DataTypes)); - if (a_DataTypes & baTypes) + if ((a_DataTypes & baTypes) != 0) { m_BlockTypes = new BLOCKTYPE[a_SizeX * a_SizeY * a_SizeZ]; if (m_BlockTypes == nullptr) @@ -1847,7 +2148,7 @@ bool cBlockArea::SetSize(int a_SizeX, int a_SizeY, int a_SizeZ, int a_DataTypes) return false; } } - if (a_DataTypes & baMetas) + if ((a_DataTypes & baMetas) != 0) { m_BlockMetas = new NIBBLETYPE[a_SizeX * a_SizeY * a_SizeZ]; if (m_BlockMetas == nullptr) @@ -1857,7 +2158,7 @@ bool cBlockArea::SetSize(int a_SizeX, int a_SizeY, int a_SizeZ, int a_DataTypes) return false; } } - if (a_DataTypes & baLight) + if ((a_DataTypes & baLight) != 0) { m_BlockLight = new NIBBLETYPE[a_SizeX * a_SizeY * a_SizeZ]; if (m_BlockLight == nullptr) @@ -1869,7 +2170,7 @@ bool cBlockArea::SetSize(int a_SizeX, int a_SizeY, int a_SizeZ, int a_DataTypes) return false; } } - if (a_DataTypes & baSkyLight) + if ((a_DataTypes & baSkyLight) != 0) { m_BlockSkyLight = new NIBBLETYPE[a_SizeX * a_SizeY * a_SizeZ]; if (m_BlockSkyLight == nullptr) @@ -1883,6 +2184,22 @@ bool cBlockArea::SetSize(int a_SizeX, int a_SizeY, int a_SizeZ, int a_DataTypes) return false; } } + if ((a_DataTypes & baBlockEntities) != 0) + { + m_BlockEntities = cpp14::make_unique(); + if (m_BlockEntities == nullptr) + { + delete[] m_BlockSkyLight; + m_BlockSkyLight = nullptr; + delete[] m_BlockLight; + m_BlockLight = nullptr; + delete[] m_BlockMetas; + m_BlockMetas = nullptr; + delete[] m_BlockTypes; + m_BlockTypes = nullptr; + return false; + } + } m_Size.Set(a_SizeX, a_SizeY, a_SizeZ); return true; } @@ -1907,6 +2224,55 @@ int cBlockArea::MakeIndex(int a_RelX, int a_RelY, int a_RelZ) const +bool cBlockArea::DoWithBlockEntityRelAt(int a_RelX, int a_RelY, int a_RelZ, cItemCallback & a_Callback) +{ + ASSERT(IsValidRelCoords(a_RelX, a_RelY, a_RelZ)); + if (!HasBlockEntities()) + { + return false; + } + auto idx = MakeIndex(a_RelX, a_RelY, a_RelZ); + auto itr = m_BlockEntities->find(idx); + if (itr == m_BlockEntities->end()) + { + return false; + } + return a_Callback.Item(itr->second); +} + + + + + +bool cBlockArea::DoWithBlockEntityAt(int a_BlockX, int a_BlockY, int a_BlockZ, cItemCallback & a_Callback) +{ + return DoWithBlockEntityRelAt(a_BlockX - m_Origin.x, a_BlockY - m_Origin.y, a_BlockZ - m_Origin.z, a_Callback); +} + + + + + +bool cBlockArea::ForEachBlockEntity(cItemCallback & a_Callback) +{ + if (!HasBlockEntities()) + { + return true; + } + for (auto & keyPair: *m_BlockEntities) + { + if (a_Callback.Item(keyPair.second)) + { + return false; + } + } + return true; +} + + + + + void cBlockArea::SetRelNibble(int a_RelX, int a_RelY, int a_RelZ, NIBBLETYPE a_Value, NIBBLETYPE * a_Array) { if (a_Array == nullptr) @@ -1954,257 +2320,33 @@ NIBBLETYPE cBlockArea::GetNibble(int a_BlockX, int a_BlockY, int a_BlockZ, NIBBL -//////////////////////////////////////////////////////////////////////////////// -// cBlockArea::cChunkReader: - -cBlockArea::cChunkReader::cChunkReader(cBlockArea & a_Area) : - m_Area(a_Area), - m_Origin(a_Area.m_Origin.x, a_Area.m_Origin.y, a_Area.m_Origin.z), - m_CurrentChunkX(0), - m_CurrentChunkZ(0) +void cBlockArea::CropBlockTypes(int a_AddMinX, int a_SubMaxX, int a_AddMinY, int a_SubMaxY, int a_AddMinZ, int a_SubMaxZ) { + int NewSizeX = GetSizeX() - a_AddMinX - a_SubMaxX; + int NewSizeY = GetSizeY() - a_AddMinY - a_SubMaxY; + int NewSizeZ = GetSizeZ() - a_AddMinZ - a_SubMaxZ; + BLOCKTYPE * NewBlockTypes = new BLOCKTYPE[NewSizeX * NewSizeY * NewSizeZ]; + int idx = 0; + for (int y = 0; y < NewSizeY; y++) + { + for (int z = 0; z < NewSizeZ; z++) + { + for (int x = 0; x < NewSizeX; x++) + { + int OldIndex = MakeIndex(x + a_AddMinX, y + a_AddMinY, z + a_AddMinZ); + NewBlockTypes[idx++] = m_BlockTypes[OldIndex]; + } // for x + } // for z + } // for y + delete m_BlockTypes; + m_BlockTypes = NewBlockTypes; } -void cBlockArea::cChunkReader::CopyNibbles(NIBBLETYPE * a_AreaDst, const NIBBLETYPE * a_ChunkSrc) -{ - int SizeY = m_Area.m_Size.y; - int MinY = m_Origin.y; - - // SizeX, SizeZ are the dmensions of the block data to copy from the current chunk (size of the geometric union) - // OffX, OffZ are the offsets of the current chunk data from the area origin - // BaseX, BaseZ are the offsets of the area data within the current chunk from the chunk borders - int SizeX = cChunkDef::Width; - int SizeZ = cChunkDef::Width; - int OffX, OffZ; - int BaseX, BaseZ; - OffX = m_CurrentChunkX * cChunkDef::Width - m_Origin.x; - if (OffX < 0) - { - BaseX = -OffX; - SizeX += OffX; // SizeX is decreased, OffX is negative - OffX = 0; - } - else - { - BaseX = 0; - } - OffZ = m_CurrentChunkZ * cChunkDef::Width - m_Origin.z; - if (OffZ < 0) - { - BaseZ = -OffZ; - SizeZ += OffZ; // SizeZ is decreased, OffZ is negative - OffZ = 0; - } - else - { - BaseZ = 0; - } - // If the chunk extends beyond the area in the X or Z axis, cut off the Size: - if ((m_CurrentChunkX + 1) * cChunkDef::Width > m_Origin.x + m_Area.m_Size.x) - { - SizeX -= (m_CurrentChunkX + 1) * cChunkDef::Width - (m_Origin.x + m_Area.m_Size.x); - } - if ((m_CurrentChunkZ + 1) * cChunkDef::Width > m_Origin.z + m_Area.m_Size.z) - { - SizeZ -= (m_CurrentChunkZ + 1) * cChunkDef::Width - (m_Origin.z + m_Area.m_Size.z); - } - - for (int y = 0; y < SizeY; y++) - { - int ChunkY = MinY + y; - int AreaY = y; - for (int z = 0; z < SizeZ; z++) - { - int ChunkZ = BaseZ + z; - int AreaZ = OffZ + z; - for (int x = 0; x < SizeX; x++) - { - int ChunkX = BaseX + x; - int AreaX = OffX + x; - a_AreaDst[m_Area.MakeIndex(AreaX, AreaY, AreaZ)] = cChunkDef::GetNibble(a_ChunkSrc, ChunkX, ChunkY, ChunkZ); - } // for x - } // for z - } // for y -} - - - - - -bool cBlockArea::cChunkReader::Coords(int a_ChunkX, int a_ChunkZ) -{ - m_CurrentChunkX = a_ChunkX; - m_CurrentChunkZ = a_ChunkZ; - return true; -} - - - - - -void cBlockArea::cChunkReader::ChunkData(const cChunkData & a_BlockBuffer) -{ - int SizeY = m_Area.m_Size.y; - int MinY = m_Origin.y; - - // SizeX, SizeZ are the dimensions of the block data to copy from the current chunk (size of the geometric union) - // OffX, OffZ are the offsets of the current chunk data from the area origin - // BaseX, BaseZ are the offsets of the area data within the current chunk from the chunk borders - int SizeX = cChunkDef::Width; - int SizeZ = cChunkDef::Width; - int OffX, OffZ; - int BaseX, BaseZ; - OffX = m_CurrentChunkX * cChunkDef::Width - m_Origin.x; - if (OffX < 0) - { - BaseX = -OffX; - SizeX += OffX; // SizeX is decreased, OffX is negative - OffX = 0; - } - else - { - BaseX = 0; - } - OffZ = m_CurrentChunkZ * cChunkDef::Width - m_Origin.z; - if (OffZ < 0) - { - BaseZ = -OffZ; - SizeZ += OffZ; // SizeZ is decreased, OffZ is negative - OffZ = 0; - } - else - { - BaseZ = 0; - } - // If the chunk extends beyond the area in the X or Z axis, cut off the Size: - if ((m_CurrentChunkX + 1) * cChunkDef::Width > m_Origin.x + m_Area.m_Size.x) - { - SizeX -= (m_CurrentChunkX + 1) * cChunkDef::Width - (m_Origin.x + m_Area.m_Size.x); - } - if ((m_CurrentChunkZ + 1) * cChunkDef::Width > m_Origin.z + m_Area.m_Size.z) - { - SizeZ -= (m_CurrentChunkZ + 1) * cChunkDef::Width - (m_Origin.z + m_Area.m_Size.z); - } - - // Copy the blocktypes: - if (m_Area.m_BlockTypes != nullptr) - { - for (int y = 0; y < SizeY; y++) - { - int InChunkY = MinY + y; - int AreaY = y; - for (int z = 0; z < SizeZ; z++) - { - int InChunkZ = BaseZ + z; - int AreaZ = OffZ + z; - for (int x = 0; x < SizeX; x++) - { - int InChunkX = BaseX + x; - int AreaX = OffX + x; - m_Area.m_BlockTypes[m_Area.MakeIndex(AreaX, AreaY, AreaZ)] = a_BlockBuffer.GetBlock(InChunkX, InChunkY, InChunkZ); - } // for x - } // for z - } // for y - } - - // Copy the block metas: - if (m_Area.m_BlockMetas != nullptr) - { - for (int y = 0; y < SizeY; y++) - { - int InChunkY = MinY + y; - int AreaY = y; - for (int z = 0; z < SizeZ; z++) - { - int InChunkZ = BaseZ + z; - int AreaZ = OffZ + z; - for (int x = 0; x < SizeX; x++) - { - int InChunkX = BaseX + x; - int AreaX = OffX + x; - m_Area.m_BlockMetas[m_Area.MakeIndex(AreaX, AreaY, AreaZ)] = a_BlockBuffer.GetMeta(InChunkX, InChunkY, InChunkZ); - } // for x - } // for z - } // for y - } - - // Copy the blocklight: - if (m_Area.m_BlockLight != nullptr) - { - for (int y = 0; y < SizeY; y++) - { - int InChunkY = MinY + y; - int AreaY = y; - for (int z = 0; z < SizeZ; z++) - { - int InChunkZ = BaseZ + z; - int AreaZ = OffZ + z; - for (int x = 0; x < SizeX; x++) - { - int InChunkX = BaseX + x; - int AreaX = OffX + x; - m_Area.m_BlockLight[m_Area.MakeIndex(AreaX, AreaY, AreaZ)] = a_BlockBuffer.GetBlockLight(InChunkX, InChunkY, InChunkZ); - } // for x - } // for z - } // for y - } - - // Copy the skylight: - if (m_Area.m_BlockSkyLight != nullptr) - { - for (int y = 0; y < SizeY; y++) - { - int InChunkY = MinY + y; - int AreaY = y; - for (int z = 0; z < SizeZ; z++) - { - int InChunkZ = BaseZ + z; - int AreaZ = OffZ + z; - for (int x = 0; x < SizeX; x++) - { - int InChunkX = BaseX + x; - int AreaX = OffX + x; - m_Area.m_BlockSkyLight[m_Area.MakeIndex(AreaX, AreaY, AreaZ)] = a_BlockBuffer.GetSkyLight(InChunkX, InChunkY, InChunkZ); - } // for x - } // for z - } // for y - } -} - - - - -void cBlockArea::CropBlockTypes(int a_AddMinX, int a_SubMaxX, int a_AddMinY, int a_SubMaxY, int a_AddMinZ, int a_SubMaxZ) -{ - int NewSizeX = GetSizeX() - a_AddMinX - a_SubMaxX; - int NewSizeY = GetSizeY() - a_AddMinY - a_SubMaxY; - int NewSizeZ = GetSizeZ() - a_AddMinZ - a_SubMaxZ; - BLOCKTYPE * NewBlockTypes = new BLOCKTYPE[NewSizeX * NewSizeY * NewSizeZ]; - int idx = 0; - for (int y = 0; y < NewSizeY; y++) - { - for (int z = 0; z < NewSizeZ; z++) - { - for (int x = 0; x < NewSizeX; x++) - { - int OldIndex = MakeIndex(x + a_AddMinX, y + a_AddMinY, z + a_AddMinZ); - NewBlockTypes[idx++] = m_BlockTypes[OldIndex]; - } // for x - } // for z - } // for y - delete m_BlockTypes; - m_BlockTypes = NewBlockTypes; -} - - - - - -void cBlockArea::CropNibbles(NIBBLEARRAY & a_Array, int a_AddMinX, int a_SubMaxX, int a_AddMinY, int a_SubMaxY, int a_AddMinZ, int a_SubMaxZ) +void cBlockArea::CropNibbles(NIBBLEARRAY & a_Array, int a_AddMinX, int a_SubMaxX, int a_AddMinY, int a_SubMaxY, int a_AddMinZ, int a_SubMaxZ) { int NewSizeX = GetSizeX() - a_AddMinX - a_SubMaxX; int NewSizeY = GetSizeY() - a_AddMinY - a_SubMaxY; @@ -2295,6 +2437,11 @@ void cBlockArea::RelSetData( NIBBLETYPE a_BlockLight, NIBBLETYPE a_BlockSkyLight ) { + if (!IsValidCoords(a_RelX, a_RelY, a_RelZ)) + { + return; + } + int Index = MakeIndex(a_RelX, a_RelY, a_RelZ); if ((a_DataTypes & baTypes) != 0) { @@ -2312,6 +2459,27 @@ void cBlockArea::RelSetData( { m_BlockSkyLight[Index] = a_BlockSkyLight; } + + // Update the block entities, if appropriate: + if (HasBlockEntities()) + { + auto itr = m_BlockEntities->find(Index); + if (itr != m_BlockEntities->end()) + { + if (itr->second->GetBlockType() == a_BlockType) + { + // The block entity is for the same block type, keep the current one + return; + } + // The block entity is for a different block type, remove it: + m_BlockEntities->erase(itr); + } + if (cBlockEntity::IsBlockEntityBlockType(a_BlockType)) + { + // The block type should have a block entity attached to it, create an empty one: + m_BlockEntities->insert({Index, cBlockEntity::CreateByBlockType(a_BlockType, a_BlockMeta, a_RelX, a_RelY, a_RelZ)}); + } + } } @@ -2354,7 +2522,7 @@ void cBlockArea::MergeByStrategy(const cBlockArea & a_Src, int a_RelX, int a_Rel a_Src.GetSizeX(), a_Src.GetSizeY(), a_Src.GetSizeZ(), m_Size.x, m_Size.y, m_Size.z ); - return; + break; } // case msOverwrite case cBlockArea::msFillAir: @@ -2368,7 +2536,7 @@ void cBlockArea::MergeByStrategy(const cBlockArea & a_Src, int a_RelX, int a_Rel a_Src.GetSizeX(), a_Src.GetSizeY(), a_Src.GetSizeZ(), m_Size.x, m_Size.y, m_Size.z ); - return; + break; } // case msFillAir case cBlockArea::msImprint: @@ -2382,7 +2550,7 @@ void cBlockArea::MergeByStrategy(const cBlockArea & a_Src, int a_RelX, int a_Rel a_Src.GetSizeX(), a_Src.GetSizeY(), a_Src.GetSizeZ(), m_Size.x, m_Size.y, m_Size.z ); - return; + break; } // case msImprint case cBlockArea::msLake: @@ -2396,7 +2564,7 @@ void cBlockArea::MergeByStrategy(const cBlockArea & a_Src, int a_RelX, int a_Rel a_Src.GetSizeX(), a_Src.GetSizeY(), a_Src.GetSizeZ(), m_Size.x, m_Size.y, m_Size.z ); - return; + break; } // case msLake case cBlockArea::msSpongePrint: @@ -2410,7 +2578,7 @@ void cBlockArea::MergeByStrategy(const cBlockArea & a_Src, int a_RelX, int a_Rel a_Src.GetSizeX(), a_Src.GetSizeY(), a_Src.GetSizeZ(), m_Size.x, m_Size.y, m_Size.z ); - return; + break; } // case msSpongePrint case cBlockArea::msDifference: @@ -2424,7 +2592,7 @@ void cBlockArea::MergeByStrategy(const cBlockArea & a_Src, int a_RelX, int a_Rel a_Src.GetSizeX(), a_Src.GetSizeY(), a_Src.GetSizeZ(), m_Size.x, m_Size.y, m_Size.z ); - return; + break; } // case msDifference case cBlockArea::msSimpleCompare: @@ -2438,7 +2606,7 @@ void cBlockArea::MergeByStrategy(const cBlockArea & a_Src, int a_RelX, int a_Rel a_Src.GetSizeX(), a_Src.GetSizeY(), a_Src.GetSizeZ(), m_Size.x, m_Size.y, m_Size.z ); - return; + break; } // case msSimpleCompare case cBlockArea::msMask: @@ -2452,14 +2620,407 @@ void cBlockArea::MergeByStrategy(const cBlockArea & a_Src, int a_RelX, int a_Rel a_Src.GetSizeX(), a_Src.GetSizeY(), a_Src.GetSizeZ(), m_Size.x, m_Size.y, m_Size.z ); - return; + break; } // case msMask + + #ifndef __clang__ // Clang complains about a default case in a switch with all cases covered + default: + { + LOGWARNING("Unknown block area merge strategy: %d", a_Strategy); + ASSERT(!"Unknown block area merge strategy"); + return; + } + #endif } // switch (a_Strategy) - LOGWARNING("Unknown block area merge strategy: %d", a_Strategy); - ASSERT(!"Unknown block area merge strategy"); - return; + if (HasBlockEntities()) + { + if (a_Src.HasBlockEntities()) + { + MergeBlockEntities(a_RelX, a_RelY, a_RelZ, a_Src); + } + else + { + RescanBlockEntities(); + } + } +} + + + + + +void cBlockArea::ClearBlockEntities(cBlockEntities & a_BlockEntities) +{ + for (auto & keyPair: a_BlockEntities) + { + delete keyPair.second; + } + a_BlockEntities.clear(); } + + +void cBlockArea::MergeBlockEntities(int a_RelX, int a_RelY, int a_RelZ, const cBlockArea & a_Src) +{ + // Only supported with both BlockEntities and BlockTypes (caller should check): + ASSERT(HasBlockTypes()); + ASSERT(HasBlockEntities()); + ASSERT(a_Src.HasBlockTypes()); + ASSERT(a_Src.HasBlockEntities()); + + // Remove block entities that no longer match the block at their coords: + RemoveNonMatchingBlockEntities(); + + // Clone BEs from a_Src wherever a BE is missing: + for (int y = 0; y < m_Size.y; ++y) for (int z = 0; z < m_Size.z; ++z) for (int x = 0; x < m_Size.x; ++x) + { + auto idx = MakeIndex(x, y, z); + auto type = m_BlockTypes[idx]; + if (!cBlockEntity::IsBlockEntityBlockType(type)) + { + continue; + } + + // This block should have a block entity, check that there is one: + auto itr = m_BlockEntities->find(idx); + if (itr != m_BlockEntities->end()) + { + // There is one already + continue; + } + + // Copy a BE from a_Src, if it exists there: + auto srcX = x + a_RelX; + auto srcY = y + a_RelY; + auto srcZ = z + a_RelZ; + if (a_Src.IsValidRelCoords(srcX, srcY, srcZ)) + { + auto srcIdx = a_Src.MakeIndex(srcX, srcY, srcZ); + auto itrSrc = a_Src.m_BlockEntities->find(srcIdx); + if (itrSrc == a_Src.m_BlockEntities->end()) + { + m_BlockEntities->insert({idx, itrSrc->second->Clone(x, y, z)}); + continue; + } + } + // No BE found in a_Src, insert a new empty one: + NIBBLETYPE meta = HasBlockMetas() ? m_BlockMetas[idx] : 0; + m_BlockEntities->insert({idx, cBlockEntity::CreateByBlockType(type, meta, x, y, z)}); + } // for x, z, y +} + + + + + +void cBlockArea::RescanBlockEntities(void) +{ + // Only supported with both BlockEntities and BlockTypes + if (!HasBlockEntities() || !HasBlockTypes()) + { + return; + } + + // Remove block entities that no longer match the block at their coords: + RemoveNonMatchingBlockEntities(); + + // Add block entities for all block types that should have a BE assigned to them: + for (int y = 0; y < m_Size.y; ++y) for (int z = 0; z < m_Size.z; ++z) for (int x = 0; x < m_Size.x; ++x) + { + auto idx = MakeIndex(x, y, z); + auto type = m_BlockTypes[idx]; + if (!cBlockEntity::IsBlockEntityBlockType(type)) + { + continue; + } + // This block should have a block entity, check that there is one: + auto itr = m_BlockEntities->find(idx); + if (itr != m_BlockEntities->end()) + { + continue; + } + // Create a new BE for this block: + NIBBLETYPE meta = HasBlockMetas() ? m_BlockMetas[idx] : 0; + m_BlockEntities->insert({idx, cBlockEntity::CreateByBlockType(type, meta, x, y, z)}); + } // for x, z, y +} + + + + + +void cBlockArea::RemoveNonMatchingBlockEntities(void) +{ + // Only supported with both BlockEntities and BlockTypes: + ASSERT(HasBlockTypes()); + ASSERT(HasBlockEntities()); + + cBlockEntities oldBE; + std::swap(oldBE, *m_BlockEntities); + for (auto & keyPair: oldBE) + { + auto type = m_BlockTypes[keyPair.first]; + if (type == keyPair.second->GetBlockType()) + { + m_BlockEntities->insert({keyPair.first, std::move(keyPair.second)}); + } + else + { + delete keyPair.second; + } + } +} + + + + + +//////////////////////////////////////////////////////////////////////////////// +// cBlockArea::cChunkReader: + +cBlockArea::cChunkReader::cChunkReader(cBlockArea & a_Area) : + m_Area(a_Area), + m_AreaBounds(cCuboid(a_Area.GetOrigin(), a_Area.GetOrigin() + a_Area.GetSize())), + m_Origin(a_Area.m_Origin.x, a_Area.m_Origin.y, a_Area.m_Origin.z), + m_CurrentChunkX(0), + m_CurrentChunkZ(0) +{ +} + + + + + +void cBlockArea::cChunkReader::CopyNibbles(NIBBLETYPE * a_AreaDst, const NIBBLETYPE * a_ChunkSrc) +{ + int SizeY = m_Area.m_Size.y; + int MinY = m_Origin.y; + + // SizeX, SizeZ are the dmensions of the block data to copy from the current chunk (size of the geometric union) + // OffX, OffZ are the offsets of the current chunk data from the area origin + // BaseX, BaseZ are the offsets of the area data within the current chunk from the chunk borders + int SizeX = cChunkDef::Width; + int SizeZ = cChunkDef::Width; + int OffX, OffZ; + int BaseX, BaseZ; + OffX = m_CurrentChunkX * cChunkDef::Width - m_Origin.x; + if (OffX < 0) + { + BaseX = -OffX; + SizeX += OffX; // SizeX is decreased, OffX is negative + OffX = 0; + } + else + { + BaseX = 0; + } + OffZ = m_CurrentChunkZ * cChunkDef::Width - m_Origin.z; + if (OffZ < 0) + { + BaseZ = -OffZ; + SizeZ += OffZ; // SizeZ is decreased, OffZ is negative + OffZ = 0; + } + else + { + BaseZ = 0; + } + // If the chunk extends beyond the area in the X or Z axis, cut off the Size: + if ((m_CurrentChunkX + 1) * cChunkDef::Width > m_Origin.x + m_Area.m_Size.x) + { + SizeX -= (m_CurrentChunkX + 1) * cChunkDef::Width - (m_Origin.x + m_Area.m_Size.x); + } + if ((m_CurrentChunkZ + 1) * cChunkDef::Width > m_Origin.z + m_Area.m_Size.z) + { + SizeZ -= (m_CurrentChunkZ + 1) * cChunkDef::Width - (m_Origin.z + m_Area.m_Size.z); + } + + for (int y = 0; y < SizeY; y++) + { + int ChunkY = MinY + y; + int AreaY = y; + for (int z = 0; z < SizeZ; z++) + { + int ChunkZ = BaseZ + z; + int AreaZ = OffZ + z; + for (int x = 0; x < SizeX; x++) + { + int ChunkX = BaseX + x; + int AreaX = OffX + x; + a_AreaDst[m_Area.MakeIndex(AreaX, AreaY, AreaZ)] = cChunkDef::GetNibble(a_ChunkSrc, ChunkX, ChunkY, ChunkZ); + } // for x + } // for z + } // for y +} + + + + + +bool cBlockArea::cChunkReader::Coords(int a_ChunkX, int a_ChunkZ) +{ + m_CurrentChunkX = a_ChunkX; + m_CurrentChunkZ = a_ChunkZ; + return true; +} + + + + + +void cBlockArea::cChunkReader::ChunkData(const cChunkData & a_BlockBuffer) +{ + int SizeY = m_Area.m_Size.y; + int MinY = m_Origin.y; + + // SizeX, SizeZ are the dimensions of the block data to copy from the current chunk (size of the geometric union) + // OffX, OffZ are the offsets of the current chunk data from the area origin + // BaseX, BaseZ are the offsets of the area data within the current chunk from the chunk borders + int SizeX = cChunkDef::Width; + int SizeZ = cChunkDef::Width; + int OffX, OffZ; + int BaseX, BaseZ; + OffX = m_CurrentChunkX * cChunkDef::Width - m_Origin.x; + if (OffX < 0) + { + BaseX = -OffX; + SizeX += OffX; // SizeX is decreased, OffX is negative + OffX = 0; + } + else + { + BaseX = 0; + } + OffZ = m_CurrentChunkZ * cChunkDef::Width - m_Origin.z; + if (OffZ < 0) + { + BaseZ = -OffZ; + SizeZ += OffZ; // SizeZ is decreased, OffZ is negative + OffZ = 0; + } + else + { + BaseZ = 0; + } + // If the chunk extends beyond the area in the X or Z axis, cut off the Size: + if ((m_CurrentChunkX + 1) * cChunkDef::Width > m_Origin.x + m_Area.m_Size.x) + { + SizeX -= (m_CurrentChunkX + 1) * cChunkDef::Width - (m_Origin.x + m_Area.m_Size.x); + } + if ((m_CurrentChunkZ + 1) * cChunkDef::Width > m_Origin.z + m_Area.m_Size.z) + { + SizeZ -= (m_CurrentChunkZ + 1) * cChunkDef::Width - (m_Origin.z + m_Area.m_Size.z); + } + + // Copy the blocktypes: + if (m_Area.m_BlockTypes != nullptr) + { + for (int y = 0; y < SizeY; y++) + { + int InChunkY = MinY + y; + int AreaY = y; + for (int z = 0; z < SizeZ; z++) + { + int InChunkZ = BaseZ + z; + int AreaZ = OffZ + z; + for (int x = 0; x < SizeX; x++) + { + int InChunkX = BaseX + x; + int AreaX = OffX + x; + m_Area.m_BlockTypes[m_Area.MakeIndex(AreaX, AreaY, AreaZ)] = a_BlockBuffer.GetBlock(InChunkX, InChunkY, InChunkZ); + } // for x + } // for z + } // for y + } + + // Copy the block metas: + if (m_Area.m_BlockMetas != nullptr) + { + for (int y = 0; y < SizeY; y++) + { + int InChunkY = MinY + y; + int AreaY = y; + for (int z = 0; z < SizeZ; z++) + { + int InChunkZ = BaseZ + z; + int AreaZ = OffZ + z; + for (int x = 0; x < SizeX; x++) + { + int InChunkX = BaseX + x; + int AreaX = OffX + x; + m_Area.m_BlockMetas[m_Area.MakeIndex(AreaX, AreaY, AreaZ)] = a_BlockBuffer.GetMeta(InChunkX, InChunkY, InChunkZ); + } // for x + } // for z + } // for y + } + + // Copy the blocklight: + if (m_Area.m_BlockLight != nullptr) + { + for (int y = 0; y < SizeY; y++) + { + int InChunkY = MinY + y; + int AreaY = y; + for (int z = 0; z < SizeZ; z++) + { + int InChunkZ = BaseZ + z; + int AreaZ = OffZ + z; + for (int x = 0; x < SizeX; x++) + { + int InChunkX = BaseX + x; + int AreaX = OffX + x; + m_Area.m_BlockLight[m_Area.MakeIndex(AreaX, AreaY, AreaZ)] = a_BlockBuffer.GetBlockLight(InChunkX, InChunkY, InChunkZ); + } // for x + } // for z + } // for y + } + + // Copy the skylight: + if (m_Area.m_BlockSkyLight != nullptr) + { + for (int y = 0; y < SizeY; y++) + { + int InChunkY = MinY + y; + int AreaY = y; + for (int z = 0; z < SizeZ; z++) + { + int InChunkZ = BaseZ + z; + int AreaZ = OffZ + z; + for (int x = 0; x < SizeX; x++) + { + int InChunkX = BaseX + x; + int AreaX = OffX + x; + m_Area.m_BlockSkyLight[m_Area.MakeIndex(AreaX, AreaY, AreaZ)] = a_BlockBuffer.GetSkyLight(InChunkX, InChunkY, InChunkZ); + } // for x + } // for z + } // for y + } +} + + + + +void cBlockArea::cChunkReader::BlockEntity(cBlockEntity * a_BlockEntity) +{ + if (!m_Area.HasBlockEntities()) + { + return; + } + if (!m_AreaBounds.IsInside(a_BlockEntity->GetPos())) + { + return; + } + auto areaX = a_BlockEntity->GetPosX() - m_Area.m_Origin.x; + auto areaY = a_BlockEntity->GetPosY() - m_Area.m_Origin.y; + auto areaZ = a_BlockEntity->GetPosZ() - m_Area.m_Origin.z; + int Idx = cChunkDef::MakeIndex(areaX, areaY, areaZ); + m_Area.m_BlockEntities->insert({Idx, a_BlockEntity->Clone(areaX, areaY, areaZ)}); +} + + + + + diff --git a/src/BlockArea.h b/src/BlockArea.h index 2100345e4..583b998c2 100644 --- a/src/BlockArea.h +++ b/src/BlockArea.h @@ -5,6 +5,8 @@ // The object also supports writing the blockdata back into cWorld, even into other coords // NOTE: All Nibble values (meta, blocklight, skylight) are stored one-nibble-per-byte for faster access / editting! +// NOTE: Lua bindings for this object explicitly check parameter values. C++ code is expected to pass in valid params, so the functions ASSERT on invalid params. +// This includes the datatypes (must be present / valid combination), coords and sizes. @@ -15,6 +17,7 @@ #include "ForEachChunkProvider.h" #include "Vector3.h" #include "ChunkDataCallback.h" +#include "Cuboid.h" @@ -38,10 +41,12 @@ public: /** What data is to be queried (bit-mask) */ enum { - baTypes = 1, - baMetas = 2, - baLight = 4, - baSkyLight = 8, + baTypes = 1, + baMetas = 2, + baLight = 4, + baSkyLight = 8, + // baEntities = 16, // Not supported yet + baBlockEntities = 32, } ; /** The per-block strategy to use when merging another block area into this object. @@ -61,20 +66,26 @@ public: cBlockArea(void); ~cBlockArea(); + /** Returns true if the datatype combination is valid. + Invalid combinations include BlockEntities without BlockTypes. */ + static bool IsValidDataTypeCombination(int a_DataTypes); + /** Clears the data stored to reclaim memory */ void Clear(void); + // tolua_end + /** Creates a new area of the specified size and contents. Origin is set to all zeroes. - BlockTypes are set to air, block metas to zero, blocklights to zero and skylights to full light. - */ - void Create(int a_SizeX, int a_SizeY, int a_SizeZ, int a_DataTypes = baTypes | baMetas); + BlockTypes are set to air, block metas to zero, blocklights to zero and skylights to full light. */ + void Create(int a_SizeX, int a_SizeY, int a_SizeZ, int a_DataTypes = baTypes | baMetas | baBlockEntities); /** Creates a new area of the specified size and contents. Origin is set to all zeroes. - BlockTypes are set to air, block metas to zero, blocklights to zero and skylights to full light. - */ - void Create(const Vector3i & a_Size, int a_DataTypes = baTypes | baMetas); + BlockTypes are set to air, block metas to zero, blocklights to zero and skylights to full light. */ + void Create(const Vector3i & a_Size, int a_DataTypes = baTypes | baMetas | baBlockEntities); + + // tolua_begin /** Resets the origin. No other changes are made, contents are untouched. */ void SetOrigin(int a_OriginX, int a_OriginY, int a_OriginZ); @@ -82,23 +93,39 @@ public: /** Resets the origin. No other changes are made, contents are untouched. */ void SetOrigin(const Vector3i & a_Origin); + /** Returns true if the specified relative coords are within this area's coord range (0 - m_Size). */ + bool IsValidRelCoords(int a_RelX, int a_RelY, int a_RelZ) const; + + /** Returns true if the specified relative coords are within this area's coord range (0 - m_Size). */ + bool IsValidRelCoords(const Vector3i & a_RelCoords) const; + + /** Returns true if the specified coords are within this area's coord range (as indicated by m_Origin). */ + bool IsValidCoords(int a_BlockX, int a_BlockY, int a_BlockZ) const; + + /** Returns true if the specified coords are within this area's coord range (as indicated by m_Origin). */ + bool IsValidCoords(const Vector3i & a_Coords) const; + + // tolua_end + /** Reads an area of blocks specified. Returns true if successful. All coords are inclusive. */ - bool Read(cForEachChunkProvider & a_ForEachChunkProvider, int a_MinBlockX, int a_MaxBlockX, int a_MinBlockY, int a_MaxBlockY, int a_MinBlockZ, int a_MaxBlockZ, int a_DataTypes = baTypes | baMetas); + bool Read(cForEachChunkProvider & a_ForEachChunkProvider, int a_MinBlockX, int a_MaxBlockX, int a_MinBlockY, int a_MaxBlockY, int a_MinBlockZ, int a_MaxBlockZ, int a_DataTypes = baTypes | baMetas | baBlockEntities); /** Reads an area of blocks specified. Returns true if successful. The bounds are included in the read area. */ - bool Read(cForEachChunkProvider & a_ForEachChunkProvider, const cCuboid & a_Bounds, int a_DataTypes = baTypes | baMetas); + bool Read(cForEachChunkProvider & a_ForEachChunkProvider, const cCuboid & a_Bounds, int a_DataTypes = baTypes | baMetas | baBlockEntities); /** Reads an area of blocks specified. Returns true if successful. The bounds are included in the read area. */ - bool Read(cForEachChunkProvider & a_ForEachChunkProvider, const Vector3i & a_Point1, const Vector3i & a_Point2, int a_DataTypes = baTypes | baMetas); + bool Read(cForEachChunkProvider & a_ForEachChunkProvider, const Vector3i & a_Point1, const Vector3i & a_Point2, int a_DataTypes = baTypes | baMetas | baBlockEntities); // TODO: Write() is not too good an interface: if it fails, there's no way to repeat only for the parts that didn't write // A better way may be to return a list of cBlockAreas for each part that didn't succeed writing, so that the caller may try again - /** Writes the area back into cWorld at the coords specified. Returns true if successful in all chunks, false if only partially / not at all */ - bool Write(cForEachChunkProvider & a_ForEachChunkProvider, int a_MinBlockX, int a_MinBlockY, int a_MinBlockZ, int a_DataTypes = baTypes | baMetas); + /** Writes the area back into cWorld at the coords specified. Returns true if successful in all chunks, false if only partially / not at all. */ + bool Write(cForEachChunkProvider & a_ForEachChunkProvider, int a_MinBlockX, int a_MinBlockY, int a_MinBlockZ, int a_DataTypes = baTypes | baMetas | baBlockEntities); - /** Writes the area back into cWorld at the coords specified. Returns true if successful in all chunks, false if only partially / not at all */ - bool Write(cForEachChunkProvider & a_ForEachChunkProvider, const Vector3i & a_MinCoords, int a_DataTypes = baTypes | baMetas); + /** Writes the area back into cWorld at the coords specified. Returns true if successful in all chunks, false if only partially / not at all. */ + bool Write(cForEachChunkProvider & a_ForEachChunkProvider, const Vector3i & a_MinCoords, int a_DataTypes = baTypes | baMetas | baBlockEntities); + + // tolua_begin /** Copies this object's contents into the specified BlockArea. */ void CopyTo(cBlockArea & a_Into) const; @@ -117,6 +144,10 @@ public: /** Merges another block area into this one, using the specified block combinating strategy This function combines another BlockArea into the current object. + The a_RelX, a_RelY and a_RelZ parameters specify the coords of this BA where a_Src should be copied. + If both areas contain baBlockEntities, the BEs are merged (with preference of keeping this' ones) (MergeBlockEntities()). + If only this contains BEs, but a_Src doesn't, the BEs are checked after merge to remove the overwritten ones and create + the missing ones (UpdateBlockEntities()). The strategy parameter specifies how individual blocks are combined together, using the table below. | area block | result | @@ -191,6 +222,8 @@ public: /** Fills the entire block area with the specified data */ void Fill(int a_DataTypes, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta = 0, NIBBLETYPE a_BlockLight = 0, NIBBLETYPE a_BlockSkyLight = 0x0f); + // tolua_end + /** Fills a cuboid inside the block area with the specified data */ void FillRelCuboid(int a_MinRelX, int a_MaxRelX, int a_MinRelY, int a_MaxRelY, int a_MinRelZ, int a_MaxRelZ, int a_DataTypes, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta = 0, @@ -203,18 +236,20 @@ public: NIBBLETYPE a_BlockLight = 0, NIBBLETYPE a_BlockSkyLight = 0x0f ); - /** Draws a line from between two points with the specified data */ + /** Draws a line between two points with the specified data. The line endpoints needn't be valid coords inside the area. */ void RelLine(int a_RelX1, int a_RelY1, int a_RelZ1, int a_RelX2, int a_RelY2, int a_RelZ2, int a_DataTypes, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta = 0, NIBBLETYPE a_BlockLight = 0, NIBBLETYPE a_BlockSkyLight = 0x0f ); - /** Draws a line from between two points with the specified data */ + /** Draws a line between two points with the specified data. The line endpoints needn't be valid coords inside the area. */ void RelLine(const Vector3i & a_Point1, const Vector3i & a_Point2, int a_DataTypes, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta = 0, NIBBLETYPE a_BlockLight = 0, NIBBLETYPE a_BlockSkyLight = 0x0f ); + // tolua_begin + /** Rotates the entire area counter-clockwise around the Y axis */ void RotateCCW(void); @@ -245,6 +280,8 @@ public: /** Mirrors the entire area around the YZ plane, doesn't use blockhandlers for block meta */ void MirrorYZNoMeta(void); + // tolua_end + // Setters: void SetRelBlockType (int a_RelX, int a_RelY, int a_RelZ, BLOCKTYPE a_BlockType); void SetBlockType (int a_BlockX, int a_BlockY, int a_BlockZ, BLOCKTYPE a_BlockType); @@ -254,8 +291,14 @@ public: void SetBlockLight (int a_BlockX, int a_BlockY, int a_BlockZ, NIBBLETYPE a_BlockLight); void SetRelBlockSkyLight(int a_RelX, int a_RelY, int a_RelZ, NIBBLETYPE a_BlockSkyLight); void SetBlockSkyLight (int a_BlockX, int a_BlockY, int a_BlockZ, NIBBLETYPE a_BlockSkyLight); - void SetWEOffset (int a_OffsetX, int a_OffsetY, int a_OffsetZ); - void SetWEOffset (const Vector3i & a_Offset); + + // tolua_begin + + void SetWEOffset (int a_OffsetX, int a_OffsetY, int a_OffsetZ); + void SetWEOffset (const Vector3i & a_Offset); + const Vector3i & GetWEOffset (void) const {return m_WEOffset;} + + // tolua_end // Getters: BLOCKTYPE GetRelBlockType (int a_RelX, int a_RelY, int a_RelZ) const; @@ -266,21 +309,14 @@ public: NIBBLETYPE GetBlockLight (int a_BlockX, int a_BlockY, int a_BlockZ) const; NIBBLETYPE GetRelBlockSkyLight(int a_RelX, int a_RelY, int a_RelZ) const; NIBBLETYPE GetBlockSkyLight (int a_BlockX, int a_BlockY, int a_BlockZ) const; - const Vector3i & GetWEOffset (void) const {return m_WEOffset;} void SetBlockTypeMeta (int a_BlockX, int a_BlockY, int a_BlockZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta); void SetRelBlockTypeMeta(int a_RelX, int a_RelY, int a_RelZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta); - // tolua_end - - // These need manual exporting, tolua generates the binding as requiring 2 extra input params void GetBlockTypeMeta (int a_BlockX, int a_BlockY, int a_BlockZ, BLOCKTYPE & a_BlockType, NIBBLETYPE & a_BlockMeta) const; void GetRelBlockTypeMeta(int a_RelX, int a_RelY, int a_RelZ, BLOCKTYPE & a_BlockType, NIBBLETYPE & a_BlockMeta) const; - // GetSize() is already exported manually to return 3 numbers, can't auto-export const Vector3i & GetSize(void) const { return m_Size; } - - // GetOrigin() is already exported manually to return 3 numbers, can't auto-export const Vector3i & GetOrigin(void) const { return m_Origin; } // tolua_begin @@ -303,6 +339,7 @@ public: bool HasBlockMetas (void) const { return (m_BlockMetas != nullptr); } bool HasBlockLights (void) const { return (m_BlockLight != nullptr); } bool HasBlockSkyLights(void) const { return (m_BlockSkyLight != nullptr); } + bool HasBlockEntities (void) const { return (m_BlockEntities != nullptr); } /** Returns the count of blocks that are not air. Returns 0 if blocktypes not available. Block metas are ignored (if present, air with any meta is still considered air). */ @@ -333,11 +370,32 @@ public: size_t GetBlockCount(void) const { return static_cast(m_Size.x * m_Size.y * m_Size.z); } int MakeIndex(int a_RelX, int a_RelY, int a_RelZ) const; + /** Calls the callback for the block entity at the specified coords. + Returns false if there is no block entity at those coords, or the block area doesn't have baBlockEntities. + Returns the value that the callback has returned if there is a block entity. */ + bool DoWithBlockEntityRelAt(int a_RelX, int a_RelY, int a_RelZ, cItemCallback & a_Callback); + + /** Calls the callback for the block entity at the specified coords. + Returns false if there is no block entity at those coords. + Returns the value that the callback has returned if there is a block entity. */ + bool DoWithBlockEntityAt (int a_BlockX, int a_BlockY, int a_BlockZ, cItemCallback & a_Callback); + + /** Calls the callback for all the block entities. + If the callback returns true, aborts the enumeration and returns false. + If the callback returns true, continues with the next BE. + Returns true if all block entities have been enumerated (including the case when there is none or the area is without baBlockEntities). */ + bool ForEachBlockEntity(cItemCallback & a_Callback); + + /** Direct read-only access to block entities. */ + const cBlockEntities & GetBlockEntities(void) const { ASSERT(HasBlockEntities()); return *m_BlockEntities; } + + protected: + friend class cChunkDesc; friend class cSchematicFileSerializer; - class cChunkReader : + class cChunkReader: public cChunkDataCallback { public: @@ -345,6 +403,7 @@ protected: protected: cBlockArea & m_Area; + cCuboid m_AreaBounds; ///< Bounds of the whole area being read, in world coords Vector3i m_Origin; int m_CurrentChunkX; int m_CurrentChunkZ; @@ -354,6 +413,7 @@ protected: // cChunkDataCallback overrides: virtual bool Coords(int a_ChunkX, int a_ChunkZ) override; virtual void ChunkData(const cChunkData & a_BlockTypes) override; + virtual void BlockEntity(cBlockEntity * a_BlockEntity) override; } ; typedef NIBBLETYPE * NIBBLEARRAY; @@ -371,6 +431,11 @@ protected: NIBBLETYPE * m_BlockLight; // Each light value is stored as a separate byte for faster access NIBBLETYPE * m_BlockSkyLight; // Each light value is stored as a separate byte for faster access + /** The block entities contained within the area. + Only valid if the area was created / read with the baBlockEntities flag. + The block entities are owned by this object. */ + std::unique_ptr m_BlockEntities; + /** Clears the data stored and prepares a fresh new block area with the specified dimensions */ bool SetSize(int a_SizeX, int a_SizeY, int a_SizeZ, int a_DataTypes); @@ -390,7 +455,8 @@ protected: void ExpandBlockTypes(int a_SubMinX, int a_AddMaxX, int a_SubMinY, int a_AddMaxY, int a_SubMinZ, int a_AddMaxZ); void ExpandNibbles (NIBBLEARRAY & a_Array, int a_SubMinX, int a_AddMaxX, int a_SubMinY, int a_AddMaxY, int a_SubMinZ, int a_AddMaxZ); - /** Sets the specified datatypes at the specified location. */ + /** Sets the specified datatypes at the specified location. + If the coords are not valid, ignores the call (so that RelLine() can work simply). */ void RelSetData( int a_RelX, int a_RelY, int a_RelZ, int a_DataTypes, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta, @@ -399,6 +465,21 @@ protected: template void MergeByStrategy(const cBlockArea & a_Src, int a_RelX, int a_RelY, int a_RelZ, eMergeStrategy a_Strategy, const NIBBLETYPE * SrcMetas, NIBBLETYPE * DstMetas); + + /** Clears the block entities from the specified container, freeing each blockentity. */ + static void ClearBlockEntities(cBlockEntities & a_BlockEntities); + + /** Updates m_BlockEntities to remove BEs that no longer match the blocktype at their coords, and clones from a_Src the BEs that are missing. + a_RelX, a_RelY and a_RelZ are relative coords that should be added to all BEs from a_Src before checking them. + If a block should have a BE but one cannot be found in either this or a_Src, a new one is created. */ + void MergeBlockEntities(int a_RelX, int a_RelY, int a_RelZ, const cBlockArea & a_Src); + + /** Updates m_BlockEntities to remove BEs that no longer match the blocktype at their coords, and add new BEs that are missing. */ + void RescanBlockEntities(void); + + /** Removes from m_BlockEntities those BEs that no longer match the blocktype at their coords. */ + void RemoveNonMatchingBlockEntities(void); + // tolua_begin } ; // tolua_end diff --git a/src/BlockEntities/BlockEntity.cpp b/src/BlockEntities/BlockEntity.cpp index f0716dd08..f60eb5622 100644 --- a/src/BlockEntities/BlockEntity.cpp +++ b/src/BlockEntities/BlockEntity.cpp @@ -25,11 +25,57 @@ +void cBlockEntity::SetPos(int a_NewBlockX, int a_NewBlockY, int a_NewBlockZ) +{ + ASSERT(m_World == nullptr); // Cannot move block entities that represent world blocks (only use this for cBlockArea's BEs) + m_PosX = a_NewBlockX; + m_PosY = a_NewBlockY; + m_PosZ = a_NewBlockZ; +} + + + + + +bool cBlockEntity::IsBlockEntityBlockType(BLOCKTYPE a_BlockType) +{ + switch (a_BlockType) + { + case E_BLOCK_BEACON: + case E_BLOCK_BREWING_STAND: + case E_BLOCK_CHEST: + case E_BLOCK_COMMAND_BLOCK: + case E_BLOCK_DISPENSER: + case E_BLOCK_DROPPER: + case E_BLOCK_ENDER_CHEST: + case E_BLOCK_FLOWER_POT: + case E_BLOCK_FURNACE: + case E_BLOCK_HEAD: + case E_BLOCK_HOPPER: + case E_BLOCK_JUKEBOX: + case E_BLOCK_LIT_FURNACE: + case E_BLOCK_MOB_SPAWNER: + case E_BLOCK_NOTE_BLOCK: + case E_BLOCK_SIGN_POST: + case E_BLOCK_TRAPPED_CHEST: + case E_BLOCK_WALLSIGN: + { + return true; + } + } + return false; +} + + + + + cBlockEntity * cBlockEntity::CreateByBlockType(BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta, int a_BlockX, int a_BlockY, int a_BlockZ, cWorld * a_World) { switch (a_BlockType) { case E_BLOCK_BEACON: return new cBeaconEntity (a_BlockType, a_BlockMeta, a_BlockX, a_BlockY, a_BlockZ, a_World); + case E_BLOCK_BREWING_STAND: return new cBrewingstandEntity(a_BlockType, a_BlockMeta, a_BlockX, a_BlockY, a_BlockZ, a_World); case E_BLOCK_CHEST: return new cChestEntity (a_BlockType, a_BlockMeta, a_BlockX, a_BlockY, a_BlockZ, a_World); case E_BLOCK_COMMAND_BLOCK: return new cCommandBlockEntity(a_BlockType, a_BlockMeta, a_BlockX, a_BlockY, a_BlockZ, a_World); case E_BLOCK_DISPENSER: return new cDispenserEntity (a_BlockType, a_BlockMeta, a_BlockX, a_BlockY, a_BlockZ, a_World); @@ -37,16 +83,15 @@ cBlockEntity * cBlockEntity::CreateByBlockType(BLOCKTYPE a_BlockType, NIBBLETYPE case E_BLOCK_ENDER_CHEST: return new cEnderChestEntity (a_BlockType, a_BlockMeta, a_BlockX, a_BlockY, a_BlockZ, a_World); case E_BLOCK_FLOWER_POT: return new cFlowerPotEntity (a_BlockType, a_BlockMeta, a_BlockX, a_BlockY, a_BlockZ, a_World); case E_BLOCK_FURNACE: return new cFurnaceEntity (a_BlockType, a_BlockMeta, a_BlockX, a_BlockY, a_BlockZ, a_World); - case E_BLOCK_BREWING_STAND: return new cBrewingstandEntity(a_BlockType, a_BlockMeta, a_BlockX, a_BlockY, a_BlockZ, a_World); case E_BLOCK_HEAD: return new cMobHeadEntity (a_BlockType, a_BlockMeta, a_BlockX, a_BlockY, a_BlockZ, a_World); case E_BLOCK_HOPPER: return new cHopperEntity (a_BlockType, a_BlockMeta, a_BlockX, a_BlockY, a_BlockZ, a_World); - case E_BLOCK_MOB_SPAWNER: return new cMobSpawnerEntity (a_BlockType, a_BlockMeta, a_BlockX, a_BlockY, a_BlockZ, a_World); case E_BLOCK_JUKEBOX: return new cJukeboxEntity (a_BlockType, a_BlockMeta, a_BlockX, a_BlockY, a_BlockZ, a_World); case E_BLOCK_LIT_FURNACE: return new cFurnaceEntity (a_BlockType, a_BlockMeta, a_BlockX, a_BlockY, a_BlockZ, a_World); + case E_BLOCK_MOB_SPAWNER: return new cMobSpawnerEntity (a_BlockType, a_BlockMeta, a_BlockX, a_BlockY, a_BlockZ, a_World); + case E_BLOCK_NOTE_BLOCK: return new cNoteEntity (a_BlockType, a_BlockMeta, a_BlockX, a_BlockY, a_BlockZ, a_World); case E_BLOCK_SIGN_POST: return new cSignEntity (a_BlockType, a_BlockMeta, a_BlockX, a_BlockY, a_BlockZ, a_World); case E_BLOCK_TRAPPED_CHEST: return new cChestEntity (a_BlockType, a_BlockMeta, a_BlockX, a_BlockY, a_BlockZ, a_World); case E_BLOCK_WALLSIGN: return new cSignEntity (a_BlockType, a_BlockMeta, a_BlockX, a_BlockY, a_BlockZ, a_World); - case E_BLOCK_NOTE_BLOCK: return new cNoteEntity (a_BlockType, a_BlockMeta, a_BlockX, a_BlockY, a_BlockZ, a_World); } LOGD("%s: Requesting creation of an unknown block entity - block type %d (%s)", __FUNCTION__, a_BlockType, ItemTypeToString(a_BlockType).c_str() diff --git a/src/BlockEntities/BlockEntity.h b/src/BlockEntities/BlockEntity.h index 6c69e8260..5b7184775 100644 --- a/src/BlockEntities/BlockEntity.h +++ b/src/BlockEntities/BlockEntity.h @@ -64,6 +64,14 @@ public: m_World = a_World; } + /** Updates the internally stored position. + Note that this should not ever be used for world-contained block entities, it is meant only for when BEs in a cBlockArea are manipulated. + Asserts when the block entity is assigned to a world. */ + void SetPos(int a_NewBlockX, int a_NewBlockY, int a_NewBlockZ); + + /** Returns true if the specified blocktype is supposed to have an associated block entity. */ + static bool IsBlockEntityBlockType(BLOCKTYPE a_BlockType); + /** Creates a new block entity for the specified block type If a_World is valid, then the entity is created bound to that world Returns nullptr for unknown block types. */ diff --git a/src/Chunk.cpp b/src/Chunk.cpp index 849b0e2f5..99f643437 100644 --- a/src/Chunk.cpp +++ b/src/Chunk.cpp @@ -408,7 +408,7 @@ void cChunk::WriteBlockArea(cBlockArea & a_Area, int a_MinBlockX, int a_MinBlock { if ((a_DataTypes & (cBlockArea::baTypes | cBlockArea::baMetas)) != (cBlockArea::baTypes | cBlockArea::baMetas)) { - LOGWARNING("cChunk::WriteBlockArea(): unsupported datatype request, can write only types + metas (0x%x), requested 0x%x. Ignoring.", + LOGWARNING("cChunk::WriteBlockArea(): unsupported datatype request, can write only types + metas together (0x%x), requested 0x%x. Ignoring.", (cBlockArea::baTypes | cBlockArea::baMetas), a_DataTypes & (cBlockArea::baTypes | cBlockArea::baMetas) ); return; @@ -420,16 +420,16 @@ void cChunk::WriteBlockArea(cBlockArea & a_Area, int a_MinBlockX, int a_MinBlock int BlockEndX = std::min(a_MinBlockX + a_Area.GetSizeX(), (m_PosX + 1) * cChunkDef::Width); int BlockStartZ = std::max(a_MinBlockZ, m_PosZ * cChunkDef::Width); int BlockEndZ = std::min(a_MinBlockZ + a_Area.GetSizeZ(), (m_PosZ + 1) * cChunkDef::Width); - int SizeX = BlockEndX - BlockStartX; + int SizeX = BlockEndX - BlockStartX; // Size of the union int SizeZ = BlockEndZ - BlockStartZ; - int OffX = BlockStartX - m_PosX * cChunkDef::Width; + int SizeY = std::min(a_Area.GetSizeY(), cChunkDef::Height - a_MinBlockY); + int OffX = BlockStartX - m_PosX * cChunkDef::Width; // Offset within the chunk where the union starts int OffZ = BlockStartZ - m_PosZ * cChunkDef::Width; - int BaseX = BlockStartX - a_MinBlockX; + int BaseX = BlockStartX - a_MinBlockX; // Offset within the area where the union starts int BaseZ = BlockStartZ - a_MinBlockZ; - int SizeY = std::min(a_Area.GetSizeY(), cChunkDef::Height - a_MinBlockY); - // TODO: Improve this by not calling FastSetBlock() and doing the processing here - // so that the heightmap is touched only once for each column. + // Copy blocktype and blockmeta: + // TODO: Right now each changed block is transmitted to all clients as a separate packet. Optimize this for larger areas. BLOCKTYPE * AreaBlockTypes = a_Area.GetBlockTypes(); NIBBLETYPE * AreaBlockMetas = a_Area.GetBlockMetas(); for (int y = 0; y < SizeY; y++) @@ -451,6 +451,50 @@ void cChunk::WriteBlockArea(cBlockArea & a_Area, int a_MinBlockX, int a_MinBlock } // for x } // for z } // for y + + // Erase all affected block entities: + cCuboid affectedArea(OffX, a_MinBlockY, OffZ, OffX + SizeX - 1, a_MinBlockY + SizeY - 1, OffZ + SizeZ - 1); + for (auto itr = m_BlockEntities.begin(); itr != m_BlockEntities.end();) + { + if (affectedArea.IsInside(itr->second->GetPos())) + { + itr = m_BlockEntities.erase(itr); + } + else + { + ++itr; + } + } + + // Clone block entities from a_Area into this chunk: + if ((a_DataTypes & cBlockArea::baBlockEntities) != 0) + { + for (const auto & keyPair: a_Area.GetBlockEntities()) + { + auto & be = keyPair.second; + auto posX = be->GetPosX() + a_MinBlockX; + auto posY = be->GetPosY() + a_MinBlockY; + auto posZ = be->GetPosZ() + a_MinBlockZ; + if ( + (posX < m_PosX * cChunkDef::Width) || (posX >= m_PosX * cChunkDef::Width + cChunkDef::Width) || + (posZ < m_PosZ * cChunkDef::Width) || (posZ >= m_PosZ * cChunkDef::Width + cChunkDef::Width) + ) + { + continue; + } + // This block entity is inside the chunk, clone it (and remove any that is there currently): + auto idx = MakeIndex(posX - m_PosX * cChunkDef::Width, posY, posZ - m_PosZ * cChunkDef::Width); + auto itr = m_BlockEntities.find(idx); + if (itr != m_BlockEntities.end()) + { + m_BlockEntities.erase(itr); + } + auto clone = be->Clone(posX, posY, posZ); + clone->SetWorld(m_World); + AddBlockEntityClean(clone); + BroadcastBlockEntity(posX, posY, posZ); + } + } } diff --git a/tests/Generating/Stubs.cpp b/tests/Generating/Stubs.cpp index 0ad3eb365..b9e171b97 100644 --- a/tests/Generating/Stubs.cpp +++ b/tests/Generating/Stubs.cpp @@ -288,3 +288,29 @@ void cDeadlockDetect::UntrackCriticalSection(cCriticalSection & a_CS) + +void cBlockEntity::SetPos(int a_BlockX, int a_BlockY, int a_BlockZ) +{ +} + + + + + +bool cBlockEntity::IsBlockEntityBlockType(BLOCKTYPE a_BlockType) +{ + return false; +} + + + + + +cBlockEntity * cBlockEntity::Clone(int a_BlockX, int a_BlockY, int a_BlockZ) +{ + return nullptr; +} + + + + diff --git a/tests/LuaThreadStress/Stubs.cpp b/tests/LuaThreadStress/Stubs.cpp index 0ad3eb365..b9e171b97 100644 --- a/tests/LuaThreadStress/Stubs.cpp +++ b/tests/LuaThreadStress/Stubs.cpp @@ -288,3 +288,29 @@ void cDeadlockDetect::UntrackCriticalSection(cCriticalSection & a_CS) + +void cBlockEntity::SetPos(int a_BlockX, int a_BlockY, int a_BlockZ) +{ +} + + + + + +bool cBlockEntity::IsBlockEntityBlockType(BLOCKTYPE a_BlockType) +{ + return false; +} + + + + + +cBlockEntity * cBlockEntity::Clone(int a_BlockX, int a_BlockY, int a_BlockZ) +{ + return nullptr; +} + + + + diff --git a/tests/SchematicFileSerializer/Stubs.cpp b/tests/SchematicFileSerializer/Stubs.cpp index 2fb500436..00f873b7a 100644 --- a/tests/SchematicFileSerializer/Stubs.cpp +++ b/tests/SchematicFileSerializer/Stubs.cpp @@ -7,6 +7,7 @@ #include "Globals.h" #include "BlockInfo.h" #include "Blocks/BlockHandler.h" +#include "BlockEntities/BlockEntity.h" @@ -191,3 +192,37 @@ bool cBlockHandler::IsInsideBlock(const Vector3d & a_Position, const BLOCKTYPE a +bool cBlockEntity::IsBlockEntityBlockType(BLOCKTYPE a_BlockType) +{ + return false; +} + + + + + +void cBlockEntity::SetPos(int a_BlockX, int a_BlockY, int a_BlockZ) +{ +} + + + + + +cBlockEntity * cBlockEntity::Clone(int a_BlockX, int a_BlockY, int a_BlockZ) +{ + return nullptr; +} + + + + + +cBlockEntity * cBlockEntity::CreateByBlockType(BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta, int a_BlockX, int a_BlockY, int a_BlockZ, cWorld * a_World) +{ + return nullptr; +} + + + + -- cgit v1.2.3