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) --- 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 +- 11 files changed, 2263 insertions(+), 704 deletions(-) create mode 100644 src/Bindings/ManualBindings_BlockArea.cpp (limited to 'src') 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); + } + } } -- cgit v1.2.3