diff options
57 files changed, 2044 insertions, 400 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 5b3fd5e7d..76b91e642 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -54,11 +54,13 @@ endif() # This has to be done before any flags have been set up. if(${BUILD_TOOLS}) + message("Building tools") add_subdirectory(Tools/MCADefrag/) add_subdirectory(Tools/ProtoProxy/) endif() if(${BUILD_UNSTABLE_TOOLS}) + message("Building unstable tools") add_subdirectory(Tools/GeneratorPerformanceTest/) endif() diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 03481ec48..f27451351 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,7 +1,7 @@ Code Stuff ---------- - * We use C++03 with some C++11 extensions (ask if you think that something would be useful) + * We use the subset of C++11 supported by MSVC 2013 (ask if you think that something would be useful) * Use the provided wrappers for OS stuff: - Threading is done by inheriting from `cIsThread`, thread synchronization through `cCriticalSection`, `cSemaphore` and `cEvent`, file access and filesystem operations through the `cFile` class, high-precision timers through `cTimer`, high-precision sleep through `cSleep` * No magic numbers, use named constants: diff --git a/CONTRIBUTORS b/CONTRIBUTORS index 9a0a675e7..a25c6f06b 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -20,6 +20,7 @@ mtilden nesco p-mcgowan rs2k +SafwatHalaby (Safwat Halaby) SamJBarney Sofapriester SphinxC0re diff --git a/MCServer/Plugins/APIDump/APIDesc.lua b/MCServer/Plugins/APIDump/APIDesc.lua index a3695fe17..7c0a8e818 100644 --- a/MCServer/Plugins/APIDump/APIDesc.lua +++ b/MCServer/Plugins/APIDump/APIDesc.lua @@ -99,6 +99,7 @@ g_APIDesc = Clear = { Params = "", Return = "", Notes = "Clears the object, resets it to zero size" }, CopyFrom = { Params = "BlockAreaSrc", Return = "", Notes = "Copies contents from BlockAreaSrc into self" }, CopyTo = { Params = "BlockAreaDst", Return = "", Notes = "Copies contents from self into BlockAreaDst." }, + CountNonAirBlocks = { Params = "", Return = "number", Notes = "Returns the count of blocks that are not air. Returns 0 if blocktypes not available. Block metas are ignored (if present, air with any meta is still considered air)." }, Create = { Params = "SizeX, SizeY, SizeZ, [DataTypes]", Return = "", Notes = "Initializes this BlockArea to an empty area of the specified size and origin of {0, 0, 0}. Any previous contents are lost." }, Crop = { Params = "AddMinX, SubMaxX, AddMinY, SubMaxY, AddMinZ, SubMaxZ", Return = "", Notes = "Crops the specified number of blocks from each border. Modifies the size of this blockarea object." }, DumpToRawFile = { Params = "FileName", Return = "", Notes = "Dumps the raw data into a file. For debugging purposes only." }, @@ -120,6 +121,7 @@ g_APIDesc = GetOriginX = { Params = "", Return = "number", Notes = "Returns the origin x-coord" }, GetOriginY = { Params = "", Return = "number", Notes = "Returns the origin y-coord" }, GetOriginZ = { Params = "", Return = "number", Notes = "Returns the origin z-coord" }, + GetNonAirCropRelCoords = { Params = "[IgnoreBlockType]", Return = "MinRelX, MinRelY, MinRelZ, MaxRelX, MaxRelY, MaxRelZ", Notes = "Returns the minimum and maximum coords in each direction for the first non-ignored block in each direction. If there are no non-ignored blocks within the area, or blocktypes are not present, the returned values are reverse-ranges (MinX <- m_RangeX, MaxX <- 0 etc.). IgnoreBlockType defaults to air." }, GetRelBlockLight = { Params = "RelBlockX, RelBlockY, RelBlockZ", Return = "NIBBLETYPE", Notes = "Returns the blocklight at the specified relative coords" }, GetRelBlockMeta = { Params = "RelBlockX, RelBlockY, RelBlockZ", Return = "NIBBLETYPE", Notes = "Returns the block meta at the specified relative coords" }, GetRelBlockSkyLight = { Params = "RelBlockX, RelBlockY, RelBlockZ", Return = "NIBBLETYPE", Notes = "Returns the skylight at the specified relative coords" }, @@ -197,13 +199,14 @@ g_APIDesc = baMetas = { Notes = "Operations should work on block metas" }, baLight = { Notes = "Operations should work on block (emissive) light" }, baSkyLight = { Notes = "Operations should work on skylight" }, - msDifference = { Notes = "Block becomes air if Src and Dst are the same. Otherwise it becomes the source block." }, - msOverwrite = { Notes = "Src overwrites anything in Dst" }, - msFillAir = { Notes = "Dst is overwritten by Src only where Src has air blocks" }, - msImprint = { Notes = "Src overwrites Dst anywhere where Dst has non-air blocks" }, + msDifference = { Notes = "Block becomes air if 'self' and src are the same. Otherwise it becomes the src block." }, + msFillAir = { Notes = "'self' is overwritten by Src only where 'self' has air blocks" }, + msImprint = { Notes = "Src overwrites 'self' anywhere where 'self' has non-air blocks" }, msLake = { Notes = "Special mode for merging lake images" }, + msMask = { Notes = "The blocks that are exactly the same are kept in 'self', all differing blocks are replaced by air"}, + msOverwrite = { Notes = "Src overwrites anything in 'self'" }, + msSimpleCompare = { Notes = "The blocks that are exactly the same are replaced with air, all differing blocks are replaced by stone"}, msSpongePrint = { Notes = "Similar to msImprint, sponge block doesn't overwrite anything, all other blocks overwrite everything"}, - msMask = { Notes = "The blocks that are exactly the same are kept in Dst, all differing blocks are replaced by air"}, }, ConstantGroups = { @@ -287,7 +290,7 @@ g_APIDesc = <table><tbody><tr> <th colspan="2"> area block </th><th> </th><th> Notes </th> </tr><tr> - <th> this </th><th> Src </th><th> result </th><th> </th> + <th> self </th><th> Src </th><th> result </th><th> </th> </tr><tr> <td> A </td><td> sponge </td><td> A </td><td> Sponge is the NOP block </td> </tr><tr> @@ -321,7 +324,7 @@ g_APIDesc = <table><tbody><tr> <th colspan="2"> area block </th><th> </th><th> Notes </th> </tr><tr> - <th> this </th><th> Src </th><th> result </th><th> </th> + <th> self </th><th> Src </th><th> result </th><th> </th> </tr><tr> <td> A </td><td> sponge </td><td> A </td><td> Sponge is the NOP block </td> </tr><tr> @@ -337,13 +340,45 @@ g_APIDesc = <table><tbody><tr> <th colspan="2"> area block </th><th> </th><th> Notes </th> </tr><tr> - <th> this </th><th> Src </th><th> result </th><th> </th> + <th> self </th><th> Src </th><th> result </th><th> </th> </tr><tr> <td> A </td><td> A </td><td> A </td><td> Same blocks are kept </td> </tr><tr> <td> A </td><td> non-A </td><td> air </td><td> Differing blocks are replaced with air </td> </tr> </tbody></table> + + <p> + <strong>msDifference</strong> - the blocks that are the same in both areas are replaced with air, all the + differing blocks are kept from the first area. Meta is used in the comparison, too, two blocks of the + same type but different meta are considered different. + </p> + <table><tbody><tr> + <th colspan="2"> area block </th><th> </th><th> Notes </th> + </tr><tr> + <th> self </th><th> Src </th><th> result </th><th> </th> + </tr><tr> + <td> A </td><td> A </td><td> air </td><td> Same blocks are replaced with air </td> + </tr><tr> + <td> A </td><td> non-A </td><td> A </td><td> Differing blocks are kept from 'self' </td> + </tr> + </tbody></table> + + <p> + <strong>msSimpleCompare</strong> - the blocks that are the same in both areas are replaced with air, all the + differing blocks are replaced with stone. Meta is used in the comparison, too, two blocks of the + same type but different meta are considered different. + </p> + <table><tbody><tr> + <th colspan="2"> area block </th><th> </th><th> Notes </th> + </tr><tr> + <th> self </th><th> Src </th><th> result </th><th> </th> + </tr><tr> + <td> A </td><td> A </td><td> air </td><td> Same blocks are replaced with air </td> + </tr><tr> + <td> A </td><td> non-A </td><td> stone </td><td> Differing blocks are replaced with stone </td> + </tr> + </tbody></table> ]], }, -- Merge strategies }, -- AdditionalInfo diff --git a/MCServer/Plugins/APIDump/main_APIDump.lua b/MCServer/Plugins/APIDump/main_APIDump.lua index 239bec69c..013ec7bef 100644 --- a/MCServer/Plugins/APIDump/main_APIDump.lua +++ b/MCServer/Plugins/APIDump/main_APIDump.lua @@ -1643,6 +1643,15 @@ end +local function HandleCmdApiShow(a_Split, a_EntireCmd) + os.execute("API" .. cFile:GetPathSeparator() .. "index.html") + return true, "Launching the browser to show the API docs..." +end + + + + + function Initialize(Plugin) g_Plugin = Plugin; g_PluginFolder = Plugin:GetLocalFolder(); @@ -1651,6 +1660,7 @@ function Initialize(Plugin) -- Bind a console command to dump the API: cPluginManager:BindConsoleCommand("api", HandleCmdApi, "Dumps the Lua API docs into the API/ subfolder") + cPluginManager:BindConsoleCommand("apishow", HandleCmdApiShow, "Runs the default browser to show the API docs") -- Add a WebAdmin tab that has a Dump button g_Plugin:AddWebTab("APIDump", HandleWebAdminDump) diff --git a/MCServer/Plugins/Core b/MCServer/Plugins/Core -Subproject 5f3aca002af6b77c1c67ddc356c63479131dfde +Subproject ea0ab964d568630fd4f2b52954186f2851a769e diff --git a/MCServer/README.txt b/MCServer/README.txt index f2611fd04..3cddc37d2 100644 --- a/MCServer/README.txt +++ b/MCServer/README.txt @@ -16,5 +16,5 @@ | Mail: faketruth@gmail.com | \============================/ -Compatible clients: 1.2.4, 1.2.5, 1.3.1, 1.3.2, 1.4.2, 1.4.4, 1.4.5, 1.4.6, 1.4.7, 1.5, 1.5.1, 1.5.2, 1.6.1, 1.6.2, 1.6.3, 1.6.4 -Compatible protocol versions: 29, 39, 47, 49, 51, 60, 61, 73, 74, 77, 78 +Compatible clients: 1.7.x and 1.8.x +Compatible protocol versions: 4, 5, 47 diff --git a/MCServer/crafting.txt b/MCServer/crafting.txt index 0b6f357c8..1e1f06156 100644 --- a/MCServer/crafting.txt +++ b/MCServer/crafting.txt @@ -50,7 +50,7 @@ JunglePlanks, 4 = JungleLog, * AcaciaPlanks, 4 = AcaciaLog, * DarkOakPlanks, 4 = DarkOakLog, * Stick, 4 = Planks^-1, 2:2, 2:3 -Torch, 4 = Stick, 1:2 | Coal, 1:1 +Torch, 4 = Stick, 1:2 | Coal^-1, 1:1 Workbench = Planks^-1, 1:1, 1:2, 2:1, 2:2 Chest = Planks^-1, 1:1, 1:2, 1:3, 2:1, 2:3, 3:1, 3:2, 3:3 TrappedChest = TripWireHook, 1:1 | Chest, 2:1 diff --git a/MCServer/furnace.txt b/MCServer/furnace.txt index fb8d63677..24bc35bc8 100644 --- a/MCServer/furnace.txt +++ b/MCServer/furnace.txt @@ -56,6 +56,7 @@ ClayBlock = HardenedClay Netherrack = NetherBrickItem RawFish = CookedFish Log = CharCoal +DarkOakLog = CharCoal Cactus = GreenDye WetSponge = Sponge Stonebrick = CrackedStonebrick @@ -87,6 +88,7 @@ RawMutton = CookedMutton ! Jukebox = 300 # -> 15 sec ! Lavabucket = 20000 # -> 1000 sec ! Log = 300 # -> 15 sec +! DarkOakLog = 300 # -> 15 sec ! Sapling = 100 # -> 5 sec ! CoalBlock = 16000 # -> 800 sec ! BlazeRod = 2400 # -> 120 sec diff --git a/MCServer/webadmin/template.lua b/MCServer/webadmin/template.lua index 2e89836af..f210a2ce8 100644 --- a/MCServer/webadmin/template.lua +++ b/MCServer/webadmin/template.lua @@ -40,7 +40,7 @@ function GetDefaultPage() cRoot:Get():ForEachPlayer( function(a_CBPlayer) - Content = Content .. "<li>" .. Player:GetName() .. "</li>" + Content = Content .. "<li>" .. a_CBPlayer:GetName() .. "</li>" end ) diff --git a/Tools/ProtoProxy/CMakeLists.txt b/Tools/ProtoProxy/CMakeLists.txt index 132a14f78..ce64db38d 100644 --- a/Tools/ProtoProxy/CMakeLists.txt +++ b/Tools/ProtoProxy/CMakeLists.txt @@ -105,5 +105,5 @@ add_executable(ProtoProxy ${SHARED_OSS_HDR} ) -target_link_libraries(ProtoProxy zlib polarssl) +target_link_libraries(ProtoProxy zlib mbedtls) @@ -1,5 +1,5 @@ name: MCServer -image: ubuntu-14-04-x64 +image: ubuntu-15-04-x64 config: #cloud-config packages: diff --git a/lib/SQLiteCpp b/lib/SQLiteCpp -Subproject 55edadd56d0d6f506954ad00c3b9a5d425814a2 +Subproject b17195b8d03e8908807c51f4d6ce610b148fc1b diff --git a/lib/libevent b/lib/libevent -Subproject 62eaa889cc1996a7c58a389bf2dfa5d8ce784bd +Subproject de2bb6568c930f76a5bc41ef6e0bf35a8a826e6 diff --git a/lib/polarssl b/lib/polarssl -Subproject 38f47a8546b55e2b593bba27f03070e1e82d3c8 +Subproject 4f4c5b7450631e46a94cb89adf4a7737fbb178b diff --git a/lib/polarssl.cmake b/lib/polarssl.cmake index ced70b94e..3506d0fb4 100644 --- a/lib/polarssl.cmake +++ b/lib/polarssl.cmake @@ -1,11 +1,10 @@ -if(NOT TARGET polarssl) +# This script includes PolarSSL, if not already included. +# It is needed for when multiple projects reference PolarSSL. + +if(NOT TARGET mbedtls) message("including polarssl") set(ENABLE_TESTING OFF CACHE BOOL "Disable tests") set(ENABLE_PROGRAMS OFF CACHE BOOL "Disable programs") - if (SELF_TEST) - add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/polarssl/ ${CMAKE_CURRENT_BINARY_DIR}/lib/polarssl) - else() - add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/polarssl/ ${CMAKE_CURRENT_BINARY_DIR}/lib/polarssl EXCLUDE_FROM_ALL) - endif() + add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/polarssl/ ${CMAKE_CURRENT_BINARY_DIR}/lib/polarssl EXCLUDE_FROM_ALL) endif() diff --git a/src/Bindings/CMakeLists.txt b/src/Bindings/CMakeLists.txt index 4cc73b350..366284fcb 100644 --- a/src/Bindings/CMakeLists.txt +++ b/src/Bindings/CMakeLists.txt @@ -142,5 +142,5 @@ set_source_files_properties(${CMAKE_SOURCE_DIR}/src/Bindings/Bindings.cpp PROPER if(NOT MSVC) add_library(Bindings ${SRCS} ${HDRS}) - target_link_libraries(Bindings lua sqlite tolualib polarssl) + target_link_libraries(Bindings lua sqlite tolualib mbedtls) endif() diff --git a/src/Bindings/LuaState.cpp b/src/Bindings/LuaState.cpp index 38e008b2a..ed31e678f 100644 --- a/src/Bindings/LuaState.cpp +++ b/src/Bindings/LuaState.cpp @@ -937,6 +937,18 @@ void cLuaState::GetStackValue(int a_StackPos, AString & a_Value) +void cLuaState::GetStackValue(int a_StackPos, BLOCKTYPE & a_ReturnedVal) +{ + if (lua_isnumber(m_LuaState, a_StackPos)) + { + a_ReturnedVal = static_cast<BLOCKTYPE>(tolua_tonumber(m_LuaState, a_StackPos, a_ReturnedVal)); + } +} + + + + + void cLuaState::GetStackValue(int a_StackPos, bool & a_ReturnedVal) { a_ReturnedVal = (tolua_toboolean(m_LuaState, a_StackPos, a_ReturnedVal ? 1 : 0) > 0); @@ -995,6 +1007,24 @@ void cLuaState::GetStackValue(int a_StackPos, int & a_ReturnedVal) +void cLuaState::GetStackValue(int a_StackPos, pBlockArea & a_ReturnedVal) +{ + if (lua_isnil(m_LuaState, a_StackPos)) + { + a_ReturnedVal = nullptr; + return; + } + tolua_Error err; + if (tolua_isusertype(m_LuaState, a_StackPos, "cBlockArea", false, &err)) + { + a_ReturnedVal = *(reinterpret_cast<cBlockArea **>(lua_touserdata(m_LuaState, a_StackPos))); + } +} + + + + + void cLuaState::GetStackValue(int a_StackPos, pBoundingBox & a_ReturnedVal) { if (lua_isnil(m_LuaState, a_StackPos)) @@ -1005,7 +1035,7 @@ void cLuaState::GetStackValue(int a_StackPos, pBoundingBox & a_ReturnedVal) tolua_Error err; if (tolua_isusertype(m_LuaState, a_StackPos, "cBoundingBox", false, &err)) { - a_ReturnedVal = *((cBoundingBox **)lua_touserdata(m_LuaState, a_StackPos)); + a_ReturnedVal = *(reinterpret_cast<cBoundingBox **>(lua_touserdata(m_LuaState, a_StackPos))); } } diff --git a/src/Bindings/LuaState.h b/src/Bindings/LuaState.h index a6e121eb7..6bedbf5ec 100644 --- a/src/Bindings/LuaState.h +++ b/src/Bindings/LuaState.h @@ -37,6 +37,7 @@ extern "C" +class cBlockArea; class cBlockEntity; class cBoundingBox; class cChunkDesc; @@ -68,6 +69,7 @@ struct HTTPRequest; struct HTTPTemplateRequest; struct TakeDamageInfo; +typedef cBlockArea * pBlockArea; typedef cBoundingBox * pBoundingBox; typedef cMapManager * pMapManager; typedef cPluginManager * pPluginManager; @@ -244,11 +246,13 @@ public: // GetStackValue() retrieves the value at a_StackPos, if it is a valid type. If not, a_Value is unchanged. // Enum values are clamped to their allowed range. void GetStackValue(int a_StackPos, AString & a_Value); + void GetStackValue(int a_StackPos, BLOCKTYPE & a_Value); void GetStackValue(int a_StackPos, bool & a_Value); void GetStackValue(int a_StackPos, cRef & a_Ref); void GetStackValue(int a_StackPos, double & a_Value); void GetStackValue(int a_StackPos, eWeather & a_Value); void GetStackValue(int a_StackPos, int & a_Value); + void GetStackValue(int a_StackPos, pBlockArea & a_Value); void GetStackValue(int a_StackPos, pBoundingBox & a_Value); void GetStackValue(int a_StackPos, pMapManager & a_Value); void GetStackValue(int a_StackPos, pPluginManager & a_Value); diff --git a/src/Bindings/ManualBindings.cpp b/src/Bindings/ManualBindings.cpp index 253d57297..f25800d5f 100644 --- a/src/Bindings/ManualBindings.cpp +++ b/src/Bindings/ManualBindings.cpp @@ -253,12 +253,13 @@ static int tolua_InflateString(lua_State * tolua_S) static int tolua_StringSplit(lua_State * tolua_S) { + // Get the params: cLuaState LuaState(tolua_S); - std::string str = (std::string)tolua_tocppstring(LuaState, 1, 0); - std::string delim = (std::string)tolua_tocppstring(LuaState, 2, 0); + AString str, delim; + LuaState.GetStackValues(1, str, delim); - AStringVector Split = StringSplit(str, delim); - LuaState.Push(Split); + // Execute and push the result: + LuaState.Push(StringSplit(str, delim)); return 1; } @@ -472,6 +473,33 @@ cPluginLua * GetLuaPlugin(lua_State * L) static int tolua_cFile_GetFolderContents(lua_State * tolua_S) { + // Check params: + cLuaState LuaState(tolua_S); + if ( + !LuaState.CheckParamUserTable(1, "cFile") || + !LuaState.CheckParamString (2) || + !LuaState.CheckParamEnd (3) + ) + { + return 0; + } + + // Get params: + AString Folder; + LuaState.GetStackValues(2, Folder); + + // Execute and push result: + LuaState.Push(cFile::GetFolderContents(Folder)); + return 1; +} + + + + + +static int tolua_cFile_ReadWholeFile(lua_State * tolua_S) +{ + // Check params: cLuaState LuaState(tolua_S); if ( !LuaState.CheckParamUserTable(1, "cFile") || @@ -482,10 +510,12 @@ static int tolua_cFile_GetFolderContents(lua_State * tolua_S) return 0; } - AString Folder = (AString)tolua_tocppstring(LuaState, 2, 0); + // Get params: + AString FileName; + LuaState.GetStackValues(2, FileName); - AStringVector Contents = cFile::GetFolderContents(Folder); - LuaState.Push(Contents); + // Execute and push result: + LuaState.Push(cFile::ReadWholeFile(FileName)); return 1; } @@ -2125,6 +2155,37 @@ static int tolua_cPlayer_GetPermissions(lua_State * tolua_S) +static int tolua_cPlayer_GetRestrictions(lua_State * tolua_S) +{ + // Function signature: cPlayer:GetRestrictions() -> {restrictions-array} + + // Check the params: + cLuaState L(tolua_S); + if ( + !L.CheckParamUserType(1, "cPlayer") || + !L.CheckParamEnd (2) + ) + { + return 0; + } + + // Get the params: + cPlayer * self = (cPlayer *)tolua_tousertype(tolua_S, 1, nullptr); + if (self == nullptr) + { + LOGWARNING("%s: invalid self (%p)", __FUNCTION__, self); + return 0; + } + + // Push the permissions: + L.Push(self->GetRestrictions()); + return 1; +} + + + + + static int tolua_cPlayer_OpenWindow(lua_State * tolua_S) { // Function signature: cPlayer:OpenWindow(Window) @@ -3156,6 +3217,44 @@ static int tolua_cBlockArea_GetOrigin(lua_State * tolua_S) +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); + L.Push(MinRelY); + L.Push(MinRelZ); + L.Push(MaxRelX); + L.Push(MaxRelY); + L.Push(MaxRelZ); + return 6; +} + + + + + static int tolua_cBlockArea_GetRelBlockTypeMeta(lua_State * tolua_S) { // function cBlockArea::GetRelBlockTypeMeta() @@ -3650,12 +3749,14 @@ void ManualBindings::Bind(lua_State * tolua_S) tolua_beginmodule(tolua_S, "cFile"); tolua_function(tolua_S, "GetFolderContents", tolua_cFile_GetFolderContents); + tolua_function(tolua_S, "ReadWholeFile", tolua_cFile_ReadWholeFile); tolua_endmodule(tolua_S); 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); @@ -3756,6 +3857,7 @@ void ManualBindings::Bind(lua_State * tolua_S) tolua_beginmodule(tolua_S, "cPlayer"); tolua_function(tolua_S, "GetPermissions", tolua_cPlayer_GetPermissions); + tolua_function(tolua_S, "GetRestrictions", tolua_cPlayer_GetRestrictions); tolua_function(tolua_S, "OpenWindow", tolua_cPlayer_OpenWindow); tolua_function(tolua_S, "PermissionMatches", tolua_cPlayer_PermissionMatches); tolua_endmodule(tolua_S); diff --git a/src/Bindings/ManualBindings_RankManager.cpp b/src/Bindings/ManualBindings_RankManager.cpp index fa1b88b6a..c9f187fc6 100644 --- a/src/Bindings/ManualBindings_RankManager.cpp +++ b/src/Bindings/ManualBindings_RankManager.cpp @@ -100,6 +100,35 @@ static int tolua_cRankManager_AddPermissionToGroup(lua_State * L) +/** Binds cRankManager::AddRestrictionToGroup */ +static int tolua_cRankManager_AddRestrictionToGroup(lua_State * L) +{ + // Function signature: + // cRankManager:AddRestrictionToGroup(Permission, GroupName) -> bool + + cLuaState S(L); + if ( + !S.CheckParamUserTable(1, "cRankManager") || + !S.CheckParamString(2, 3) || + !S.CheckParamEnd(4) + ) + { + return 0; + } + + // Read the params: + AString GroupName, Permission; + S.GetStackValues(2, Permission, GroupName); + + // Add the group to the rank: + S.Push(cRoot::Get()->GetRankManager()->AddRestrictionToGroup(Permission, GroupName)); + return 1; +} + + + + + /** Binds cRankManager::AddRank */ static int tolua_cRankManager_AddRank(lua_State * L) { @@ -204,6 +233,60 @@ static int tolua_cRankManager_GetAllPermissions(lua_State * L) +/** Binds cRankManager::GetAllPermissions */ +static int tolua_cRankManager_GetAllRestrictions(lua_State * L) +{ + // Function signature: + // cRankManager:GetAllRestrictions() -> arraytable of Permissions + + cLuaState S(L); + if ( + !S.CheckParamUserTable(1, "cRankManager") || + !S.CheckParamEnd(2) + ) + { + return 0; + } + + // Get the permissions: + AStringVector Permissions = cRoot::Get()->GetRankManager()->GetAllRestrictions(); + + // Push the results: + S.Push(Permissions); + return 1; +} + + + + + +/** Binds cRankManager::GetAllPermissionsRestrictions */ +static int tolua_cRankManager_GetAllPermissionsRestrictions(lua_State * L) +{ + // Function signature: + // cRankManager:GetAllPermissionsRestrictions() -> arraytable of Permissions and Restrictions + + cLuaState S(L); + if ( + !S.CheckParamUserTable(1, "cRankManager") || + !S.CheckParamEnd(2) + ) + { + return 0; + } + + // Get the permissions: + AStringVector Permissions = cRoot::Get()->GetRankManager()->GetAllPermissionsRestrictions(); + + // Push the results: + S.Push(Permissions); + return 1; +} + + + + + /** Binds cRankManager::GetAllPlayerUUIDs */ static int tolua_cRankManager_GetAllPlayerUUIDs(lua_State * L) { @@ -314,6 +397,38 @@ static int tolua_cRankManager_GetGroupPermissions(lua_State * L) +/** Binds cRankManager::GetGroupRestrictions */ +static int tolua_cRankManager_GetGroupRestrictions(lua_State * L) +{ + // Function signature: + // cRankManager:GetGroupRestrictions(GroupName) -> arraytable of restrictions + + cLuaState S(L); + if ( + !S.CheckParamUserTable(1, "cRankManager") || + !S.CheckParamString(2) || + !S.CheckParamEnd(3) + ) + { + return 0; + } + + // Get the params: + AString GroupName; + S.GetStackValue(2, GroupName); + + // Get the restrictions: + AStringVector Restrictions = cRoot::Get()->GetRankManager()->GetGroupRestrictions(GroupName); + + // Push the results: + S.Push(Restrictions); + return 1; +} + + + + + /** Binds cRankManager::GetPlayerGroups */ static int tolua_cRankManager_GetPlayerGroups(lua_State * L) { @@ -416,6 +531,38 @@ static int tolua_cRankManager_GetPlayerPermissions(lua_State * L) +/** Binds cRankManager::GetPlayerRestrictions */ +static int tolua_cRankManager_GetPlayerRestrictions(lua_State * L) +{ + // Function signature: + // cRankManager:GetPlayerRestrictions(PlayerUUID) -> arraytable of restrictions + + cLuaState S(L); + if ( + !S.CheckParamUserTable(1, "cRankManager") || + !S.CheckParamString(2) || + !S.CheckParamEnd(3) + ) + { + return 0; + } + + // Get the params: + AString PlayerUUID; + S.GetStackValue(2, PlayerUUID); + + // Get the permissions: + AStringVector Restrictions = cRoot::Get()->GetRankManager()->GetPlayerRestrictions(PlayerUUID); + + // Push the results: + S.Push(Restrictions); + return 1; +} + + + + + /** Binds cRankManager::GetPlayerRankName */ static int tolua_cRankManager_GetPlayerRankName(lua_State * L) { @@ -544,6 +691,38 @@ static int tolua_cRankManager_GetRankPermissions(lua_State * L) +/** Binds cRankManager::GetRankRestrictions */ +static int tolua_cRankManager_GetRankRestrictions(lua_State * L) +{ + // Function signature: + // cRankManager:GetRankRestrictions(RankName) -> arraytable of restrictions + + cLuaState S(L); + if ( + !S.CheckParamUserTable(1, "cRankManager") || + !S.CheckParamString(2) || + !S.CheckParamEnd(3) + ) + { + return 0; + } + + // Get the params: + AString RankName; + S.GetStackValue(2, RankName); + + // Get the permissions: + AStringVector Restrictions = cRoot::Get()->GetRankManager()->GetRankRestrictions(RankName); + + // Push the results: + S.Push(Restrictions); + return 1; +} + + + + + /** Binds cRankManager::GetRankVisuals */ static int tolua_cRankManager_GetRankVisuals(lua_State * L) { @@ -679,6 +858,38 @@ static int tolua_cRankManager_IsPermissionInGroup(lua_State * L) +/** Binds cRankManager::IsRestrictionInGroup */ +static int tolua_cRankManager_IsRestrictionInGroup(lua_State * L) +{ + // Function signature: + // cRankManager:IsRestrictionInGroup(Restriction, GroupName) -> bool + + cLuaState S(L); + if ( + !S.CheckParamUserTable(1, "cRankManager") || + !S.CheckParamString(2, 3) || + !S.CheckParamEnd(4) + ) + { + return 0; + } + + // Get the params: + AString GroupName, Restriction; + S.GetStackValues(2, Restriction, GroupName); + + // Get the response: + bool res = cRoot::Get()->GetRankManager()->IsRestrictionInGroup(Restriction, GroupName); + + // Push the result: + S.Push(res); + return 1; +} + + + + + /** Binds cRankManager::IsPlayerRankSet */ static int tolua_cRankManager_IsPlayerRankSet(lua_State * L) { @@ -821,7 +1032,7 @@ static int tolua_cRankManager_RemovePermissionFromGroup(lua_State * L) AString GroupName, Permission; S.GetStackValues(2, Permission, GroupName); - // Remove the group: + // Remove the permission: cRoot::Get()->GetRankManager()->RemovePermissionFromGroup(Permission, GroupName); return 0; } @@ -830,6 +1041,35 @@ static int tolua_cRankManager_RemovePermissionFromGroup(lua_State * L) +/** Binds cRankManager::RemoveRestrictionFromGroup */ +static int tolua_cRankManager_RemoveRestrictionFromGroup(lua_State * L) +{ + // Function signature: + // cRankManager:RemoveRestrictionFromGroup(Restriction, GroupName) + + cLuaState S(L); + if ( + !S.CheckParamUserTable(1, "cRankManager") || + !S.CheckParamString(2, 3) || + !S.CheckParamEnd(4) + ) + { + return 0; + } + + // Get the params: + AString GroupName, Restriction; + S.GetStackValues(2, Restriction, GroupName); + + // Remove the restriction: + cRoot::Get()->GetRankManager()->RemoveRestrictionFromGroup(Restriction, GroupName); + return 0; +} + + + + + /** Binds cRankManager::RemovePlayerRank */ static int tolua_cRankManager_RemovePlayerRank(lua_State * L) { @@ -1048,40 +1288,48 @@ void ManualBindings::BindRankManager(lua_State * tolua_S) // Fill in the functions (alpha-sorted): tolua_beginmodule(tolua_S, "cRankManager"); - tolua_function(tolua_S, "AddGroup", tolua_cRankManager_AddGroup); - tolua_function(tolua_S, "AddGroupToRank", tolua_cRankManager_AddGroupToRank); - tolua_function(tolua_S, "AddPermissionToGroup", tolua_cRankManager_AddPermissionToGroup); - tolua_function(tolua_S, "AddRank", tolua_cRankManager_AddRank); - tolua_function(tolua_S, "ClearPlayerRanks", tolua_cRankManager_ClearPlayerRanks); - tolua_function(tolua_S, "GetAllGroups", tolua_cRankManager_GetAllGroups); - tolua_function(tolua_S, "GetAllPermissions", tolua_cRankManager_GetAllPermissions); - tolua_function(tolua_S, "GetAllPlayerUUIDs", tolua_cRankManager_GetAllPlayerUUIDs); - tolua_function(tolua_S, "GetAllRanks", tolua_cRankManager_GetAllRanks); - tolua_function(tolua_S, "GetDefaultRank", tolua_cRankManager_GetDefaultRank); - tolua_function(tolua_S, "GetGroupPermissions", tolua_cRankManager_GetGroupPermissions); - tolua_function(tolua_S, "GetPlayerGroups", tolua_cRankManager_GetPlayerGroups); - tolua_function(tolua_S, "GetPlayerMsgVisuals", tolua_cRankManager_GetPlayerMsgVisuals); - tolua_function(tolua_S, "GetPlayerPermissions", tolua_cRankManager_GetPlayerPermissions); - tolua_function(tolua_S, "GetPlayerRankName", tolua_cRankManager_GetPlayerRankName); - tolua_function(tolua_S, "GetPlayerName", tolua_cRankManager_GetPlayerName); - tolua_function(tolua_S, "GetRankGroups", tolua_cRankManager_GetRankGroups); - tolua_function(tolua_S, "GetRankPermissions", tolua_cRankManager_GetRankPermissions); - tolua_function(tolua_S, "GetRankVisuals", tolua_cRankManager_GetRankVisuals); - tolua_function(tolua_S, "GroupExists", tolua_cRankManager_GroupExists); - tolua_function(tolua_S, "IsGroupInRank", tolua_cRankManager_IsGroupInRank); - tolua_function(tolua_S, "IsPermissionInGroup", tolua_cRankManager_IsPermissionInGroup); - tolua_function(tolua_S, "IsPlayerRankSet", tolua_cRankManager_IsPlayerRankSet); - tolua_function(tolua_S, "RankExists", tolua_cRankManager_RankExists); - tolua_function(tolua_S, "RemoveGroup", tolua_cRankManager_RemoveGroup); - tolua_function(tolua_S, "RemoveGroupFromRank", tolua_cRankManager_RemoveGroupFromRank); - tolua_function(tolua_S, "RemovePermissionFromGroup", tolua_cRankManager_RemovePermissionFromGroup); - tolua_function(tolua_S, "RemovePlayerRank", tolua_cRankManager_RemovePlayerRank); - tolua_function(tolua_S, "RemoveRank", tolua_cRankManager_RemoveRank); - tolua_function(tolua_S, "RenameGroup", tolua_cRankManager_RenameGroup); - tolua_function(tolua_S, "RenameRank", tolua_cRankManager_RenameRank); - tolua_function(tolua_S, "SetDefaultRank", tolua_cRankManager_SetDefaultRank); - tolua_function(tolua_S, "SetPlayerRank", tolua_cRankManager_SetPlayerRank); - tolua_function(tolua_S, "SetRankVisuals", tolua_cRankManager_SetRankVisuals); + tolua_function(tolua_S, "AddGroup", tolua_cRankManager_AddGroup); + tolua_function(tolua_S, "AddGroupToRank", tolua_cRankManager_AddGroupToRank); + tolua_function(tolua_S, "AddPermissionToGroup", tolua_cRankManager_AddPermissionToGroup); + tolua_function(tolua_S, "AddRestrictionToGroup", tolua_cRankManager_AddRestrictionToGroup); + tolua_function(tolua_S, "AddRank", tolua_cRankManager_AddRank); + tolua_function(tolua_S, "ClearPlayerRanks", tolua_cRankManager_ClearPlayerRanks); + tolua_function(tolua_S, "GetAllGroups", tolua_cRankManager_GetAllGroups); + tolua_function(tolua_S, "GetAllPermissions", tolua_cRankManager_GetAllPermissions); + tolua_function(tolua_S, "GetAllRestrictions", tolua_cRankManager_GetAllRestrictions); + tolua_function(tolua_S, "GetAllPermissionsRestrictions", tolua_cRankManager_GetAllPermissionsRestrictions); + tolua_function(tolua_S, "GetAllPlayerUUIDs", tolua_cRankManager_GetAllPlayerUUIDs); + tolua_function(tolua_S, "GetAllRanks", tolua_cRankManager_GetAllRanks); + tolua_function(tolua_S, "GetDefaultRank", tolua_cRankManager_GetDefaultRank); + tolua_function(tolua_S, "GetGroupPermissions", tolua_cRankManager_GetGroupPermissions); + tolua_function(tolua_S, "GetGroupRestrictions", tolua_cRankManager_GetGroupRestrictions); + tolua_function(tolua_S, "GetPlayerGroups", tolua_cRankManager_GetPlayerGroups); + tolua_function(tolua_S, "GetPlayerMsgVisuals", tolua_cRankManager_GetPlayerMsgVisuals); + tolua_function(tolua_S, "GetPlayerPermissions", tolua_cRankManager_GetPlayerPermissions); + tolua_function(tolua_S, "GetPlayerPermissions", tolua_cRankManager_GetPlayerRestrictions); + tolua_function(tolua_S, "GetPlayerRankName", tolua_cRankManager_GetPlayerRankName); + tolua_function(tolua_S, "GetPlayerName", tolua_cRankManager_GetPlayerName); + tolua_function(tolua_S, "GetRankGroups", tolua_cRankManager_GetRankGroups); + tolua_function(tolua_S, "GetRankPermissions", tolua_cRankManager_GetRankPermissions); + tolua_function(tolua_S, "GetRankRestrictions", tolua_cRankManager_GetRankRestrictions); + tolua_function(tolua_S, "GetRankVisuals", tolua_cRankManager_GetRankVisuals); + tolua_function(tolua_S, "GroupExists", tolua_cRankManager_GroupExists); + tolua_function(tolua_S, "IsGroupInRank", tolua_cRankManager_IsGroupInRank); + tolua_function(tolua_S, "IsPermissionInGroup", tolua_cRankManager_IsPermissionInGroup); + tolua_function(tolua_S, "IsRestrictionInGroup", tolua_cRankManager_IsRestrictionInGroup); + tolua_function(tolua_S, "IsPlayerRankSet", tolua_cRankManager_IsPlayerRankSet); + tolua_function(tolua_S, "RankExists", tolua_cRankManager_RankExists); + tolua_function(tolua_S, "RemoveGroup", tolua_cRankManager_RemoveGroup); + tolua_function(tolua_S, "RemoveGroupFromRank", tolua_cRankManager_RemoveGroupFromRank); + tolua_function(tolua_S, "RemovePermissionFromGroup", tolua_cRankManager_RemovePermissionFromGroup); + tolua_function(tolua_S, "RemoveRestrictionFromGroup", tolua_cRankManager_RemoveRestrictionFromGroup); + tolua_function(tolua_S, "RemovePlayerRank", tolua_cRankManager_RemovePlayerRank); + tolua_function(tolua_S, "RemoveRank", tolua_cRankManager_RemoveRank); + tolua_function(tolua_S, "RenameGroup", tolua_cRankManager_RenameGroup); + tolua_function(tolua_S, "RenameRank", tolua_cRankManager_RenameRank); + tolua_function(tolua_S, "SetDefaultRank", tolua_cRankManager_SetDefaultRank); + tolua_function(tolua_S, "SetPlayerRank", tolua_cRankManager_SetPlayerRank); + tolua_function(tolua_S, "SetRankVisuals", tolua_cRankManager_SetRankVisuals); tolua_endmodule(tolua_S); } diff --git a/src/Bindings/PluginLua.cpp b/src/Bindings/PluginLua.cpp index ddd3398a5..4c98b8d26 100644 --- a/src/Bindings/PluginLua.cpp +++ b/src/Bindings/PluginLua.cpp @@ -63,6 +63,11 @@ void cPluginLua::Close(void) return; } + // Remove the command bindings and web tabs: + ClearCommands(); + ClearConsoleCommands(); + ClearTabs(); + // Notify and remove all m_Resettables (unlock the m_CriticalSection while resetting them): cResettablePtrs resettables; std::swap(m_Resettables, resettables); diff --git a/src/BlockArea.cpp b/src/BlockArea.cpp index 4c3da0535..89cf18d4a 100644 --- a/src/BlockArea.cpp +++ b/src/BlockArea.cpp @@ -245,6 +245,26 @@ void MergeCombinatorDifference(BLOCKTYPE & a_DstType, BLOCKTYPE a_SrcType, NIBBL +/** Combinator used for cBlockArea::msSimpleCompare merging */ +template <bool MetaValid> +void MergeCombinatorSimpleCompare(BLOCKTYPE & a_DstType, BLOCKTYPE a_SrcType, NIBBLETYPE & a_DstMeta, NIBBLETYPE a_SrcMeta) +{ + if ((a_DstType == a_SrcType) && (!MetaValid || (a_DstMeta == a_SrcMeta))) + { + // The blocktypes are the same, and the blockmetas are not present or are the same + a_DstType = E_BLOCK_AIR; + } + else + { + // The blocktypes or blockmetas differ + a_DstType = E_BLOCK_STONE; + } +} + + + + + /** Combinator used for cBlockArea::msMask merging */ template <bool MetaValid> void MergeCombinatorMask(BLOCKTYPE & a_DstType, BLOCKTYPE a_SrcType, NIBBLETYPE & a_DstMeta, NIBBLETYPE a_SrcMeta) @@ -1614,6 +1634,104 @@ void cBlockArea::GetRelBlockTypeMeta(int a_RelX, int a_RelY, int a_RelZ, BLOCKTY +size_t cBlockArea::CountNonAirBlocks(void) const +{ + // Check if blocktypes are valid: + if (m_BlockTypes == nullptr) + { + LOGWARNING("%s: BlockTypes have not been read!", __FUNCTION__); + return 0; + } + + // Count the blocks: + size_t res = 0; + 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++) + { + if (m_BlockTypes[MakeIndex(x, y, z)] != E_BLOCK_AIR) + { + ++res; + } + } // for x + } // for z + } // for y + return res; +} + + + + + +void cBlockArea::GetNonAirCropRelCoords(int & a_MinRelX, int & a_MinRelY, int & a_MinRelZ, int & a_MaxRelX, int & a_MaxRelY, int & a_MaxRelZ, BLOCKTYPE a_IgnoreBlockType) +{ + // Check if blocktypes are valid: + if (m_BlockTypes == nullptr) + { + LOGWARNING("%s: BlockTypes have not been read!", __FUNCTION__); + a_MinRelX = 1; + a_MaxRelX = 0; + return; + } + + // Walk all the blocks and find the min and max coords for the non-ignored ones: + int MaxX = 0, MinX = m_Size.x - 1; + int MaxY = 0, MinY = m_Size.y - 1; + int MaxZ = 0, MinZ = m_Size.z - 1; + 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++) + { + if (m_BlockTypes[MakeIndex(x, y, z)] == a_IgnoreBlockType) + { + continue; + } + // The block is not ignored, update any coords that need updating: + if (x < MinX) + { + MinX = x; + } + if (x > MaxX) + { + MaxX = x; + } + if (y < MinY) + { + MinY = y; + } + if (y > MaxY) + { + MaxY = y; + } + if (z < MinZ) + { + MinZ = z; + } + if (z > MaxZ) + { + MaxZ = z; + } + } // for x + } // for z + } // for y + + // Assign to the output: + a_MinRelX = MinX; + a_MinRelY = MinY; + a_MinRelZ = MinZ; + a_MaxRelX = MaxX; + a_MaxRelY = MaxY; + a_MaxRelZ = MaxZ; +} + + + + + int cBlockArea::GetDataTypes(void) const { int res = 0; @@ -2121,10 +2239,12 @@ void cBlockArea::RelSetData( + + template <bool MetasValid> void cBlockArea::MergeByStrategy(const cBlockArea & a_Src, int a_RelX, int a_RelY, int a_RelZ, eMergeStrategy a_Strategy, const NIBBLETYPE * SrcMetas, NIBBLETYPE * DstMetas) { - // Block types are compulsory, block metas are voluntary + // Block types are compulsory, block metas are optional if (!HasBlockTypes() || !a_Src.HasBlockTypes()) { LOGWARNING("%s: cannot merge because one of the areas doesn't have blocktypes.", __FUNCTION__); @@ -2230,6 +2350,20 @@ void cBlockArea::MergeByStrategy(const cBlockArea & a_Src, int a_RelX, int a_Rel return; } // case msDifference + case cBlockArea::msSimpleCompare: + { + InternalMergeBlocks<MetasValid, MergeCombinatorSimpleCompare<MetasValid> >( + m_BlockTypes, a_Src.GetBlockTypes(), + DstMetas, SrcMetas, + SizeX, SizeY, SizeZ, + SrcOffX, SrcOffY, SrcOffZ, + DstOffX, DstOffY, DstOffZ, + a_Src.GetSizeX(), a_Src.GetSizeY(), a_Src.GetSizeZ(), + m_Size.x, m_Size.y, m_Size.z + ); + return; + } // case msSimpleCompare + case cBlockArea::msMask: { InternalMergeBlocks<MetasValid, MergeCombinatorMask<MetasValid> >( diff --git a/src/BlockArea.h b/src/BlockArea.h index 348e960dd..856df542f 100644 --- a/src/BlockArea.h +++ b/src/BlockArea.h @@ -54,6 +54,7 @@ public: msLake, msSpongePrint, msDifference, + msSimpleCompare, msMask, } ; @@ -156,6 +157,22 @@ public: | A | sponge | A | Sponge is the NOP block | * | B | B | Everything else overwrites anything + msDifference: + Used to determine the differences between two areas. Only the differring blocks are preserved: + | area block | | + | this | Src | result | + +------+-------+--------+ + | A | A | air | Same blocks are replaced with air + | A | non-A | A | Differring blocks are kept from "this" + + msSimpleCompare: + Used to determine the differences between two areas. Blocks that differ are replaced with stone, same blocks are replaced with air + | area block | | + | this | Src | result | + +------+-------+--------+ + | A | A | air | Same blocks are replaced with air + | A | non-A | stone | Differring blocks are replaced with stone + msMask: Combines two areas, the blocks that are the same are kept, differing ones are reset to air | area block | | @@ -286,8 +303,17 @@ 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); } - + + /** 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). */ + size_t CountNonAirBlocks(void) const; + // tolua_end + + /** Returns the minimum and maximum coords in each direction for the first non-ignored block in each direction. + If there are no non-ignored blocks within the area, or blocktypes are not present, the returned values are reverse-ranges (MinX <- m_RangeX, MaxX <- 0 etc.) + Exported to Lua in ManualBindings.cpp. */ + void GetNonAirCropRelCoords(int & a_MinRelX, int & a_MinRelY, int & a_MinRelZ, int & a_MaxRelX, int & a_MaxRelY, int & a_MaxRelZ, BLOCKTYPE a_IgnoreBlockType = E_BLOCK_AIR); // Clients can use these for faster access to all blocktypes. Be careful though! /** Returns the internal pointer to the block types */ diff --git a/src/Blocks/BlockHandler.cpp b/src/Blocks/BlockHandler.cpp index 6fff4c18f..452cc94a5 100644 --- a/src/Blocks/BlockHandler.cpp +++ b/src/Blocks/BlockHandler.cpp @@ -198,6 +198,7 @@ cBlockHandler * cBlockHandler::CreateBlockHandler(BLOCKTYPE a_BlockType) case E_BLOCK_CARPET: return new cBlockCarpetHandler (a_BlockType); case E_BLOCK_CAULDRON: return new cBlockCauldronHandler (a_BlockType); case E_BLOCK_CHEST: return new cBlockChestHandler (a_BlockType); + case E_BLOCK_CLAY: return new cBlockOreHandler (a_BlockType); case E_BLOCK_COAL_ORE: return new cBlockOreHandler (a_BlockType); case E_BLOCK_COCOA_POD: return new cBlockCocoaPodHandler (a_BlockType); case E_BLOCK_COMMAND_BLOCK: return new cBlockCommandBlockHandler (a_BlockType); diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index b91c4f65a..e04e6311f 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -324,4 +324,4 @@ endif () if (WIN32) target_link_libraries(${EXECUTABLE} expat tolualib ws2_32.lib Psapi.lib) endif() -target_link_libraries(${EXECUTABLE} luaexpat jsoncpp polarssl zlib sqlite lua SQLiteCpp event_core event_extra) +target_link_libraries(${EXECUTABLE} luaexpat jsoncpp mbedtls zlib sqlite lua SQLiteCpp event_core event_extra) diff --git a/src/CheckBasicStyle.lua b/src/CheckBasicStyle.lua index 19156b537..8cd454e8f 100644..100755 --- a/src/CheckBasicStyle.lua +++ b/src/CheckBasicStyle.lua @@ -266,14 +266,113 @@ end +--- Array of files to process. Filled from cmdline arguments +local ToProcess = {} + + + + + +--- Handlers for the command-line arguments +-- Maps flag => function +local CmdLineHandlers = +{ + -- "-f file" checks the specified file + ["-f"] = function (a_Args, a_Idx) + local fnam = a_Args[a_Idx + 1] + if not(fnam) then + error("Invalid flag: '-f' needs a filename following it.") + end + table.insert(ToProcess, fnam) + return a_Idx + 2 -- skip the filename in param parsing + end, + + -- "-g" checks files reported by git as being committed. + ["-g"] = function (a_Args, a_Idx) + local f = io.popen("git diff --cached --name-only --diff-filter=ACMR") + for fnam in f:lines() do + table.insert(ToProcess, fnam) + end + end, + + -- "-h" prints help and exits + ["-h"] = function (a_Args, a_Idx) + print([[ +Usage:") +"CheckBasicStyle [<options>] + +Available options: +-f <filename> - checks the specified filename +-g - checks files reported by Git as being committed +-h - prints this help and exits +-l <listfile> - checks all files listed in the specified listfile +-- - reads the list of files to check from stdin + +When no options are given, the script checks all files listed in the AllFiles.lst file. + +Only .cpp and .h files are ever checked. +]]) + os.exit(0) + end, + + -- "-l listfile" loads the list of files to check from the specified listfile + ["-l"] = function (a_Args, a_Idx) + local listFile = a_Args[a_Idx + 1] + if not(listFile) then + error("Invalid flag: '-l' needs a filename following it.") + end + for fnam in io.lines(listFile) do + table.insert(ToProcess, fnam) + end + return a_Idx + 2 -- Skip the listfile in param parsing + end, + + -- "--" reads the list of files from stdin + ["--"] = function (a_Args, a_Idx) + for fnam in io.lines() do + table.insert(ToProcess, fnam) + end + end, +} + + + + + -- Remove buffering from stdout, so that the output appears immediately in IDEs: io.stdout:setvbuf("no") --- Process all files in the AllFiles.lst file (generated by cmake): -for fnam in io.lines("AllFiles.lst") do +-- Parse the cmdline arguments to see what files to check: +local idx = 1 +while (arg[idx]) do + local handler = CmdLineHandlers[arg[idx]] + if not(handler) then + error("Unknown command-line argument #" .. idx .. ": " .. arg[idx]) + end + idx = handler(arg, idx) or (idx + 1) -- Call the handler, let it change the next index if it wants +end + + +-- By default process all files in the AllFiles.lst file (generated by cmake): +if not(arg[1]) then + for fnam in io.lines("AllFiles.lst") do + table.insert(ToProcess, fnam) + end +end + + + + + +-- Process the files in the list: +for _, fnam in ipairs(ToProcess) do ProcessItem(fnam) end + + + + -- Report final verdict: print("Number of violations found: " .. g_NumViolations) if (g_NumViolations > 0) then diff --git a/src/ChunkMap.cpp b/src/ChunkMap.cpp index 394dc703b..b84b8dc3d 100644 --- a/src/ChunkMap.cpp +++ b/src/ChunkMap.cpp @@ -1880,10 +1880,12 @@ void cChunkMap::DoExplosionAt(double a_ExplosionSize, double a_BlockX, double a_ if (ShouldDestroyBlocks) { cBlockArea area; - a_BlocksAffected.reserve(8 * ExplosionSizeInt * ExplosionSizeInt * ExplosionSizeInt); - - area.Read(m_World, bx - ExplosionSizeInt, (int)ceil(a_BlockX + ExplosionSizeInt), MinY, MaxY, bz - ExplosionSizeInt, (int)ceil(a_BlockZ + ExplosionSizeInt)); + if (!area.Read(m_World, bx - ExplosionSizeInt, (int)ceil(a_BlockX + ExplosionSizeInt), MinY, MaxY, bz - ExplosionSizeInt, (int)ceil(a_BlockZ + ExplosionSizeInt))) + { + return; + } + for (int x = -ExplosionSizeInt; x < ExplosionSizeInt; x++) { for (int y = -ExplosionSizeInt; y < ExplosionSizeInt; y++) diff --git a/src/Entities/Entity.cpp b/src/Entities/Entity.cpp index 51cee248e..b87cf51a6 100644 --- a/src/Entities/Entity.cpp +++ b/src/Entities/Entity.cpp @@ -247,7 +247,6 @@ void cEntity::TakeDamage(eDamageType a_DamageType, cEntity * a_Attacker, int a_R if (a_Attacker != nullptr) { Heading = a_Attacker->GetLookVector() * (a_Attacker->IsSprinting() ? 16 : 11); - Heading.y = 1.6; } TDI.Knockback = Heading * a_KnockbackAmount; diff --git a/src/Entities/Player.cpp b/src/Entities/Player.cpp index 2549a8481..7f625f5d4 100644 --- a/src/Entities/Player.cpp +++ b/src/Entities/Player.cpp @@ -1414,14 +1414,23 @@ bool cPlayer::HasPermission(const AString & a_Permission) AStringVector Split = StringSplit(a_Permission, "."); + // Iterate over all restrictions; if any matches, then return failure: + for (auto & Restriction: m_SplitRestrictions) + { + if (PermissionMatches(Split, Restriction)) + { + return false; + } + } // for Restriction - m_SplitRestrictions[] + // Iterate over all granted permissions; if any matches, then return success: - for (AStringVectorVector::const_iterator itr = m_SplitPermissions.begin(), end = m_SplitPermissions.end(); itr != end; ++itr) + for (auto & Permission: m_SplitPermissions) { - if (PermissionMatches(Split, *itr)) + if (PermissionMatches(Split, Permission)) { return true; } - } // for itr - m_SplitPermissions[] + } // for Permission - m_SplitPermissions[] // No granted permission matches return false; @@ -2169,15 +2178,24 @@ void cPlayer::LoadRank(void) RankMgr->UpdatePlayerName(m_UUID, m_PlayerName); } m_Permissions = RankMgr->GetPlayerPermissions(m_UUID); + m_Restrictions = RankMgr->GetPlayerRestrictions(m_UUID); RankMgr->GetRankVisuals(m_Rank, m_MsgPrefix, m_MsgSuffix, m_MsgNameColorCode); // Break up the individual permissions on each dot, into m_SplitPermissions: m_SplitPermissions.clear(); m_SplitPermissions.reserve(m_Permissions.size()); - for (AStringVector::const_iterator itr = m_Permissions.begin(), end = m_Permissions.end(); itr != end; ++itr) + for (auto & Permission: m_Permissions) + { + m_SplitPermissions.push_back(StringSplit(Permission, ".")); + } // for Permission - m_Permissions[] + + // Break up the individual restrictions on each dot, into m_SplitRestrictions: + m_SplitRestrictions.clear(); + m_SplitRestrictions.reserve(m_Restrictions.size()); + for (auto & Restriction: m_Restrictions) { - m_SplitPermissions.push_back(StringSplit(*itr, ".")); - } // for itr - m_Permissions[] + m_SplitRestrictions.push_back(StringSplit(Restriction, ".")); + } // for itr - m_Restrictions[] } diff --git a/src/Entities/Player.h b/src/Entities/Player.h index 3dae58dc1..4b8c01dc4 100644 --- a/src/Entities/Player.h +++ b/src/Entities/Player.h @@ -122,7 +122,7 @@ public: double GetEyeHeight(void) const; // tolua_export Vector3d GetEyePosition(void) const; // tolua_export virtual bool IsOnGround(void) const override { return m_bTouchGround; } - inline double GetStance(void) const { return GetPosY() + 1.62; } // tolua_export // TODO: Proper stance when crouching etc. + inline double GetStance(void) const { return m_Stance; } // tolua_export inline cInventory & GetInventory(void) { return m_Inventory; } // tolua_export inline const cInventory & GetInventory(void) const { return m_Inventory; } @@ -254,7 +254,10 @@ public: static bool PermissionMatches(const AStringVector & a_Permission, const AStringVector & a_Template); // Exported in ManualBindings with AString params /** Returns all the permissions that the player has assigned to them. */ - const AStringVector & GetPermissions(void) { return m_Permissions; } // Exported in ManualBindings.cpp + const AStringVector & GetPermissions(void) const { return m_Permissions; } // Exported in ManualBindings.cpp + + /** Returns all the restrictions that the player has assigned to them. */ + const AStringVector & GetRestrictions(void) const { return m_Restrictions; } // Exported in ManualBindings.cpp // tolua_begin @@ -500,10 +503,18 @@ protected: /** All the permissions that this player has, based on their rank. */ AStringVector m_Permissions; + /** All the restrictions that this player has, based on their rank. */ + AStringVector m_Restrictions; + /** All the permissions that this player has, based on their rank, split into individual dot-delimited parts. This is used mainly by the HasPermission() function to optimize the lookup. */ AStringVectorVector m_SplitPermissions; + /** All the restrictions that this player has, based on their rank, split into individual dot-delimited parts. + This is used mainly by the HasPermission() function to optimize the lookup. */ + AStringVectorVector m_SplitRestrictions; + + // Message visuals: AString m_MsgPrefix, m_MsgSuffix; AString m_MsgNameColorCode; diff --git a/src/Mobs/AggressiveMonster.cpp b/src/Mobs/AggressiveMonster.cpp index 526b39e39..d0fb79f6d 100644 --- a/src/Mobs/AggressiveMonster.cpp +++ b/src/Mobs/AggressiveMonster.cpp @@ -37,10 +37,7 @@ void cAggressiveMonster::InStateChasing(std::chrono::milliseconds a_Dt) } } - if (!IsMovingToTargetPosition()) - { - MoveToPosition(m_Target->GetPosition()); - } + MoveToPosition(m_Target->GetPosition()); } } @@ -100,7 +97,7 @@ void cAggressiveMonster::Attack(std::chrono::milliseconds a_Dt) { return; } - + // Setting this higher gives us more wiggle room for attackrate m_AttackInterval = 0.0; m_Target->TakeDamage(dtMobAttack, this, m_AttackDamage, 0); diff --git a/src/Mobs/CMakeLists.txt b/src/Mobs/CMakeLists.txt index 7a291dcf2..ffbcdf3ea 100644 --- a/src/Mobs/CMakeLists.txt +++ b/src/Mobs/CMakeLists.txt @@ -24,6 +24,7 @@ SET (SRCS Mooshroom.cpp PassiveAggressiveMonster.cpp PassiveMonster.cpp + Path.cpp Pig.cpp Rabbit.cpp Sheep.cpp @@ -62,6 +63,7 @@ SET (HDRS Ocelot.h PassiveAggressiveMonster.h PassiveMonster.h + Path.h Pig.h Rabbit.h Sheep.h diff --git a/src/Mobs/Monster.cpp b/src/Mobs/Monster.cpp index 55d83302a..7ced89e45 100644 --- a/src/Mobs/Monster.cpp +++ b/src/Mobs/Monster.cpp @@ -13,7 +13,7 @@ #include "../Chunk.h" #include "../FastRandom.h" - +#include "Path.h" @@ -74,8 +74,12 @@ cMonster::cMonster(const AString & a_ConfigName, eMonsterType a_MobType, const A , m_EMState(IDLE) , m_EMPersonality(AGGRESSIVE) , m_Target(nullptr) - , m_bMovingToDestination(false) + , m_Path(nullptr) + , m_IsFollowingPath(false) + , m_GiveUpCounter(0) + , m_TicksSinceLastPathReset(1000) , m_LastGroundHeight(POSY_TOINT) + , m_JumpCoolDown(0) , m_IdleInterval(0) , m_DestroyTimer(0) , m_MobType(a_MobType) @@ -94,8 +98,9 @@ cMonster::cMonster(const AString & a_ConfigName, eMonsterType a_MobType, const A , m_DropChanceLeggings(0.085f) , m_DropChanceBoots(0.085f) , m_CanPickUpLoot(true) + , m_TicksSinceLastDamaged(100) , m_BurnsInDaylight(false) - , m_RelativeWalkSpeed(1.0) + , m_RelativeWalkSpeed(1) { if (!a_ConfigName.empty()) { @@ -116,137 +121,163 @@ void cMonster::SpawnOn(cClientHandle & a_Client) -void cMonster::TickPathFinding() +bool cMonster::TickPathFinding(cChunk & a_Chunk) { - const int PosX = POSX_TOINT; - const int PosY = POSY_TOINT; - const int PosZ = POSZ_TOINT; - - std::vector<Vector3d> m_PotentialCoordinates; - m_TraversedCoordinates.push_back(Vector3i(PosX, PosY, PosZ)); - - static const struct // Define which directions to try to move to + if (!m_IsFollowingPath) { - int x, z; - } gCrossCoords[] = + return false; + } + if (m_TicksSinceLastPathReset < 1000) { - { 1, 0}, - {-1, 0}, - { 0, 1}, - { 0, -1}, - } ; - - if ((PosY - 1 < 0) || (PosY + 2 >= cChunkDef::Height) /* PosY + 1 will never be true if PosY + 2 is not */) + // No need to count beyond 1000. 1000 is arbitary here. + ++m_TicksSinceLastPathReset; + } + + if (ReachedFinalDestination()) { - // Too low/high, can't really do anything - FinishPathFinding(); - return; + StopMovingToPosition(); + return false; } - for (size_t i = 0; i < ARRAYCOUNT(gCrossCoords); i++) + if ((m_FinalDestination - m_PathFinderDestination).Length() > 0.25) // if the distance between where we're going and where we should go is too big. { - if (IsCoordinateInTraversedList(Vector3i(gCrossCoords[i].x + PosX, PosY, gCrossCoords[i].z + PosZ))) + /* If we reached the last path waypoint, + Or if we haven't re-calculated for too long. + Interval is proportional to distance squared. (Recalculate lots when close, calculate rarely when far) */ + if ( + ((GetPosition() - m_PathFinderDestination).Length() < 0.25) || + m_TicksSinceLastPathReset > (0.15 * (m_FinalDestination - GetPosition()).SqrLength()) + ) { - continue; + ResetPathFinding(); } + } - BLOCKTYPE BlockAtY = m_World->GetBlock(gCrossCoords[i].x + PosX, PosY, gCrossCoords[i].z + PosZ); - BLOCKTYPE BlockAtYP = m_World->GetBlock(gCrossCoords[i].x + PosX, PosY + 1, gCrossCoords[i].z + PosZ); - BLOCKTYPE BlockAtYPP = m_World->GetBlock(gCrossCoords[i].x + PosX, PosY + 2, gCrossCoords[i].z + PosZ); - int LowestY = FindFirstNonAirBlockPosition(gCrossCoords[i].x + PosX, gCrossCoords[i].z + PosZ); - BLOCKTYPE BlockAtLowestY = (LowestY >= cChunkDef::Height) ? E_BLOCK_AIR : m_World->GetBlock(gCrossCoords[i].x + PosX, LowestY, gCrossCoords[i].z + PosZ); + if (m_Path == nullptr) + { + m_PathFinderDestination = m_FinalDestination; + m_Path = new cPath(a_Chunk, GetPosition().Floor(), m_PathFinderDestination.Floor(), 20); + } - if ( - (!cBlockInfo::IsSolid(BlockAtY)) && - (!cBlockInfo::IsSolid(BlockAtYP)) && - (!IsBlockLava(BlockAtLowestY)) && - (BlockAtLowestY != E_BLOCK_CACTUS) && - (PosY - LowestY < FALL_DAMAGE_HEIGHT) - ) + switch (m_Path->Step(a_Chunk)) + { + case ePathFinderStatus::PATH_NOT_FOUND: { - m_PotentialCoordinates.push_back(Vector3d((gCrossCoords[i].x + PosX), PosY, gCrossCoords[i].z + PosZ)); + StopMovingToPosition(); // Give up pathfinding to that destination. + break; } - else if ( - (cBlockInfo::IsSolid(BlockAtY)) && - (BlockAtY != E_BLOCK_CACTUS) && - (!cBlockInfo::IsSolid(BlockAtYP)) && - (!cBlockInfo::IsSolid(BlockAtYPP)) && - (BlockAtY != E_BLOCK_FENCE) && - (BlockAtY != E_BLOCK_FENCE_GATE) - ) + case ePathFinderStatus::CALCULATING: { - m_PotentialCoordinates.push_back(Vector3d((gCrossCoords[i].x + PosX), PosY + 1, gCrossCoords[i].z + PosZ)); + // Pathfinder needs more time + break; + } + case ePathFinderStatus::PATH_FOUND: + { + if (--m_GiveUpCounter == 0) + { + ResetPathFinding(); // Try to calculate a path again. + return false; + } + else if (!m_Path->IsLastPoint() && (m_Path->IsFirstPoint() || ReachedNextWaypoint())) // Have we arrived at the next cell, as denoted by m_NextWayPointPosition? + { + m_NextWayPointPosition = Vector3d(0.5, 0, 0.5) + m_Path->GetNextPoint(); + m_GiveUpCounter = 40; // Give up after 40 ticks (2 seconds) if failed to reach m_NextWayPointPosition. + } + return true; } } - if (!m_PotentialCoordinates.empty()) + return false; +} + + + + + +void cMonster::MoveToWayPoint(cChunk & a_Chunk) +{ + if (m_JumpCoolDown == 0) { - Vector3f ShortestCoords = m_PotentialCoordinates.front(); - for (std::vector<Vector3d>::const_iterator itr = m_PotentialCoordinates.begin(); itr != m_PotentialCoordinates.end(); ++itr) + // We're not moving (or barely moving), and waypoint is above us, it means we are hitting something and we should jump. + if ((GetSpeedX() < 0.1) && (GetSpeedZ() < 0.1) && DoesPosYRequireJump(FloorC(m_NextWayPointPosition.y))) { - Vector3f Distance = m_FinalDestination - ShortestCoords; - Vector3f Distance2 = m_FinalDestination - *itr; - if (Distance.SqrLength() > Distance2.SqrLength()) + if (IsOnGround() || IsSwimming()) { - ShortestCoords = *itr; + m_bOnGround = false; + m_JumpCoolDown = 20; + // TODO: Change to AddSpeedY once collision detection is fixed - currently, mobs will go into blocks attempting to jump without a teleport + AddPosY(1.6); // Jump!! + SetSpeedX(3.2 * (m_NextWayPointPosition.x - GetPosition().x)); // Move forward in a preset speed. + SetSpeedZ(3.2 * (m_NextWayPointPosition.z - GetPosition().z)); // The numbers were picked based on trial and error and 1.6 and 3.2 are perfect. } } - - m_Destination = ShortestCoords; - m_Destination.z += 0.5f; - m_Destination.x += 0.5f; } else { - FinishPathFinding(); + --m_JumpCoolDown; } -} - - + Vector3d Distance = m_NextWayPointPosition - GetPosition(); + if ((Distance.x != 0) || (Distance.z != 0)) + { + Distance.y = 0; + Distance.Normalize(); + if (m_bOnGround) + { + Distance *= 2.5f; + } + else if (IsSwimming()) + { + Distance *= 1.3f; + } + else + { + // Don't let the mob move too much if he's falling. + Distance *= 0.25f; + } + // Apply walk speed: + Distance *= m_RelativeWalkSpeed; + /* Reduced default speed. + Close to Vanilla, easier for mobs to follow m_NextWayPointPositions, hence + better pathfinding. */ + Distance *= 0.5; + AddSpeedX(Distance.x); + AddSpeedZ(Distance.z); + } +} -void cMonster::MoveToPosition(const Vector3d & a_Position) -{ - FinishPathFinding(); - m_FinalDestination = a_Position; - m_bMovingToDestination = true; - TickPathFinding(); -} -bool cMonster::IsCoordinateInTraversedList(Vector3i a_Coords) +void cMonster::MoveToPosition(const Vector3d & a_Position) { - return (std::find(m_TraversedCoordinates.begin(), m_TraversedCoordinates.end(), a_Coords) != m_TraversedCoordinates.end()); + m_FinalDestination = a_Position; + m_IsFollowingPath = true; } -bool cMonster::ReachedDestination() +void cMonster::StopMovingToPosition() { - if ((m_Destination - GetPosition()).Length() < 0.5f) - { - return true; - } - - return false; + m_IsFollowingPath = false; } -bool cMonster::ReachedFinalDestination() + +void cMonster::ResetPathFinding(void) { - if ((GetPosition() - m_FinalDestination).Length() <= m_AttackRange) + m_TicksSinceLastPathReset = 0; + if (m_Path != nullptr) { - return true; + delete m_Path; + m_Path = nullptr; } - - return false; } @@ -256,6 +287,7 @@ bool cMonster::ReachedFinalDestination() void cMonster::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) { super::Tick(a_Dt, a_Chunk); + GET_AND_VERIFY_CURRENT_CHUNK(Chunk, POSX_TOINT, POSZ_TOINT); if (m_Health <= 0) { @@ -268,73 +300,28 @@ void cMonster::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) return; } + if (m_TicksSinceLastDamaged < 100) + { + ++m_TicksSinceLastDamaged; + } if ((m_Target != nullptr) && m_Target->IsDestroyed()) { m_Target = nullptr; } - // Burning in daylight - HandleDaylightBurning(a_Chunk); - - if (m_bMovingToDestination) + // Process the undead burning in daylight + HandleDaylightBurning(*Chunk, WouldBurnAt(GetPosition(), *Chunk)); + if (TickPathFinding(*Chunk)) { - if (m_bOnGround) + if (m_BurnsInDaylight && WouldBurnAt(m_NextWayPointPosition, *Chunk->GetNeighborChunk(FloorC(m_NextWayPointPosition.x), FloorC(m_NextWayPointPosition.z))) && !IsOnFire() && (m_TicksSinceLastDamaged == 100)) { - if (DoesPosYRequireJump((int)floor(m_Destination.y))) - { - m_bOnGround = false; - - // TODO: Change to AddSpeedY once collision detection is fixed - currently, mobs will go into blocks attempting to jump without a teleport - AddPosY(1.2); // Jump!! - } - } - - Vector3d Distance = m_Destination - GetPosition(); - if (!ReachedDestination() && !ReachedFinalDestination()) // If we haven't reached any sort of destination, move - { - Distance.y = 0; - Distance.Normalize(); - - if (m_bOnGround) - { - Distance *= 2.5f; - } - else if (IsSwimming()) - { - Distance *= 1.3f; - } - else - { - // Don't let the mob move too much if he's falling. - Distance *= 0.25f; - } - - // Apply walk speed: - Distance *= m_RelativeWalkSpeed; - - AddSpeedX(Distance.x); - AddSpeedZ(Distance.z); - - // It's too buggy! - /* - if (m_EMState == ESCAPING) - { - // Runs Faster when escaping :D otherwise they just walk away - SetSpeedX (GetSpeedX() * 2.f); - SetSpeedZ (GetSpeedZ() * 2.f); - } - */ + // If we burn in daylight, and we would burn at the next step, and we won't burn where we are right now, and we weren't provoked recently: + StopMovingToPosition(); + m_GiveUpCounter = 40; // This doesn't count as giving up, keep the giveup timer as is. } else { - if (ReachedFinalDestination()) // If we have reached the ultimate, final destination, stop pathfinding and attack if appropriate - { - FinishPathFinding(); - } - else - { - TickPathFinding(); // We have reached the next point in our path, calculate another point - } + MoveToWayPoint(*Chunk); } } @@ -345,13 +332,13 @@ void cMonster::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) { case IDLE: { - // If enemy passive we ignore checks for player visibility + // If enemy passive we ignore checks for player visibility. InStateIdle(a_Dt); break; } case CHASING: { - // If we do not see a player anymore skip chasing action + // If we do not see a player anymore skip chasing action. InStateChasing(a_Dt); break; } @@ -360,7 +347,6 @@ void cMonster::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) InStateEscaping(a_Dt); break; } - case ATTACKING: break; } // switch (m_EMState) @@ -370,6 +356,7 @@ void cMonster::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) + void cMonster::SetPitchAndYawFromDestination() { Vector3d FinalDestination = m_FinalDestination; @@ -377,38 +364,36 @@ void cMonster::SetPitchAndYawFromDestination() { if (m_Target->IsPlayer()) { - FinalDestination.y = ((cPlayer *)m_Target)->GetStance(); + FinalDestination.y = static_cast<cPlayer *>(m_Target)->GetStance() - 1; } else { - FinalDestination.y = GetHeight(); + FinalDestination.y = m_Target->GetPosY() + GetHeight(); } } Vector3d Distance = FinalDestination - GetPosition(); - if (Distance.SqrLength() > 0.1f) { - { - double Rotation, Pitch; - Distance.Normalize(); - VectorToEuler(Distance.x, Distance.y, Distance.z, Rotation, Pitch); - SetHeadYaw(Rotation); - SetPitch(-Pitch); - } + double Rotation, Pitch; + Distance.Normalize(); + VectorToEuler(Distance.x, Distance.y, Distance.z, Rotation, Pitch); + SetHeadYaw(Rotation); + SetPitch(-Pitch); + } - { - Vector3d BodyDistance = m_Destination - GetPosition(); - double Rotation, Pitch; - Distance.Normalize(); - VectorToEuler(BodyDistance.x, BodyDistance.y, BodyDistance.z, Rotation, Pitch); - SetYaw(Rotation); - } + { + Vector3d BodyDistance = m_NextWayPointPosition - GetPosition(); + double Rotation, Pitch; + BodyDistance.Normalize(); + VectorToEuler(BodyDistance.x, BodyDistance.y, BodyDistance.z, Rotation, Pitch); + SetYaw(Rotation); } } + void cMonster::HandleFalling() { if (m_bOnGround) @@ -460,7 +445,6 @@ int cMonster::FindFirstNonAirBlockPosition(double a_PosX, double a_PosZ) - bool cMonster::DoTakeDamage(TakeDamageInfo & a_TDI) { if (!super::DoTakeDamage(a_TDI)) @@ -476,6 +460,7 @@ bool cMonster::DoTakeDamage(TakeDamageInfo & a_TDI) if (a_TDI.Attacker != nullptr) { m_Target = a_TDI.Attacker; + m_TicksSinceLastDamaged = 0; } return true; } @@ -641,7 +626,7 @@ void cMonster::EventLosePlayer(void) void cMonster::InStateIdle(std::chrono::milliseconds a_Dt) { - if (m_bMovingToDestination) + if (m_IsFollowingPath) { return; // Still getting there } @@ -661,14 +646,8 @@ void cMonster::InStateIdle(std::chrono::milliseconds a_Dt) if ((Dist.SqrLength() > 2) && (rem >= 3)) { Vector3d Destination(GetPosX() + Dist.x, 0, GetPosZ() + Dist.z); - - int NextHeight = FindFirstNonAirBlockPosition(Destination.x, Destination.z); - - if (IsNextYPosReachable(NextHeight)) - { - Destination.y = NextHeight; - MoveToPosition(Destination); - } + Destination.y = FindFirstNonAirBlockPosition(Destination.x, Destination.z); + MoveToPosition(Destination); } } } @@ -692,7 +671,7 @@ void cMonster::InStateChasing(std::chrono::milliseconds a_Dt) void cMonster::InStateEscaping(std::chrono::milliseconds a_Dt) { UNUSED(a_Dt); - + if (m_Target != nullptr) { Vector3d newloc = GetPosition(); @@ -771,7 +750,7 @@ AString cMonster::MobTypeToString(eMonsterType a_MobType) return g_MobTypeNames[i].m_lcName; } } - + // Not found: return ""; } @@ -866,7 +845,7 @@ cMonster::eFamily cMonster::FamilyFromType(eMonsterType a_Type) case mtWolf: return mfHostile; case mtZombie: return mfHostile; case mtZombiePigman: return mfHostile; - + case mtInvalidType: break; } ASSERT(!"Unhandled mob type"); @@ -1041,7 +1020,7 @@ void cMonster::AddRandomArmorDropItem(cItems & a_Drops, short a_LootingLevel) a_Drops.push_back(GetEquippedHelmet()); } } - + if (r1.randInt() % 200 < ((m_DropChanceChestplate * 200) + (a_LootingLevel * 2))) { if (!GetEquippedChestplate().IsEmpty()) @@ -1049,7 +1028,7 @@ void cMonster::AddRandomArmorDropItem(cItems & a_Drops, short a_LootingLevel) a_Drops.push_back(GetEquippedChestplate()); } } - + if (r1.randInt() % 200 < ((m_DropChanceLeggings * 200) + (a_LootingLevel * 2))) { if (!GetEquippedLeggings().IsEmpty()) @@ -1057,7 +1036,7 @@ void cMonster::AddRandomArmorDropItem(cItems & a_Drops, short a_LootingLevel) a_Drops.push_back(GetEquippedLeggings()); } } - + if (r1.randInt() % 200 < ((m_DropChanceBoots * 200) + (a_LootingLevel * 2))) { if (!GetEquippedBoots().IsEmpty()) @@ -1087,45 +1066,56 @@ void cMonster::AddRandomWeaponDropItem(cItems & a_Drops, short a_LootingLevel) -void cMonster::HandleDaylightBurning(cChunk & a_Chunk) +void cMonster::HandleDaylightBurning(cChunk & a_Chunk, bool WouldBurn) { if (!m_BurnsInDaylight) { return; } - + int RelY = POSY_TOINT; if ((RelY < 0) || (RelY >= cChunkDef::Height)) { // Outside the world return; } - - int RelX = POSX_TOINT - GetChunkX() * cChunkDef::Width; - int RelZ = POSZ_TOINT - GetChunkZ() * cChunkDef::Width; - if (!a_Chunk.IsLightValid()) { m_World->QueueLightChunk(GetChunkX(), GetChunkZ()); return; } + if (!IsOnFire() && WouldBurn) + { + // Burn for 100 ticks, then decide again + StartBurning(100); + } +} + + + + +bool cMonster::WouldBurnAt(Vector3d a_Location, cChunk & a_Chunk) +{ + int RelX = FloorC(a_Location.x) - a_Chunk.GetPosX() * cChunkDef::Width; + int RelY = FloorC(a_Location.y); + int RelZ = FloorC(a_Location.z) - a_Chunk.GetPosZ() * cChunkDef::Width; if ( (a_Chunk.GetSkyLight(RelX, RelY, RelZ) == 15) && // In the daylight (a_Chunk.GetBlock(RelX, RelY, RelZ) != E_BLOCK_SOULSAND) && // Not on soulsand (GetWorld()->GetTimeOfDay() < (12000 + 1000)) && // It is nighttime - !IsOnFire() && // Not already burning GetWorld()->IsWeatherSunnyAt(POSX_TOINT, POSZ_TOINT) // Not raining ) { - // Burn for 100 ticks, then decide again - StartBurning(100); + return true; } + return false; } + cMonster::eFamily cMonster::GetMobFamily(void) const { return FamilyFromType(m_MobType); diff --git a/src/Mobs/Monster.h b/src/Mobs/Monster.h index 21ed0c25a..c7f38c9f7 100644 --- a/src/Mobs/Monster.h +++ b/src/Mobs/Monster.h @@ -10,11 +10,12 @@ - - class cClientHandle; class cWorld; +// Fwd: cPath +enum class ePathFinderStatus; +class cPath; @@ -60,8 +61,9 @@ public: virtual void OnRightClicked(cPlayer & a_Player) override; + /** Engage pathfinder and tell it to calculate a path to a given position, and move the mobile accordingly + Currently, the mob will only start moving to a new position after the position it is currently going to is reached. */ virtual void MoveToPosition(const Vector3d & a_Position); // tolua_export - virtual bool ReachedDestination(void); // tolua_begin eMonsterType GetMobType(void) const { return m_MobType; } @@ -158,19 +160,25 @@ public: protected: - /* ======= PATHFINDING ======= */ - /** A pointer to the entity this mobile is aiming to reach */ cEntity * m_Target; + cPath * m_Path; // TODO unique ptr + + /** Stores if mobile is currently moving towards the ultimate, final destination */ + bool m_IsFollowingPath; + + /* If 0, will give up reaching the next m_NextWayPointPosition and will re-compute path. */ + int m_GiveUpCounter; + int m_TicksSinceLastPathReset; + /** Coordinates of the next position that should be reached */ - Vector3d m_Destination; + Vector3d m_NextWayPointPosition; + /** Coordinates for the ultimate, final destination. */ Vector3d m_FinalDestination; - /** Returns if the ultimate, final destination has been reached */ - bool ReachedFinalDestination(void); - /** Stores if mobile is currently moving towards the ultimate, final destination */ - bool m_bMovingToDestination; + /** Coordinates for the ultimate, final destination last given to the pathfinder. */ + Vector3d m_PathFinderDestination; /** Finds the lowest non-air block position (not the highest, as cWorld::GetHeight does) If current Y is nonsolid, goes down to try to find a solid block, then returns that + 1 @@ -178,44 +186,40 @@ protected: If no suitable position is found, returns cChunkDef::Height. */ int FindFirstNonAirBlockPosition(double a_PosX, double a_PosZ); - /** Returns if a monster can actually reach a given height by jumping or walking */ - inline bool IsNextYPosReachable(int a_PosY) - { - return ( - (a_PosY <= POSY_TOINT) || - DoesPosYRequireJump(a_PosY) - ); - } + /** Returns if the ultimate, final destination has been reached */ + bool ReachedFinalDestination(void) { return ((m_FinalDestination - GetPosition()).SqrLength() < (m_AttackRange * m_AttackRange)); } + + /** Returns if the intermediate waypoint of m_NextWayPointPosition has been reached */ + bool ReachedNextWaypoint(void) { return ((m_NextWayPointPosition - GetPosition()).SqrLength() < 0.25); } + /** Returns if a monster can reach a given height by jumping */ inline bool DoesPosYRequireJump(int a_PosY) { return ((a_PosY > POSY_TOINT) && (a_PosY == POSY_TOINT + 1)); } - /** A semi-temporary list to store the traversed coordinates during active pathfinding so we don't visit them again */ - std::vector<Vector3i> m_TraversedCoordinates; - /** Returns if coordinate is in the traversed list */ - bool IsCoordinateInTraversedList(Vector3i a_Coords); + /** Finds the next place to go by calculating a path and setting the m_NextWayPointPosition variable for the next block to head to + This is based on the ultimate, final destination and the current position, as well as the A* algorithm, and any environmental hazards + Returns if a path is ready, and therefore if the mob should move to m_NextWayPointPosition + */ + bool TickPathFinding(cChunk & a_Chunk); + void MoveToWayPoint(cChunk & a_Chunk); + + /** Resets a pathfinding task, be it due to failure or something else + Resets the pathfinder. If m_IsFollowingPath is true, TickPathFinding starts a brand new path. + Should only be called by the pathfinder, cMonster::Tick or StopMovingToPosition. */ + void ResetPathFinding(void); + + /** Stops pathfinding + Calls ResetPathFinding and sets m_IsFollowingPath to false */ + void StopMovingToPosition(); - /** Finds the next place to go - This is based on the ultimate, final destination and the current position, as well as the traversed coordinates, and any environmental hazards */ - void TickPathFinding(void); - /** Finishes a pathfinding task, be it due to failure or something else */ - inline void FinishPathFinding(void) - { - m_TraversedCoordinates.clear(); - m_bMovingToDestination = false; - } /** Sets the body yaw and head yaw/pitch based on next/ultimate destinations */ void SetPitchAndYawFromDestination(void); - /* =========================== */ - /* ========= FALLING ========= */ - virtual void HandleFalling(void); int m_LastGroundHeight; - - /* =========================== */ + int m_JumpCoolDown; std::chrono::milliseconds m_IdleInterval; std::chrono::milliseconds m_DestroyTimer; @@ -239,10 +243,11 @@ protected: float m_DropChanceLeggings; float m_DropChanceBoots; bool m_CanPickUpLoot; + int m_TicksSinceLastDamaged; // How many ticks ago we were last damaged by a player? - void HandleDaylightBurning(cChunk & a_Chunk); + void HandleDaylightBurning(cChunk & a_Chunk, bool WouldBurn); + bool WouldBurnAt(Vector3d a_Location, cChunk & a_Chunk); bool m_BurnsInDaylight; - double m_RelativeWalkSpeed; /** Adds a random number of a_Item between a_Min and a_Max to itemdrops a_Drops*/ diff --git a/src/Mobs/Path.cpp b/src/Mobs/Path.cpp new file mode 100644 index 000000000..8701dad10 --- /dev/null +++ b/src/Mobs/Path.cpp @@ -0,0 +1,383 @@ +#include "Globals.h" + +#include <cmath> + +#include "Path.h" +#include "../Chunk.h" + +#define DISTANCE_MANHATTAN 0 // 1: More speed, a bit less accuracy 0: Max accuracy, less speed. +#define HEURISTICS_ONLY 0 // 1: Much more speed, much less accurate. +#define CALCULATIONS_PER_STEP 60 // Higher means more CPU load but faster path calculations. +// The only version which guarantees the shortest path is 0, 0. + +enum class eCellStatus {OPENLIST, CLOSEDLIST, NOLIST}; +struct cPathCell +{ + Vector3i m_Location; // Location of the cell in the world. + int m_F, m_G, m_H; // F, G, H as defined in regular A*. + eCellStatus m_Status; // Which list is the cell in? Either non, open, or closed. + cPathCell * m_Parent; // Cell's parent, as defined in regular A*. + bool m_IsSolid; // Is the cell an air or a solid? Partial solids are currently considered solids. +}; + + + + + +bool compareHeuristics::operator()(cPathCell * & a_Cell1, cPathCell * & a_Cell2) +{ + return a_Cell1->m_F > a_Cell2->m_F; +} + + + + + +/* cPath implementation */ +cPath::cPath( + cChunk & a_Chunk, + const Vector3i & a_StartingPoint, const Vector3i & a_EndingPoint, int a_MaxSteps, + double a_BoundingBoxWidth, double a_BoundingBoxHeight, + int a_MaxUp, int a_MaxDown +) : + m_Destination(a_EndingPoint.Floor()), + m_Source(a_StartingPoint.Floor()), + m_CurrentPoint(0), // GetNextPoint increments this to 1, but that's fine, since the first cell is always a_StartingPoint + m_Chunk(&a_Chunk) +{ + // TODO: if src not walkable OR dest not walkable, then abort. + // Borrow a new "isWalkable" from ProcessIfWalkable, make ProcessIfWalkable also call isWalkable + + if (GetCell(m_Source)->m_IsSolid || GetCell(m_Destination)->m_IsSolid) + { + m_Status = ePathFinderStatus::PATH_NOT_FOUND; + return; + } + + // If destination in water, set water surface as destination. + cChunk * Chunk = m_Chunk->GetNeighborChunk(m_Destination.x, m_Destination.z); + if ((Chunk != nullptr) && Chunk->IsValid()) + { + BLOCKTYPE BlockType; + NIBBLETYPE BlockMeta; + int RelX = m_Destination.x - m_Chunk->GetPosX() * cChunkDef::Width; + int RelZ = m_Destination.z - m_Chunk->GetPosZ() * cChunkDef::Width; + bool inwater = false; + for (;;) + { + m_Chunk->GetBlockTypeMeta(RelX, m_Destination.y, RelZ, BlockType, BlockMeta); + if (BlockType != E_BLOCK_STATIONARY_WATER) + { + break; + } + inwater = true; + m_Destination+=Vector3d(0, 1, 0); + } + if (inwater) + { + m_Destination+=Vector3d(0, -1, 0); + } + } + + m_Status = ePathFinderStatus::CALCULATING; + m_StepsLeft = a_MaxSteps; + + ProcessCell(GetCell(a_StartingPoint), nullptr, 0); + m_Chunk = nullptr; +} + + + + + +cPath::~cPath() +{ + if (m_Status == ePathFinderStatus::CALCULATING) + { + FinishCalculation(); + } +} + + + + + +ePathFinderStatus cPath::Step(cChunk & a_Chunk) +{ + m_Chunk = &a_Chunk; + + if (m_Status != ePathFinderStatus::CALCULATING) + { + return m_Status; + } + + if (m_StepsLeft == 0) + { + FinishCalculation(ePathFinderStatus::PATH_NOT_FOUND); + } + else + { + --m_StepsLeft; + int i; + for (i = 0; i < CALCULATIONS_PER_STEP; ++i) + { + if (Step_Internal()) // Step_Internal returns true when no more calculation is needed. + { + break; // if we're here, m_Status must have changed either to PATH_FOUND or PATH_NOT_FOUND. + } + } + } + + m_Chunk = nullptr; + return m_Status; +} + + + + + +bool cPath::IsSolid(const Vector3i & a_Location) +{ + ASSERT(m_Chunk != nullptr); + + auto Chunk = m_Chunk->GetNeighborChunk(a_Location.x, a_Location.z); + if ((Chunk == nullptr) || !Chunk->IsValid()) + { + return true; + } + m_Chunk = Chunk; + + BLOCKTYPE BlockType; + NIBBLETYPE BlockMeta; + int RelX = a_Location.x - m_Chunk->GetPosX() * cChunkDef::Width; + int RelZ = a_Location.z - m_Chunk->GetPosZ() * cChunkDef::Width; + + m_Chunk->GetBlockTypeMeta(RelX, a_Location.y, RelZ, BlockType, BlockMeta); + if ((BlockType == E_BLOCK_FENCE) || (BlockType == E_BLOCK_FENCE_GATE)) + { + GetCell(a_Location + Vector3i(0, 1, 0))->m_IsSolid = true; // Mobs will always think that the fence is 2 blocks high and therefore won't jump over. + } + if (BlockType == E_BLOCK_STATIONARY_WATER) + { + GetCell(a_Location + Vector3i(0, -1, 0))->m_IsSolid = true; // Mobs will always think that the fence is 2 blocks high and therefore won't jump over. + } + + return cBlockInfo::IsSolid(BlockType); +} + + + + + +bool cPath::Step_Internal() +{ + cPathCell * CurrentCell = OpenListPop(); + + // Path not reachable, open list exauhsted. + if (CurrentCell == nullptr) + { + FinishCalculation(ePathFinderStatus::PATH_NOT_FOUND); + ASSERT(m_Status == ePathFinderStatus::PATH_NOT_FOUND); + return true; + } + + // Path found. + if (CurrentCell->m_Location == m_Destination) + { + do + { + m_PathPoints.push_back(CurrentCell->m_Location); // Populate the cPath with points. + CurrentCell = CurrentCell->m_Parent; + } while (CurrentCell != nullptr); + + FinishCalculation(ePathFinderStatus::PATH_FOUND); + return true; + } + + // Calculation not finished yet, process a currentCell by inspecting all neighbors. + + // Check North, South, East, West on all 3 different heights. + int i; + for (i = -1; i <= 1; ++i) + { + ProcessIfWalkable(CurrentCell->m_Location + Vector3i(1, i, 0), CurrentCell, 10); + ProcessIfWalkable(CurrentCell->m_Location + Vector3i(-1, i, 0), CurrentCell, 10); + ProcessIfWalkable(CurrentCell->m_Location + Vector3i(0, i, 1), CurrentCell, 10); + ProcessIfWalkable(CurrentCell->m_Location + Vector3i(0, i, -1), CurrentCell, 10); + } + + // Check diagonals on mob's height only. + int x, z; + for (x = -1; x <= 1; x += 2) + { + for (z = -1; z <= 1; z += 2) + { + // This condition prevents diagonal corner cutting. + if (!GetCell(CurrentCell->m_Location + Vector3i(x, 0, 0))->m_IsSolid && !GetCell(CurrentCell->m_Location + Vector3i(0, 0, z))->m_IsSolid) + { + // This prevents falling of "sharp turns" e.g. a 1x1x20 rectangle in the air which breaks in a right angle suddenly. + if (GetCell(CurrentCell->m_Location + Vector3i(x, -1, 0))->m_IsSolid && GetCell(CurrentCell->m_Location + Vector3i(0, -1, z))->m_IsSolid) + { + ProcessIfWalkable(CurrentCell->m_Location + Vector3i(x, 0, z), CurrentCell, 14); // 14 is a good enough approximation of sqrt(10 + 10). + } + } + } + } + + return false; +} + + + + + +void cPath::FinishCalculation() +{ + for (auto && pair : m_Map) + { + delete pair.second; + } + + m_Map.clear(); + m_OpenList.empty(); +} + + + + + +void cPath::FinishCalculation(ePathFinderStatus a_NewStatus) +{ + m_Status = a_NewStatus; + FinishCalculation(); +} + + + + + +void cPath::OpenListAdd(cPathCell * a_Cell) +{ + a_Cell->m_Status = eCellStatus::OPENLIST; + m_OpenList.push(a_Cell); + #ifdef COMPILING_PATHFIND_DEBUGGER + si::setBlock(a_Cell->m_Location.x, a_Cell->m_Location.y, a_Cell->m_Location.z, debug_open, SetMini(a_Cell)); + #endif +} + + + + + +cPathCell * cPath::OpenListPop() // Popping from the open list also means adding to the closed list. +{ + if (m_OpenList.size() == 0) + { + return nullptr; // We've exhausted the search space and nothing was found, this will trigger a PATH_NOT_FOUND status. + } + + cPathCell * Ret = m_OpenList.top(); + m_OpenList.pop(); + Ret->m_Status = eCellStatus::CLOSEDLIST; + #ifdef COMPILING_PATHFIND_DEBUGGER +si::setBlock((Ret)->m_Location.x, (Ret)->m_Location.y, (Ret)->m_Location.z, debug_closed, SetMini(Ret)); + #endif + return Ret; +} + + + + + +void cPath::ProcessIfWalkable(const Vector3i & a_Location, cPathCell * a_Parent, int a_Cost) +{ + cPathCell * cell = GetCell(a_Location); + if (!cell->m_IsSolid && GetCell(a_Location + Vector3i(0, -1, 0))->m_IsSolid && !GetCell(a_Location + Vector3i(0, 1, 0))->m_IsSolid) + { + ProcessCell(cell, a_Parent, a_Cost); + } +} + + + + + +void cPath::ProcessCell(cPathCell * a_Cell, cPathCell * a_Caller, int a_GDelta) +{ + // Case 1: Cell is in the closed list, ignore it. + if (a_Cell->m_Status == eCellStatus::CLOSEDLIST) + { + return; + } + if (a_Cell->m_Status == eCellStatus::NOLIST) // Case 2: The cell is not in any list. + { + // Cell is walkable, add it to the open list. + // Note that non-walkable cells are filtered out in Step_internal(); + // Special case: Start cell goes here, gDelta is 0, caller is NULL. + a_Cell->m_Parent = a_Caller; + if (a_Caller != nullptr) + { + a_Cell->m_G = a_Caller->m_G + a_GDelta; + } + else + { + a_Cell->m_G = 0; + } + + // Calculate H. This is A*'s Heuristics value. + #if DISTANCE_MANHATTAN == 1 + // Manhattan distance. DeltaX + DeltaY + DeltaZ. + a_Cell->m_H = 10 * (abs(a_Cell->m_Location.x-m_Destination.x) + abs(a_Cell->m_Location.y-m_Destination.y) + abs(a_Cell->m_Location.z-m_Destination.z)); + #else + // Euclidian distance. sqrt(DeltaX^2 + DeltaY^2 + DeltaZ^2), more precise. + a_Cell->m_H = static_cast<decltype(a_Cell->m_H)>((a_Cell->m_Location - m_Destination).Length() * 10); + #endif + + #if HEURISTICS_ONLY == 1 + a_Cell->m_F = a_Cell->m_H; // Greedy search. https://en.wikipedia.org/wiki/Greedy_search + #else + a_Cell->m_F = a_Cell->m_H + a_Cell->m_G; // Regular A*. + #endif + + OpenListAdd(a_Cell); + return; + } + + // Case 3: Cell is in the open list, check if G and H need an update. + int NewG = a_Caller->m_G + a_GDelta; + if (NewG < a_Cell->m_G) + { + a_Cell->m_G = NewG; + a_Cell->m_H = a_Cell->m_F + a_Cell->m_G; + a_Cell->m_Parent = a_Caller; + } + +} + + + + + +cPathCell * cPath::GetCell(const Vector3i & a_Location) +{ + // Create the cell in the hash table if it's not already there. + cPathCell * Cell; + if (m_Map.count(a_Location) == 0) // Case 1: Cell is not on any list. We've never checked this cell before. + { + Cell = new cPathCell(); + Cell->m_Location = a_Location; + m_Map[a_Location] = Cell; + Cell->m_IsSolid = IsSolid(a_Location); + Cell->m_Status = eCellStatus::NOLIST; + #ifdef COMPILING_PATHFIND_DEBUGGER + #ifdef COMPILING_PATHFIND_DEBUGGER_MARK_UNCHECKED + si::setBlock(a_Location.x, a_Location.y, a_Location.z, debug_unchecked, Cell->m_IsSolid ? NORMAL : MINI); + #endif + #endif + return Cell; + } + else + { + return m_Map[a_Location]; + } +} diff --git a/src/Mobs/Path.h b/src/Mobs/Path.h new file mode 100644 index 000000000..9e893f1d7 --- /dev/null +++ b/src/Mobs/Path.h @@ -0,0 +1,150 @@ +#pragma once + +/* Wanna use the pathfinder? Put this in your header file: + +// Fwd: cPath +enum class ePathFinderStatus; +class cPath; + +Put this in your .cpp: +#include "...Path.h" +*/ + +#ifdef COMPILING_PATHFIND_DEBUGGER + /* Note: the COMPILING_PATHFIND_DEBUGGER flag is used by Native/WiseOldMan95 to debug + this class outside of MCServer. This preprocessor flag is never set when compiling MCServer. */ + #include "PathFinderIrrlicht_Head.h" +#endif + +#include <unordered_map> + +//fwd: ../Chunk.h +class cChunk; + +/* Various little structs and classes */ +enum class ePathFinderStatus {CALCULATING, PATH_FOUND, PATH_NOT_FOUND}; +struct cPathCell; // Defined inside Path.cpp +class compareHeuristics +{ +public: + bool operator()(cPathCell * & a_V1, cPathCell * & a_V2); +}; + +class cPath +{ +public: + /** Creates a pathfinder instance. A Mob will probably need a single pathfinder instance for its entire life. + + Note that if you have a man-sized mob (1x1x2, zombies, etc), you are advised to call this function without parameters + because the declaration might change in later version of the pathFinder, and a parameter-less call always assumes a man-sized mob. + + If your mob is not man-sized, you are advised to use cPath(width, height), this would be compatible with future versions, + but please be aware that as of now those parameters will be ignored and your mob will be assumed to be man sized. + + @param a_BoundingBoxWidth the character's boundingbox width in blocks. Currently the parameter is ignored and 1 is assumed. + @param a_BoundingBoxHeight the character's boundingbox width in blocks. Currently the parameter is ignored and 2 is assumed. + @param a_MaxUp the character's max jump height in blocks. Currently the parameter is ignored and 1 is assumed. + @param a_MaxDown How far is the character willing to fall? Currently the parameter is ignored and 1 is assumed. */ + /** Attempts to find a path starting from source to destination. + After calling this, you are expected to call Step() once per tick or once per several ticks until it returns true. You should then call getPath() to obtain the path. + Calling this before a path is found resets the current path and starts another search. + @param a_StartingPoint The function expects this position to be the lowest block the mob is in, a rule of thumb: "The block where the Zombie's knees are at". + @param a_EndingPoint "The block where the Zombie's knees want to be". + @param a_MaxSteps The maximum steps before giving up. */ + cPath( + cChunk & a_Chunk, + const Vector3i & a_StartingPoint, const Vector3i & a_EndingPoint, int a_MaxSteps, + double a_BoundingBoxWidth = 1, double a_BoundingBoxHeight = 2, + int a_MaxUp = 1, int a_MaxDown = 1 + ); + + /** Destroys the path and frees its memory. */ + ~cPath(); + + /** Performs part of the path calculation and returns true if the path computation has finished. */ + ePathFinderStatus Step(cChunk & a_Chunk); + + /* Point retrieval functions, inlined for performance. */ + /** Returns the next point in the path. */ + inline Vector3i GetNextPoint() + { + ASSERT(m_Status == ePathFinderStatus::PATH_FOUND); + return m_PathPoints[m_PathPoints.size() - 1 - (++m_CurrentPoint)]; + } + /** Checks whether this is the last point or not. Never call getnextPoint when this is true. */ + inline bool IsLastPoint() + { + ASSERT(m_Status == ePathFinderStatus::PATH_FOUND); + return (m_CurrentPoint == m_PathPoints.size() - 1); + } + inline bool IsFirstPoint() + { + ASSERT(m_Status == ePathFinderStatus::PATH_FOUND); + return (m_CurrentPoint == 0); + } + /** Get the point at a_index. Remark: Internally, the indexes are reversed. */ + inline Vector3i GetPoint(size_t a_index) + { + ASSERT(m_Status == ePathFinderStatus::PATH_FOUND); + ASSERT(a_index < m_PathPoints.size()); + return m_PathPoints[m_PathPoints.size() - 1 - a_index]; + } + /** Returns the total number of points this path has. */ + inline int GetPointCount() + { + ASSERT(m_Status == ePathFinderStatus::PATH_FOUND); + return m_PathPoints.size(); + } + + struct VectorHasher + { + std::size_t operator()(const Vector3i & a_Vector) const + { + // Guaranteed to have no hash collisions for any 128x128x128 area. Suitable for pathfinding. + int32_t t = 0; + t += (int8_t)a_Vector.x; + t = t << 8; + t += (int8_t)a_Vector.y; + t = t << 8; + t += (int8_t)a_Vector.z; + t = t << 8; + return (size_t)t; + } + }; +private: + + /* General */ + bool IsSolid(const Vector3i & a_Location); // Query our hosting world and ask it if there's a solid at a_location. + bool Step_Internal(); // The public version just calls this version * CALCULATIONS_PER_CALL times. + void FinishCalculation(); // Clears the memory used for calculating the path. + void FinishCalculation(ePathFinderStatus a_NewStatus); // Clears the memory used for calculating the path and changes the status. + + /* Openlist and closedlist management */ + void OpenListAdd(cPathCell * a_Cell); + cPathCell * OpenListPop(); + void ProcessIfWalkable(const Vector3i &a_Location, cPathCell * a_Parent, int a_Cost); + + /* Map management */ + void ProcessCell(cPathCell * a_Cell, cPathCell * a_Caller, int a_GDelta); + cPathCell * GetCell(const Vector3i & a_location); + + /* Pathfinding fields */ + std::priority_queue<cPathCell *, std::vector<cPathCell *>, compareHeuristics> m_OpenList; + std::unordered_map<Vector3i, cPathCell *, VectorHasher> m_Map; + Vector3i m_Destination; + Vector3i m_Source; + int m_StepsLeft; + + /* Control fields */ + ePathFinderStatus m_Status; + + /* Final path fields */ + size_t m_CurrentPoint; + std::vector<Vector3i> m_PathPoints; + + /* Interfacing with the world */ + cChunk * m_Chunk; // Only valid inside Step()! + #ifdef COMPILING_PATHFIND_DEBUGGER + #include "../path_irrlicht.cpp" + #endif +}; diff --git a/src/Mobs/Pig.cpp b/src/Mobs/Pig.cpp index edd4d9de4..56d6abfd5 100644 --- a/src/Mobs/Pig.cpp +++ b/src/Mobs/Pig.cpp @@ -90,7 +90,6 @@ void cPig::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) if (m_Attachee->IsPlayer() && (m_Attachee->GetEquippedWeapon().m_ItemType == E_ITEM_CARROT_ON_STICK)) { MoveToPosition((m_Attachee->GetPosition()) + (m_Attachee->GetLookVector()*10)); - m_bMovingToDestination = true; } } } diff --git a/src/Mobs/Sheep.cpp b/src/Mobs/Sheep.cpp index c0cdec035..ec24f167e 100644 --- a/src/Mobs/Sheep.cpp +++ b/src/Mobs/Sheep.cpp @@ -98,7 +98,7 @@ void cSheep::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) if (m_TimeToStopEating > 0) { - m_bMovingToDestination = false; // The sheep should not move when he's eating + StopMovingToPosition(); m_TimeToStopEating--; if (m_TimeToStopEating == 0) diff --git a/src/Mobs/Skeleton.cpp b/src/Mobs/Skeleton.cpp index 331c8e8ad..f99404669 100644 --- a/src/Mobs/Skeleton.cpp +++ b/src/Mobs/Skeleton.cpp @@ -37,7 +37,7 @@ void cSkeleton::GetDrops(cItems & a_Drops, cEntity * a_Killer) else { AddRandomDropItem(a_Drops, 0, 2 + LootingLevel, E_ITEM_ARROW); - + } AddRandomDropItem(a_Drops, 0, 2 + LootingLevel, E_ITEM_BONE); AddRandomArmorDropItem(a_Drops, LootingLevel); @@ -48,25 +48,6 @@ void cSkeleton::GetDrops(cItems & a_Drops, cEntity * a_Killer) -void cSkeleton::MoveToPosition(const Vector3d & a_Position) -{ - // If the destination is sufficiently skylight challenged AND the skeleton isn't on fire then block the movement - if ( - !IsOnFire() && - (m_World->GetBlockSkyLight((int)floor(a_Position.x), (int)floor(a_Position.y), (int)floor(a_Position.z)) - m_World->GetSkyDarkness() > 8) - ) - { - m_bMovingToDestination = false; - return; - } - - super::MoveToPosition(a_Position); -} - - - - - void cSkeleton::Attack(std::chrono::milliseconds a_Dt) { m_AttackInterval += (static_cast<float>(a_Dt.count()) / 1000) * m_AttackRate; diff --git a/src/Mobs/Skeleton.h b/src/Mobs/Skeleton.h index 9c49c52fb..1b6ce4bf2 100644 --- a/src/Mobs/Skeleton.h +++ b/src/Mobs/Skeleton.h @@ -11,19 +11,18 @@ class cSkeleton : public cAggressiveMonster { typedef cAggressiveMonster super; - + public: cSkeleton(bool IsWither); CLASS_PROTODEF(cSkeleton) virtual void GetDrops(cItems & a_Drops, cEntity * a_Killer = nullptr) override; - virtual void MoveToPosition(const Vector3d & a_Position) override; virtual void Attack(std::chrono::milliseconds a_Dt) override; virtual void SpawnOn(cClientHandle & a_ClientHandle) override; virtual bool IsUndead(void) override { return true; } - + bool IsWither(void) const { return m_bIsWither; } private: diff --git a/src/Mobs/Villager.cpp b/src/Mobs/Villager.cpp index 6f647ac18..e4953d546 100644 --- a/src/Mobs/Villager.cpp +++ b/src/Mobs/Villager.cpp @@ -156,7 +156,7 @@ void cVillager::HandleFarmerPrepareFarmCrops() void cVillager::HandleFarmerTryHarvestCrops() { // Harvest the crops if the villager isn't moving and if the crops are closer then 2 blocks. - if (!m_bMovingToDestination && (GetPosition() - m_CropsPos).Length() < 2) + if (!m_IsFollowingPath && (GetPosition() - m_CropsPos).Length() < 2) { // Check if the blocks didn't change while the villager was walking to the coordinates. BLOCKTYPE CropBlock = m_World->GetBlock(m_CropsPos.x, m_CropsPos.y, m_CropsPos.z); diff --git a/src/Mobs/Wolf.cpp b/src/Mobs/Wolf.cpp index b3eefdf79..c66763f17 100644 --- a/src/Mobs/Wolf.cpp +++ b/src/Mobs/Wolf.cpp @@ -137,7 +137,7 @@ void cWolf::OnRightClicked(cPlayer & a_Player) } } } - + m_World->BroadcastEntityMetadata(*this); } @@ -203,7 +203,7 @@ void cWolf::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) } else if (IsSitting()) { - m_bMovingToDestination = false; + StopMovingToPosition(); } } diff --git a/src/Mobs/Zombie.cpp b/src/Mobs/Zombie.cpp index 63042e252..fa4ac855d 100644 --- a/src/Mobs/Zombie.cpp +++ b/src/Mobs/Zombie.cpp @@ -37,26 +37,3 @@ void cZombie::GetDrops(cItems & a_Drops, cEntity * a_Killer) AddRandomArmorDropItem(a_Drops, LootingLevel); AddRandomWeaponDropItem(a_Drops, LootingLevel); } - - - - - -void cZombie::MoveToPosition(const Vector3d & a_Position) -{ - // If the destination is sufficiently skylight challenged AND the skeleton isn't on fire then block the movement - if ( - !IsOnFire() && - (m_World->GetBlockSkyLight((int)floor(a_Position.x), (int)floor(a_Position.y), (int)floor(a_Position.z)) - m_World->GetSkyDarkness() > 8) - ) - { - m_bMovingToDestination = false; - return; - } - - super::MoveToPosition(a_Position); -} - - - - diff --git a/src/Mobs/Zombie.h b/src/Mobs/Zombie.h index 809c2a6fe..47a9f1904 100644 --- a/src/Mobs/Zombie.h +++ b/src/Mobs/Zombie.h @@ -10,17 +10,15 @@ class cZombie : public cAggressiveMonster { typedef cAggressiveMonster super; - + public: cZombie(bool a_IsVillagerZombie); CLASS_PROTODEF(cZombie) - - virtual void GetDrops(cItems & a_Drops, cEntity * a_Killer = nullptr) override; - virtual void MoveToPosition(const Vector3d & a_Position) override; + virtual void GetDrops(cItems & a_Drops, cEntity * a_Killer = nullptr) override; virtual bool IsUndead(void) override { return true; } - + bool IsVillagerZombie(void) const { return m_IsVillagerZombie; } bool IsConverting (void) const { return m_IsConverting; } diff --git a/src/OSSupport/File.h b/src/OSSupport/File.h index 6ee080480..dc6543180 100644 --- a/src/OSSupport/File.h +++ b/src/OSSupport/File.h @@ -124,9 +124,14 @@ public: /** Creates a new folder with the specified name. Returns true if successful. Path may be relative or absolute */ static bool CreateFolder(const AString & a_FolderPath); - /** Returns the entire contents of the specified file as a string. Returns empty string on error. */ + // tolua_end + + /** Returns the entire contents of the specified file as a string. Returns empty string on error. + Exported manually in ManualBindings.cpp due to #1914 - ToLua code doesn't work well with binary files. */ static AString ReadWholeFile(const AString & a_FileName); + // tolua_begin + /** Returns a_FileName with its extension changed to a_NewExt. a_FileName may contain path specification. */ static AString ChangeFileExt(const AString & a_FileName, const AString & a_NewExt); diff --git a/src/PolarSSL++/CMakeLists.txt b/src/PolarSSL++/CMakeLists.txt index 39d41292d..b11d16e33 100644 --- a/src/PolarSSL++/CMakeLists.txt +++ b/src/PolarSSL++/CMakeLists.txt @@ -37,6 +37,6 @@ if(NOT MSVC) add_library(PolarSSL++ ${SRCS} ${HDRS}) if (UNIX) - target_link_libraries(PolarSSL++ polarssl) + target_link_libraries(PolarSSL++ mbedtls) endif() endif() diff --git a/src/Protocol/Protocol17x.cpp b/src/Protocol/Protocol17x.cpp index 2bc58e7e5..8e7d526ef 100644 --- a/src/Protocol/Protocol17x.cpp +++ b/src/Protocol/Protocol17x.cpp @@ -1728,7 +1728,7 @@ void cProtocol172::HandlePacketStatusRequest(cByteBuffer & a_ByteBuffer) // Version: Json::Value Version; - Version["name"] = "1.7.2"; + Version["name"] = "MCServer 1.7.2"; Version["protocol"] = 4; // Players: @@ -3170,7 +3170,7 @@ void cProtocol176::HandlePacketStatusRequest(cByteBuffer & a_ByteBuffer) // Version: Json::Value Version; - Version["name"] = "1.7.6"; + Version["name"] = "MCServer 1.7.6"; Version["protocol"] = 5; // Players: diff --git a/src/Protocol/Protocol18x.cpp b/src/Protocol/Protocol18x.cpp index 0baae00de..4429ca683 100644 --- a/src/Protocol/Protocol18x.cpp +++ b/src/Protocol/Protocol18x.cpp @@ -1970,7 +1970,7 @@ void cProtocol180::HandlePacketStatusRequest(cByteBuffer & a_ByteBuffer) // Version: Json::Value Version; - Version["name"] = "1.8"; + Version["name"] = "MCServer 1.8"; Version["protocol"] = 47; // Players: @@ -2310,7 +2310,7 @@ void cProtocol180::HandlePacketPlayerPos(cByteBuffer & a_ByteBuffer) HANDLE_READ(a_ByteBuffer, ReadBEDouble, double, PosY); HANDLE_READ(a_ByteBuffer, ReadBEDouble, double, PosZ); HANDLE_READ(a_ByteBuffer, ReadBool, bool, IsOnGround); - m_Client->HandlePlayerPos(PosX, PosY, PosZ, PosY + 1.62, IsOnGround); + m_Client->HandlePlayerPos(PosX, PosY, PosZ, PosY + (m_Client->GetPlayer()->IsCrouched() ? 1.54 : 1.62), IsOnGround); } diff --git a/src/Protocol/ProtocolRecognizer.h b/src/Protocol/ProtocolRecognizer.h index d46d31cb1..13be9478f 100644 --- a/src/Protocol/ProtocolRecognizer.h +++ b/src/Protocol/ProtocolRecognizer.h @@ -18,7 +18,7 @@ // Adjust these if a new protocol is added or an old one is removed: -#define MCS_CLIENT_VERSIONS "1.7.x, 1.8" +#define MCS_CLIENT_VERSIONS "1.7.x, 1.8.x" #define MCS_PROTOCOL_VERSIONS "4, 5, 47" diff --git a/src/RankManager.cpp b/src/RankManager.cpp index 451de88e7..54fef5ce7 100644 --- a/src/RankManager.cpp +++ b/src/RankManager.cpp @@ -414,6 +414,7 @@ void cRankManager::Initialize(cMojangAPI & a_MojangAPI) m_DB.exec("CREATE TABLE IF NOT EXISTS PermGroup (PermGroupID INTEGER PRIMARY KEY, Name)"); m_DB.exec("CREATE TABLE IF NOT EXISTS RankPermGroup (RankID INTEGER, PermGroupID INTEGER)"); m_DB.exec("CREATE TABLE IF NOT EXISTS PermissionItem (PermGroupID INTEGER, Permission)"); + m_DB.exec("CREATE TABLE IF NOT EXISTS RestrictionItem (PermGroupID INTEGER, Permission)"); m_DB.exec("CREATE TABLE IF NOT EXISTS DefaultRank (RankID INTEGER)"); m_IsInitialized = true; @@ -571,6 +572,20 @@ AStringVector cRankManager::GetPlayerPermissions(const AString & a_PlayerUUID) +AStringVector cRankManager::GetPlayerRestrictions(const AString & a_PlayerUUID) +{ + AString Rank = GetPlayerRankName(a_PlayerUUID); + if (Rank.empty()) + { + Rank = m_DefaultRank; + } + return GetRankRestrictions(Rank); +} + + + + + AStringVector cRankManager::GetRankGroups(const AString & a_RankName) { ASSERT(m_IsInitialized); @@ -632,6 +647,36 @@ AStringVector cRankManager::GetGroupPermissions(const AString & a_GroupName) +AStringVector cRankManager::GetGroupRestrictions(const AString & a_GroupName) +{ + ASSERT(m_IsInitialized); + cCSLock Lock(m_CS); + + AStringVector res; + try + { + SQLite::Statement stmt(m_DB, + "SELECT RestrictionItem.Permission FROM RestrictionItem " + "LEFT JOIN PermGroup ON PermGroup.PermGroupID = RestrictionItem.PermGroupID " + "WHERE PermGroup.Name = ?" + ); + stmt.bind(1, a_GroupName); + while (stmt.executeStep()) + { + res.push_back(stmt.getColumn(0).getText()); + } + } + catch (const SQLite::Exception & ex) + { + LOGWARNING("%s: Failed to get group restrictions from DB: %s", __FUNCTION__, ex.what()); + } + return res; +} + + + + + AStringVector cRankManager::GetRankPermissions(const AString & a_RankName) { ASSERT(m_IsInitialized); @@ -663,6 +708,37 @@ AStringVector cRankManager::GetRankPermissions(const AString & a_RankName) +AStringVector cRankManager::GetRankRestrictions(const AString & a_RankName) +{ + ASSERT(m_IsInitialized); + cCSLock Lock(m_CS); + + AStringVector res; + try + { + SQLite::Statement stmt(m_DB, + "SELECT RestrictionItem.Permission FROM RestrictionItem " + "LEFT JOIN RankPermGroup ON RankPermGroup.PermGroupID = RestrictionItem.PermGroupID " + "LEFT JOIN Rank ON Rank.RankID = RankPermGroup.RankID " + "WHERE Rank.Name = ?" + ); + stmt.bind(1, a_RankName); + while (stmt.executeStep()) + { + res.push_back(stmt.getColumn(0).getText()); + } + } + catch (const SQLite::Exception & ex) + { + LOGWARNING("%s: Failed to get rank restrictions from DB: %s", __FUNCTION__, ex.what()); + } + return res; +} + + + + + AStringVector cRankManager::GetAllPlayerUUIDs(void) { ASSERT(m_IsInitialized); @@ -764,6 +840,46 @@ AStringVector cRankManager::GetAllPermissions(void) +AStringVector cRankManager::GetAllRestrictions(void) +{ + ASSERT(m_IsInitialized); + cCSLock Lock(m_CS); + + AStringVector res; + try + { + SQLite::Statement stmt(m_DB, "SELECT DISTINCT(Permission) FROM RestrictionItem"); + while (stmt.executeStep()) + { + res.push_back(stmt.getColumn(0).getText()); + } + } + catch (const SQLite::Exception & ex) + { + LOGWARNING("%s: Failed to get restrictions from DB: %s", __FUNCTION__, ex.what()); + } + return res; +} + + + + + +AStringVector cRankManager::GetAllPermissionsRestrictions(void) +{ + AStringVector Permissions = GetAllPermissions(); + AStringVector Restrictions = GetAllRestrictions(); + for (auto & restriction: Restrictions) + { + Permissions.push_back(restriction); + } + return Permissions; +} + + + + + bool cRankManager::GetPlayerMsgVisuals( const AString & a_PlayerUUID, AString & a_MsgPrefix, @@ -1063,6 +1179,73 @@ bool cRankManager::AddPermissionToGroup(const AString & a_Permission, const AStr +bool cRankManager::AddRestrictionToGroup(const AString & a_Restriction, const AString & a_GroupName) +{ + ASSERT(m_IsInitialized); + cCSLock Lock(m_CS); + + try + { + // Get the group's ID: + int GroupID; + { + SQLite::Statement stmt(m_DB, "SELECT PermGroupID FROM PermGroup WHERE Name = ?"); + stmt.bind(1, a_GroupName); + if (!stmt.executeStep()) + { + LOGWARNING("%s: No such group (%s), aborting.", __FUNCTION__, a_GroupName.c_str()); + return false; + } + GroupID = stmt.getColumn(0).getInt(); + } + + // Check if the restriction is already present: + { + SQLite::Statement stmt(m_DB, "SELECT COUNT(*) FROM RestrictionItem WHERE PermGroupID = ? AND Permission = ?"); + stmt.bind(1, GroupID); + stmt.bind(2, a_Restriction); + if (!stmt.executeStep()) + { + LOGWARNING("%s: Failed to check binding between restriction %s and group %s, aborting.", __FUNCTION__, a_Restriction.c_str(), a_GroupName.c_str()); + return false; + } + if (stmt.getColumn(0).getInt() > 0) + { + LOGD("%s: Restriction %s is already present in group %s, skipping and returning success.", + __FUNCTION__, a_Restriction.c_str(), a_GroupName.c_str() + ); + return true; + } + } + + // Add the restriction: + { + SQLite::Statement stmt(m_DB, "INSERT INTO RestrictionItem (Permission, PermGroupID) VALUES (?, ?)"); + stmt.bind(1, a_Restriction); + stmt.bind(2, GroupID); + if (stmt.exec() <= 0) + { + LOGWARNING("%s: Failed to add restriction %s to group %s, aborting.", __FUNCTION__, a_Restriction.c_str(), a_GroupName.c_str()); + return false; + } + } + + // Adding succeeded: + return true; + } + catch (const SQLite::Exception & ex) + { + LOGWARNING("%s: Failed to add restriction %s to group %s: %s", + __FUNCTION__, a_Restriction.c_str(), a_GroupName.c_str(), ex.what() + ); + } + return false; +} + + + + + bool cRankManager::AddPermissionsToGroup(const AStringVector & a_Permissions, const AString & a_GroupName) { ASSERT(m_IsInitialized); @@ -1133,6 +1316,76 @@ bool cRankManager::AddPermissionsToGroup(const AStringVector & a_Permissions, co +bool cRankManager::AddRestrictionsToGroup(const AStringVector & a_Restrictions, const AString & a_GroupName) +{ + ASSERT(m_IsInitialized); + cCSLock Lock(m_CS); + + try + { + // Get the group's ID: + int GroupID; + { + SQLite::Statement stmt(m_DB, "SELECT PermGroupID FROM PermGroup WHERE Name = ?"); + stmt.bind(1, a_GroupName); + if (!stmt.executeStep()) + { + LOGWARNING("%s: No such group (%s), aborting.", __FUNCTION__, a_GroupName.c_str()); + return false; + } + GroupID = stmt.getColumn(0).getInt(); + } + + for (auto itr = a_Restrictions.cbegin(), end = a_Restrictions.cend(); itr != end; ++itr) + { + // Check if the restriction is already present: + { + SQLite::Statement stmt(m_DB, "SELECT COUNT(*) FROM RestrictionItem WHERE PermGroupID = ? AND Permission = ?"); + stmt.bind(1, GroupID); + stmt.bind(2, *itr); + if (!stmt.executeStep()) + { + LOGWARNING("%s: Failed to check binding between restriction %s and group %s, aborting.", __FUNCTION__, itr->c_str(), a_GroupName.c_str()); + return false; + } + if (stmt.getColumn(0).getInt() > 0) + { + LOGD("%s: Restriction %s is already present in group %s, skipping and returning success.", + __FUNCTION__, itr->c_str(), a_GroupName.c_str() + ); + continue; + } + } + + // Add the permission: + { + SQLite::Statement stmt(m_DB, "INSERT INTO RestrictionItem (Permission, PermGroupID) VALUES (?, ?)"); + stmt.bind(1, *itr); + stmt.bind(2, GroupID); + if (stmt.exec() <= 0) + { + LOGWARNING("%s: Failed to add restriction %s to group %s, skipping.", __FUNCTION__, itr->c_str(), a_GroupName.c_str()); + continue; + } + } + } // for itr - a_Restrictions[] + + // Adding succeeded: + return true; + } + catch (const SQLite::Exception & ex) + { + LOGWARNING("%s: Failed to add restrictions to group %s: %s", + __FUNCTION__, a_GroupName.c_str(), ex.what() + ); + } + return false; +} + + + + + void cRankManager::RemoveRank(const AString & a_RankName, const AString & a_ReplacementRankName) { ASSERT(m_IsInitialized); @@ -1362,6 +1615,46 @@ void cRankManager::RemovePermissionFromGroup(const AString & a_Permission, const +void cRankManager::RemoveRestrictionFromGroup(const AString & a_Restriction, const AString & a_GroupName) +{ + ASSERT(m_IsInitialized); + cCSLock Lock(m_CS); + + try + { + // Get the ID of the group: + int GroupID; + { + SQLite::Statement stmt(m_DB, "SELECT PermGroupID FROM PermGroup WHERE Name = ?"); + stmt.bind(1, a_GroupName); + if (!stmt.executeStep()) + { + LOGINFO("%s: Group %s was not found, skipping.", __FUNCTION__, a_GroupName.c_str()); + return; + } + GroupID = stmt.getColumn(0).getInt(); + } + + // Remove the permission from the group: + { + SQLite::Statement stmt(m_DB, "DELETE FROM RestrictionItem WHERE PermGroupID = ? AND Permission = ?"); + stmt.bind(1, GroupID); + stmt.bind(2, a_Restriction); + stmt.exec(); + } + } + catch (const SQLite::Exception & ex) + { + LOGWARNING("%s: Failed to remove restriction %s from group %s in DB: %s", + __FUNCTION__, a_Restriction.c_str(), a_GroupName.c_str(), ex.what() + ); + } +} + + + + + bool cRankManager::RenameRank(const AString & a_OldName, const AString & a_NewName) { ASSERT(m_IsInitialized); @@ -1744,6 +2037,37 @@ bool cRankManager::IsPermissionInGroup(const AString & a_Permission, const AStri +bool cRankManager::IsRestrictionInGroup(const AString & a_Restriction, const AString & a_GroupName) +{ + ASSERT(m_IsInitialized); + cCSLock Lock(m_CS); + + try + { + SQLite::Statement stmt(m_DB, + "SELECT * FROM RestrictionItem " + "LEFT JOIN PermGroup ON PermGroup.PermGroupID = RestrictionItem.PermGroupID " + "WHERE RestrictionItem.Permission = ? AND PermGroup.Name = ?" + ); + stmt.bind(1, a_Restriction); + stmt.bind(2, a_GroupName); + if (stmt.executeStep()) + { + // The restriction is in the group + return true; + } + } + catch (const SQLite::Exception & ex) + { + LOGWARNING("%s: Failed to query DB: %s", __FUNCTION__, ex.what()); + } + return false; +} + + + + + void cRankManager::NotifyNameUUID(const AString & a_PlayerName, const AString & a_UUID) { ASSERT(m_IsInitialized); @@ -1936,3 +2260,58 @@ void cRankManager::CreateDefaults(void) +bool cRankManager::DoesColumnExist(const char * a_TableName, const char * a_ColumnName) +{ + try + { + SQLite::Statement stmt(m_DB, Printf("PRAGMA table_info(%s)", a_TableName)); + while (stmt.executeStep()) // Iterate over all table's columns + { + int NumColumns = stmt.getColumnCount(); + for (int i = 0; i < NumColumns; i++) // Iterate over all reply's columns (table column's metadata) + { + auto column = stmt.getColumn(i); + if (strcmp(column.getName(), "name") == 0) + { + if (NoCaseCompare(column.getText(), a_ColumnName) == 0) + { + // Colun found + return true; + } + } + } // for i - stmt.getColumns() + } // while (stmt.executeStep()) + } + catch (const SQLite::Exception & ex) + { + LOGWARNING("%s: Failed to query DB: %s", __FUNCTION__, ex.what()); + } + return false; +} + + + + + +void cRankManager::CreateColumnIfNotExists(const char * a_TableName, const char * a_ColumnName, const char * a_ColumnType) +{ + // If the column already exists, bail out: + if (DoesColumnExist(a_TableName, a_ColumnName)) + { + return; + } + + // Add the column: + try + { + m_DB.exec(Printf("ALTER TABLE %s ADD COLUMN %s %s", a_TableName, a_ColumnName, a_ColumnType)); + } + catch (const SQLite::Exception & exc) + { + LOGWARNING("%s: Failed to query DB: %s", __FUNCTION__, exc.what()); + } +} + + + + diff --git a/src/RankManager.h b/src/RankManager.h index 5dff634b5..b3431b7d1 100644 --- a/src/RankManager.h +++ b/src/RankManager.h @@ -71,6 +71,10 @@ public: If the player has no rank assigned to them, returns the default rank's permissions. */ AStringVector GetPlayerPermissions(const AString & a_PlayerUUID); + /** Returns the restrictions that the specified player has assigned to them. + If the player has no rank assigned to them, returns the default rank's restrictions. */ + AStringVector GetPlayerRestrictions(const AString & a_PlayerUUID); + /** Returns the names of groups that the specified rank has assigned to it. Returns an empty vector if the rank doesn't exist. */ AStringVector GetRankGroups(const AString & a_RankName); @@ -79,10 +83,18 @@ public: Returns an empty vector if the group doesn't exist. */ AStringVector GetGroupPermissions(const AString & a_GroupName); + /** Returns the restrictions that the specified group has assigned to it. + Returns an empty vector if the group doesn't exist. */ + AStringVector GetGroupRestrictions(const AString & a_GroupName); + /** Returns all permissions that the specified rank has assigned to it, through all its groups. Returns an empty vector if the rank doesn't exist. Any non-existent groups are ignored. */ AStringVector GetRankPermissions(const AString & a_RankName); + /** Returns all restrictions that the specified rank has assigned to it, through all its groups. + Returns an empty vector if the rank doesn't exist. Any non-existent groups are ignored. */ + AStringVector GetRankRestrictions(const AString & a_RankName); + /** Returns the short uuids of all defined players. The returned players are ordered by their name (NOT their UUIDs). */ AStringVector GetAllPlayerUUIDs(void); @@ -95,6 +107,12 @@ public: /** Returns all the distinct permissions that are stored in the DB. */ AStringVector GetAllPermissions(void); + /** Returns all the distinct restrictions that are stored in the DB. */ + AStringVector GetAllRestrictions(void); + + /** Returns all the distinct permissions and restrictions that are stored in the DB. */ + AStringVector GetAllPermissionsRestrictions(void); + /** Returns the message visuals (prefix, postfix, color) for the specified player. Returns true if the visuals were read from the DB, false if not (player not found etc). */ bool GetPlayerMsgVisuals( @@ -128,17 +146,27 @@ public: Returns true if successful, false on error. */ bool AddPermissionToGroup(const AString & a_Permission, const AString & a_GroupName); + /** Adds the specified restriction to the specified group. + Fails if the group name is not found. + Returns true if successful, false on error. */ + bool AddRestrictionToGroup(const AString & a_Restriction, const AString & a_GroupName); + /** Adds the specified permissions to the specified permission group. Fails if the permission group name is not found. Returns true if successful, false on error. */ bool AddPermissionsToGroup(const AStringVector & a_Permissions, const AString & a_GroupName); + /** Adds the specified restrictions to the specified group. + Fails if the group name is not found. + Returns true if successful, false on error. */ + bool AddRestrictionsToGroup(const AStringVector & a_Restrictions, const AString & a_GroupName); + /** Removes the specified rank. All players assigned to that rank will be re-assigned to a_ReplacementRankName. If a_ReplacementRankName is empty or not a valid rank, the player will be removed from the DB, which means they will receive the default rank the next time they are queried. If the rank being removed is the default rank, the default will be changed to the replacement - rank; the operation fails if there's no replacement. */ + rank; the operation fails silently if there's no replacement. */ void RemoveRank(const AString & a_RankName, const AString & a_ReplacementRankName); /** Removes the specified group completely. @@ -152,6 +180,9 @@ public: /** Removes the specified permission from the specified group. */ void RemovePermissionFromGroup(const AString & a_Permission, const AString & a_GroupName); + /** Removes the specified restriction from the specified group. */ + void RemoveRestrictionFromGroup(const AString & a_Restriction, const AString & a_GroupName); + /** Renames the specified rank. No action if the rank name is not found. Fails if the new name is already used. Updates the cached m_DefaultRank if the default rank is being renamed. @@ -208,6 +239,9 @@ public: /** Returns true iff the specified group contains the specified permission. */ bool IsPermissionInGroup(const AString & a_Permission, const AString & a_GroupName); + /** Returns true iff the specified group contains the specified restriction. */ + bool IsRestrictionInGroup(const AString & a_Restriction, const AString & a_GroupName); + /** Called by cMojangAPI whenever the playername-uuid pairing is discovered. Updates the DB. */ void NotifyNameUUID(const AString & a_PlayerName, const AString & a_UUID); @@ -253,6 +287,13 @@ protected: /** Creates a default set of ranks / groups / permissions. */ void CreateDefaults(void); + + /** Returns true if the specified column exists in the specified table. */ + bool DoesColumnExist(const char * a_TableName, const char * a_ColumnName); + + /** If the specified table doesn't contain the specified column, it is added to the table. + The column type is used only when creating the column, it is not used when checking for existence. */ + void CreateColumnIfNotExists(const char * a_TableName, const char * a_ColumnName, const char * a_ColumnType = ""); } ; diff --git a/src/Root.cpp b/src/Root.cpp index 690bd7357..1379b01a2 100644 --- a/src/Root.cpp +++ b/src/Root.cpp @@ -607,7 +607,7 @@ bool cRoot::FindAndDoWithPlayer(const AString & a_PlayerName, cPlayerListCallbac size_t Rating = RateCompareString (m_PlayerName, a_pPlayer->GetName()); if ((Rating > 0) && (Rating >= m_BestRating)) { - m_BestMatch = a_pPlayer; + m_BestMatch = a_pPlayer->GetName(); if (Rating > m_BestRating) { m_NumMatches = 0; @@ -627,18 +627,18 @@ bool cRoot::FindAndDoWithPlayer(const AString & a_PlayerName, cPlayerListCallbac m_BestRating(0), m_NameLength(a_PlayerName.length()), m_PlayerName(a_PlayerName), - m_BestMatch(nullptr), + m_BestMatch(), m_NumMatches(0) {} - cPlayer * m_BestMatch; + AString m_BestMatch; unsigned m_NumMatches; } Callback (a_PlayerName); ForEachPlayer(Callback); if (Callback.m_NumMatches == 1) { - return a_Callback.Item(Callback.m_BestMatch); + return DoWithPlayer(Callback.m_BestMatch, a_Callback); } return false; } diff --git a/src/Server.cpp b/src/Server.cpp index 8b6a2e769..996de2695 100644 --- a/src/Server.cpp +++ b/src/Server.cpp @@ -113,7 +113,7 @@ void cServer::cTickThread::Execute(void) auto msec = std::chrono::duration_cast<std::chrono::milliseconds>(NowTime - LastTime).count(); m_ShouldTerminate = !m_Server.Tick(static_cast<float>(msec)); auto TickTime = std::chrono::steady_clock::now() - NowTime; - + if (TickTime < msPerTick) { // Stretch tick time until it's at least msPerTick @@ -206,7 +206,7 @@ bool cServer::InitServer(cIniFile & a_SettingsIni, bool a_ShouldAuth) LOGINFO("Compatible protocol versions %s", MCS_PROTOCOL_VERSIONS); m_Ports = ReadUpgradeIniPorts(a_SettingsIni, "Server", "Ports", "Port", "PortsIPv6", "25565"); - + m_RCONServer.Initialize(a_SettingsIni); m_bIsConnected = true; @@ -231,10 +231,10 @@ bool cServer::InitServer(cIniFile & a_SettingsIni, bool a_ShouldAuth) { LOGWARNING("WARNING: BungeeCord is allowed and server set to online mode. This is unsafe and will not work properly. Disable either authentication or BungeeCord in settings.ini."); } - + m_ShouldLoadOfflinePlayerData = a_SettingsIni.GetValueSetB("PlayerData", "LoadOfflinePlayerData", false); m_ShouldLoadNamedPlayerData = a_SettingsIni.GetValueSetB("PlayerData", "LoadNamedPlayerData", true); - + m_ClientViewDistance = a_SettingsIni.GetValueSetI("Server", "DefaultViewDistance", cClientHandle::DEFAULT_VIEW_DISTANCE); if (m_ClientViewDistance < cClientHandle::MIN_VIEW_DISTANCE) { @@ -246,9 +246,9 @@ bool cServer::InitServer(cIniFile & a_SettingsIni, bool a_ShouldAuth) m_ClientViewDistance = cClientHandle::MAX_VIEW_DISTANCE; LOGINFO("Setting default viewdistance to the maximum of %d", m_ClientViewDistance); } - + PrepareKeys(); - + return true; } @@ -320,13 +320,13 @@ bool cServer::Tick(float a_Dt) cCSLock Lock(m_CSPlayerCount); m_PlayerCount += PlayerCountDiff; } - + // Send the tick to the plugins, as well as let the plugin manager reload, if asked to (issue #102): cPluginManager::Get()->Tick(a_Dt); - + // Let the Root process all the queued commands: cRoot::Get()->TickCommands(); - + // Tick all clients not yet assigned to a world: TickClients(a_Dt); @@ -351,7 +351,7 @@ void cServer::TickClients(float a_Dt) cClientHandlePtrs RemoveClients; { cCSLock Lock(m_CSClients); - + // Remove clients that have moved to a world (the world will be ticking them from now on) for (auto itr = m_ClientsToRemove.begin(), end = m_ClientsToRemove.end(); itr != end; ++itr) { @@ -365,7 +365,7 @@ void cServer::TickClients(float a_Dt) } } // for itr - m_ClientsToRemove[] m_ClientsToRemove.clear(); - + // Tick the remaining clients, take out those that have been destroyed into RemoveClients for (auto itr = m_Clients.begin(); itr != m_Clients.end();) { @@ -380,7 +380,7 @@ void cServer::TickClients(float a_Dt) ++itr; } // for itr - m_Clients[] } - + // Delete the clients that have been destroyed RemoveClients.clear(); } @@ -439,7 +439,7 @@ void cServer::ExecuteConsoleCommand(const AString & a_Cmd, cCommandOutputCallbac } // "stop" and "restart" are handled in cRoot::ExecuteConsoleCommand, our caller, due to its access to controlling variables - + // "help" and "reload" are to be handled by MCS, so that they work no matter what if (split[0] == "help") { @@ -529,7 +529,7 @@ void cServer::ExecuteConsoleCommand(const AString & a_Cmd, cCommandOutputCallbac DumpUsedMemory(&Output); return; } - + else if (split[0].compare("killmem") == 0) { for (;;) @@ -544,7 +544,7 @@ void cServer::ExecuteConsoleCommand(const AString & a_Cmd, cCommandOutputCallbac a_Output.Finished(); return; } - + a_Output.Out("Unknown command, type 'help' for all commands."); a_Output.Finished(); } @@ -558,13 +558,13 @@ void cServer::PrintHelp(const AStringVector & a_Split, cCommandOutputCallback & UNUSED(a_Split); typedef std::pair<AString, AString> AStringPair; typedef std::vector<AStringPair> AStringPairs; - + class cCallback : public cPluginManager::cCommandEnumCallback { public: cCallback(void) : m_MaxLen(0) {} - + virtual bool Command(const AString & a_Command, const cPlugin * a_Plugin, const AString & a_Permission, const AString & a_HelpString) override { UNUSED(a_Plugin); @@ -579,7 +579,7 @@ void cServer::PrintHelp(const AStringVector & a_Split, cCommandOutputCallback & } return false; } - + AStringPairs m_Commands; size_t m_MaxLen; } Callback; @@ -625,7 +625,7 @@ void cServer::Shutdown(void) srv->Close(); } m_ServerHandles.clear(); - + // Notify the tick thread and wait for it to terminate: m_bRestarting = true; m_RestartEvent.Wait(); diff --git a/src/Vector3.h b/src/Vector3.h index 6164721da..071997f50 100644 --- a/src/Vector3.h +++ b/src/Vector3.h @@ -355,6 +355,7 @@ protected: + template <> inline Vector3<int> Vector3<int>::Floor(void) const { return *this; diff --git a/src/World.cpp b/src/World.cpp index 8e1d0b33e..87209e4c2 100644 --- a/src/World.cpp +++ b/src/World.cpp @@ -833,7 +833,7 @@ void cWorld::InitialiseAndLoadMobSpawningValues(cIniFile & a_IniFile) AString DefaultMonsters; switch (m_Dimension) { - case dimOverworld: DefaultMonsters = "bat, cavespider, chicken, cow, creeper, enderman, horse, mooshroom, ocelot, pig, sheep, silverfish, skeleton, slime, spider, squid, wolf, zombie"; break; + case dimOverworld: DefaultMonsters = "bat, cavespider, chicken, cow, creeper, enderman, guardian, horse, mooshroom, ocelot, pig, rabbit, sheep, silverfish, skeleton, slime, spider, squid, wolf, zombie"; break; case dimNether: DefaultMonsters = "blaze, ghast, magmacube, skeleton, zombie, zombiepigman"; break; case dimEnd: DefaultMonsters = "enderman"; break; case dimNotSet: ASSERT(!"Dimension not set"); break; diff --git a/src/main.cpp b/src/main.cpp index 2e7e107f7..1c34b8f61 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -12,6 +12,7 @@ #endif // _MSC_VER #include "OSSupport/NetworkSingleton.h" +#include "BuildInfo.h" @@ -78,6 +79,10 @@ void NonCtrlHandler(int a_Signal) std::signal(SIGSEGV, SIG_DFL); LOGERROR(" D: | MCServer has encountered an error and needs to close"); LOGERROR("Details | SIGSEGV: Segmentation fault"); + #ifdef BUILD_ID + LOGERROR("MCServer " BUILD_SERIES_NAME " build id: " BUILD_ID); + LOGERROR("from commit id: " BUILD_COMMIT_ID " built at: " BUILD_DATETIME); + #endif PrintStackTrace(); abort(); } @@ -89,6 +94,10 @@ void NonCtrlHandler(int a_Signal) std::signal(a_Signal, SIG_DFL); LOGERROR(" D: | MCServer has encountered an error and needs to close"); LOGERROR("Details | SIGABRT: Server self-terminated due to an internal fault"); + #ifdef BUILD_ID + LOGERROR("MCServer " BUILD_SERIES_NAME " build id: " BUILD_ID); + LOGERROR("from commit id: " BUILD_COMMIT_ID " built at: " BUILD_DATETIME); + #endif PrintStackTrace(); abort(); } |