diff options
72 files changed, 2492 insertions, 461 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 5b3fd5e7d..2bb780eba 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() @@ -76,6 +78,9 @@ endif() # The Expat library is linked in statically, make the source files aware of that: add_definitions(-DXML_STATIC) +# Let Lua use additional checks on its C API. This is only compiled into Debug builds: +add_definitions(-DLUA_USE_APICHECK) + # Self Test Mode enables extra checks at startup if(${SELF_TEST}) add_definitions(-DSELF_TEST) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 03481ec48..82f09b6bf 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,7 +1,27 @@ -Code Stuff +Code Conventions ---------- - * We use C++03 with some C++11 extensions (ask if you think that something would be useful) +When contributing, you must follow our code conventions. Otherwise, the CI builds will automatically fail and your PR will not be merged until the non-conforming code is fixed. Due to this, we strongly advise you to run `src/CheckBasicStyle.lua` before commiting, it will perform various code style checks and warn you if your code does not conform to our conventions. `CheckBasicStyle.lua` can be configured to run automatically before every commit via a pre-commit hook, **this is highly recommended**. The way to do it is listed at the bottom of this file. + +Here are the conventions: + + * We use the subset of C++11 supported by MSVC 2013 (ask if you think that something would be useful) + * All new public functions in all classes need documenting comments on what they do and what behavior they follow, use doxy-comments formatted as `/** Description */`. Do not use asterisks on additional lines in multi-line comments. + * Use spaces after the comment markers: `// Comment` instead of `//Comment`. A comment must be prefixed with two spaces if it's on the same line with code: + - `SomeFunction() // Note the two spaces prefixed to me and the space after the slashes.` + * All variable names and function names use CamelCase style. + - `ThisIsAProperFunction()` `This_is_bad()` `this_is_bad` `GoodVariableName` `badVariableName`. + * All member variables start with `m_`, all function parameters start with `a_`, all class names start with `c`. + - `class cMonster { int m_Health; int DecreaseHealth(int a_Amount); }` + * Put spaces after commas. `Vector3d(1, 2, 3)` instead of `Vector3d(1,2,3)` + * Put spaces before and after every operator. + - `a = b + c;` + - `if (a == b)` + * Keep individual functions spaced out by 5 empty lines, this enhances readability and makes navigation in the source file easier. + * Add those extra parentheses to conditions, especially in C++: + - `if ((a == 1) && ((b == 2) || (c == 3)))` instead of ambiguous `if (a == 1 && b == 2 || c == 3)` + - This helps prevent mistakes such as `if (a & 1 == 0)` + * * 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: @@ -16,10 +36,6 @@ Code Stuff - `cPlayer:IsGameModeCreative()` instead of` (cPlayer:GetGameMode() == gmCreative)` (the player can also inherit the gamemode from the world, which the value-d condition doesn't catch) * Please use **tabs for indentation and spaces for alignment**. This means that if it's at line start, it's a tab; if it's in the middle of a line, it's a space * Alpha-sort stuff that makes sense alpha-sorting - long lists of similar items etc. - * Keep individual functions spaced out by 5 empty lines, this enhances readability and makes navigation in the source file easier. - * Add those extra parentheses to conditions, especially in C++ - - `if ((a == 1) && ((b == 2) || (c == 3)))` instead of ambiguous `if (a == 1 && b == 2 || c == 3)` - - This helps prevent mistakes such as `if (a & 1 == 0)` * White space is free, so use it freely - "freely" as in "plentifully", not "arbitrarily" * All `case` statements inside a `switch` need an extra indent. @@ -27,9 +43,22 @@ Code Stuff - The only exception: a `switch` statement with all `case` statements being a single short statement is allowed to use the short brace-less form. - These two rules really mean that indent is governed by braces * Add an empty last line in all source files (GCC and GIT can complain otherwise) - * All new public functions in all classes need documenting comments on what they do and what behavior they follow, use doxy-comments formatted as `/** Description */`. Do not use asterisks on additional lines in multi-line comments. - * Use spaces after the comment markers: `// Comment` instead of `//Comment` +Pre-commit hook +--------- +When contributing, the code conventions above *must* be followed. Otherwise, the CI builds will automatically fail and your PR will not be merged until the non-conforming code is fixed. It is highly recommended to set up a pre-commit hook which will check your code style before every commit. Here is how to do that: + + * Clone the repository as usual. + * Go to your `<clone location>/.git/hooks` folder, create a text file named "pre-commit" there with the following contents: +``` +#!/usr/sh +src/CheckBasicStyle.lua 1>&2 -g +``` + * If on Linux/Unix, you need to give the newly created file an execute permission: `chmod +x .git/hooks/pre-commit` + * Lua must be installed. + * You're done. Now, `src/CheckBasicStyle.lua` will check the changed files before every commit. If a problem is found, it will point you to that problem and will cancel the commit. + +Note that the check script is not smart enough to catch everything, so not having any warnings does not necessarily imply that you followed the conventions fully. The other humans working on this will perform more checks before merging. Copyright --------- 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..2c26ccecc 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 @@ -2011,8 +2046,8 @@ a_Player:OpenWindow(Window); BroadcastChatLeave = { Params = "MessageText", Return = "", Notes = "Broadcasts the specified message to all players, with its message type set to mtLeave. Use for players leaving the server." }, BroadcastChatSuccess = { Params = "MessageText", Return = "", Notes = "Broadcasts the specified message to all players, with its message type set to mtSuccess. Use for success messages." }, BroadcastChatWarning = { Params = "MessageText", Return = "", Notes = "Broadcasts the specified message to all players, with its message type set to mtWarning. Use for concerning events, such as plugin reload etc." }, - CreateAndInitializeWorld = { Params = "WorldName", Return = "{{cWorld|cWorld}}", Notes = "Creates a new world and initializes it. If there is a world whith the same name it returns nil.<br/><br/><b>NOTE</b>This function is currently unsafe, do not use!" }, - FindAndDoWithPlayer = { Params = "PlayerName, CallbackFunction", Return = "", Notes = "Calls the given callback function for all players with names partially (or fully) matching the name string provided." }, + CreateAndInitializeWorld = { Params = "WorldName", Return = "{{cWorld|cWorld}}", Notes = "Creates a new world and initializes it. If there is a world whith the same name it returns nil.<br><br><b>NOTE</b>This function is currently unsafe, do not use!" }, + FindAndDoWithPlayer = { Params = "PlayerName, CallbackFunction", Return = "bool", Notes = "Calls the given callback function for the player with the name best matching the name string provided.<br>This function is case-insensitive and will match partial names.<br>Returns false if player not found or there is ambiguity, true otherwise. The CallbackFunction has the following signature: <pre class=\"prettyprint lang-lua\">function Callback({{cPlayer|Player}})</pre>" }, DoWithPlayerByUUID = { Params = "PlayerUUID, CallbackFunction", Return = "bool", Notes = "If there is the player with the uuid, calls the CallbackFunction with the {{cPlayer}} parameter representing the player. The CallbackFunction has the following signature: <pre class=\"prettyprint lang-lua\">function Callback({{cPlayer|Player}})</pre> The function returns false if the player was not found, or whatever bool value the callback returned if the player was found." }, ForEachPlayer = { Params = "CallbackFunction", Return = "", Notes = "Calls the given callback function for each player. The callback function has the following signature: <pre class=\"prettyprint lang-lua\">function Callback({{cPlayer|cPlayer}})</pre>" }, ForEachWorld = { Params = "CallbackFunction", Return = "", Notes = "Calls the given callback function for each world. The callback function has the following signature: <pre class=\"prettyprint lang-lua\">function Callback({{cWorld|cWorld}})</pre>" }, @@ -2296,7 +2331,7 @@ local CompressedString = cStringCompression.CompressStringGZIP("DataToCompress") { Params = "BlockX, BlockY, BlockZ, BlockType, BlockMeta", Return = "", Notes = "Sets the block at the specified coords, without waking up the simulators or replacing the block entities for the previous block type. Do not use if the block being replaced has a block entity tied to it!" }, { Params = "{{Vector3i|BlockCoords}}, BlockType, BlockMeta", Return = "", Notes = "Sets the block at the specified coords, without waking up the simulators or replacing the block entities for the previous block type. Do not use if the block being replaced has a block entity tied to it!" }, }, - FindAndDoWithPlayer = { Params = "PlayerNameHint, CallbackFunction", Return = "bool", Notes = "If there is a player of a name similar to the specified name (weighted-match), calls the CallbackFunction with the {{cPlayer}} parameter representing the player. The CallbackFunction has the following signature: <pre class=\"prettyprint lang-lua\">function Callback({{cPlayer|Player}})</pre> The function returns false if the player was not found, or whatever bool value the callback returned if the player was found. Note that the name matching is very loose, so it is a good idea to check the player name in the callback function." }, + FindAndDoWithPlayer = { Params = "PlayerName, CallbackFunction", Return = "bool", Notes = "Calls the given callback function for the player with the name best matching the name string provided.<br>This function is case-insensitive and will match partial names.<br>Returns false if player not found or there is ambiguity, true otherwise. The CallbackFunction has the following signature: <pre class=\"prettyprint lang-lua\">function Callback({{cPlayer|Player}})</pre>" }, ForEachBlockEntityInChunk = { Params = "ChunkX, ChunkZ, CallbackFunction", Return = "bool", Notes = "Calls the specified callback for each block entity in the chunk. Returns true if all block entities in the chunk have been processed (including when there are zero block entities), or false if the callback has aborted the enumeration by returning true. The CallbackFunction has the following signature: <pre class=\"prettyprint lang-lua\">function Callback({{cBlockEntity|BlockEntity}})</pre> The callback should return false or no value to continue with the next block entity, or true to abort the enumeration. Use {{tolua}}.cast() to cast the Callback's BlockEntity parameter to the correct {{cBlockEntity}} descendant." }, ForEachChestInChunk = { Params = "ChunkX, ChunkZ, CallbackFunction", Return = "bool", Notes = "Calls the specified callback for each chest in the chunk. Returns true if all chests in the chunk have been processed (including when there are zero chests), or false if the callback has aborted the enumeration by returning true. The CallbackFunction has the following signature: <pre class=\"prettyprint lang-lua\">function Callback({{cChestEntity|ChestEntity}})</pre> The callback should return false or no value to continue with the next chest, or true to abort the enumeration." }, ForEachEntity = { Params = "CallbackFunction", Return = "bool", Notes = "Calls the specified callback for each entity in the loaded world. Returns true if all the entities have been processed (including when there are zero entities), or false if the callback function has aborted the enumeration by returning true. The callback function has the following signature: <pre class=\"prettyprint lang-lua\">function Callback({{cEntity|Entity}})</pre> The callback should return false or no value to continue with the next entity, or true to abort the enumeration." }, 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/Plugins/Debuggers/Debuggers.lua b/MCServer/Plugins/Debuggers/Debuggers.lua index bffc6e844..a49f8b5a6 100644 --- a/MCServer/Plugins/Debuggers/Debuggers.lua +++ b/MCServer/Plugins/Debuggers/Debuggers.lua @@ -54,7 +54,7 @@ function Initialize(a_Plugin) -- TestBlockAreas() -- TestSQLiteBindings() -- TestExpatBindings() - -- TestPluginCalls() + TestPluginCalls() TestBlockAreasString() TestStringBase64() @@ -157,26 +157,18 @@ function TestPluginCalls() -- The Split parameter should be a table, but it is not used in that function anyway, -- so we can get away with passing nil to it. - -- Use the old, deprecated and unsafe method: - local Core = cPluginManager:Get():GetPlugin("Core") - if (Core ~= nil) then - LOGINFO("Calling Core::ReturnColorFromChar() the old-fashioned way...") - local Gray = Core:Call("ReturnColorFromChar", nil, "8") - if (Gray ~= cChatColor.Gray) then - LOGWARNING("Call failed, exp " .. cChatColor.Gray .. ", got " .. (Gray or "<nil>")) - else - LOGINFO("Call succeeded") - end - end - - -- Use the new method: - LOGINFO("Calling Core::ReturnColorFromChar() the recommended way...") - local Gray = cPluginManager:CallPlugin("Core", "ReturnColorFromChar", nil, "8") + LOG("Debuggers: Calling NoSuchPlugin.FnName()...") + cPluginManager:CallPlugin("NoSuchPlugin", "FnName", "SomeParam") + LOG("Debuggers: Calling Core.NoSuchFunction()...") + cPluginManager:CallPlugin("Core", "NoSuchFunction", "SomeParam") + LOG("Debuggers: Calling Core.ReturnColorFromChar(..., \"8\")...") + local Gray = cPluginManager:CallPlugin("Core", "ReturnColorFromChar", "split", "8") if (Gray ~= cChatColor.Gray) then - LOGWARNING("Call failed, exp " .. cChatColor.Gray .. ", got " .. (Gray or "<nil>")) + LOGWARNING("Debuggers: Call failed, exp " .. cChatColor.Gray .. ", got " .. (Gray or "<nil>")) else - LOGINFO("Call succeeded") + LOG("Debuggers: Call succeeded") end + LOG("Debuggers: Inter-plugin calls done.") end 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..e7d11ab92 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 @@ -304,7 +304,7 @@ Dropper = Cobblestone, 1:1, 2:1, 3:1, 1:2, 1:3, 3:2, 3:3 | Hopper, 2:2 Repeater = Stone, 1:2, 2:2, 3:2 | RedstoneTorchOn, 1:1, 3:1 | RedstoneDust, 2:1 Comparator = RedstoneTorchOn, 2:1, 1:2, 3:2 | NetherQuartz, 2:2 | Stone, 1:3, 2:3, 3:3 DaylightSensor = Glass, 1:1, 2:1, 3:1 | NetherQuartz, 1:2, 2:2, 3:2 | Woodslab, 1:3, 2:3, 3:3 -Hopper = Ironbars, 1:1, 3:1, 1:2, 3:2, 2:3 | Chest, 2:2 +Hopper = IronIngot, 1:1, 3:1, 1:2, 3:2, 2:3 | Chest, 2:2 Piston = Planks^-1, 1:1, 2:1, 3:1 | RedstoneDust, 2:3 | Cobblestone, 1:2, 3:2, 1:3, 3:3 | IronIngot, 2:2 StickyPiston = Piston, * | SlimeBall, * RedstoneLamp = RedstoneDust, 2:1, 1:2, 3:2, 2:3 | Glowstone, 2:2 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/easyinstall.sh b/easyinstall.sh index 40fa85cfe..15ca1d358 100755 --- a/easyinstall.sh +++ b/easyinstall.sh @@ -4,10 +4,10 @@ PLATFORM=$(uname -m) echo "Identifying platform: $PLATFORM" case $PLATFORM in - "i686") DOWNLOADURL="http://builds.cuberite.org/job/MCServer%20Linux%20x86/lastSuccessfulBuild/artifact/MCServer.tar" ;; - "x86_64") DOWNLOADURL="http://builds.cuberite.org/job/MCServer%20Linux%20x64/lastSuccessfulBuild/artifact/MCServer.tar" ;; + "i686") DOWNLOADURL="http://builds.cuberite.org/job/MCServer%20Linux%20x86/lastSuccessfulBuild/artifact/MCServer.tar.gz" ;; + "x86_64") DOWNLOADURL="http://builds.cuberite.org/job/MCServer%20Linux%20x64/lastSuccessfulBuild/artifact/MCServer.tar.gz" ;; # Assume that all arm devices are a raspi for now. - arm*) DOWNLOADURL="http://builds.cuberite.org/job/MCServer%20Linux%20armhf/lastSuccessfulBuild/artifact/MCServer/MCServer.tar" + arm*) DOWNLOADURL="http://builds.cuberite.org/job/MCServer%20Linux%20armhf/lastSuccessfulBuild/artifact/MCServer/MCServer.tar.gz" esac echo "Downloading precompiled binaries." 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..9c1e2865c 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))); } } @@ -1103,6 +1133,23 @@ void cLuaState::GetStackValue(int a_StackPos, pWorld & a_ReturnedVal) +void cLuaState::GetStackValue(int a_StackPos, pClientHandle & a_ReturnedVal) +{ + if (lua_isnil(m_LuaState, a_StackPos)) + { + a_ReturnedVal = nullptr; + return; + } + tolua_Error err; + if (tolua_isusertype(m_LuaState, a_StackPos, "cClientHandle", false, &err)) + { + a_ReturnedVal = *(reinterpret_cast<cClientHandle **>(lua_touserdata(m_LuaState, a_StackPos))); + } +} + + + + bool cLuaState::CallFunction(int a_NumResults) { ASSERT (m_NumCurrentFunctionArgs >= 0); // A function must be pushed to stack first @@ -1385,6 +1432,30 @@ bool cLuaState::CheckParamEnd(int a_Param) +bool cLuaState::IsParamUserType(int a_Param, AString a_UserType) +{ + ASSERT(IsValid()); + + tolua_Error tolua_err; + return tolua_isusertype(m_LuaState, a_Param, a_UserType.c_str(), 0, &tolua_err); +} + + + + + +bool cLuaState::IsParamNumber(int a_Param) +{ + ASSERT(IsValid()); + + tolua_Error tolua_err; + return tolua_isnumber(m_LuaState, a_Param, 0, &tolua_err); +} + + + + + bool cLuaState::ReportErrors(int a_Status) { return ReportErrors(m_LuaState, a_Status); @@ -1464,7 +1535,7 @@ int cLuaState::CallFunctionWithForeignParams( if (!PushFunction(a_FunctionName.c_str())) { LOGWARNING("Function '%s' not found", a_FunctionName.c_str()); - lua_pop(m_LuaState, 2); + lua_settop(m_LuaState, OldTop); return -1; } @@ -1472,7 +1543,7 @@ int cLuaState::CallFunctionWithForeignParams( if (CopyStackFrom(a_SrcLuaState, a_SrcParamStart, a_SrcParamEnd) < 0) { // Something went wrong, fix the stack and exit - lua_pop(m_LuaState, 2); + lua_settop(m_LuaState, OldTop); m_NumCurrentFunctionArgs = -1; m_CurrentFunctionName.clear(); return -1; @@ -1483,13 +1554,8 @@ int cLuaState::CallFunctionWithForeignParams( if (ReportErrors(s)) { LOGWARN("Error while calling function '%s' in '%s'", a_FunctionName.c_str(), m_SubsystemName.c_str()); - // Fix the stack. - // We don't know how many values have been pushed, so just get rid of any that weren't there initially - int CurTop = lua_gettop(m_LuaState); - if (CurTop > OldTop) - { - lua_pop(m_LuaState, CurTop - OldTop); - } + // Reset the stack: + lua_settop(m_LuaState, OldTop); // Reset the internal checking mechanisms: m_NumCurrentFunctionArgs = -1; diff --git a/src/Bindings/LuaState.h b/src/Bindings/LuaState.h index a6e121eb7..3f2e828f3 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,12 +69,14 @@ struct HTTPRequest; struct HTTPTemplateRequest; struct TakeDamageInfo; +typedef cBlockArea * pBlockArea; typedef cBoundingBox * pBoundingBox; typedef cMapManager * pMapManager; typedef cPluginManager * pPluginManager; typedef cRoot * pRoot; typedef cScoreboard * pScoreboard; typedef cWorld * pWorld; +typedef cClientHandle * pClientHandle; @@ -244,12 +247,15 @@ 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, pClientHandle & a_Value); void GetStackValue(int a_StackPos, pMapManager & a_Value); void GetStackValue(int a_StackPos, pPluginManager & a_Value); void GetStackValue(int a_StackPos, pRoot & a_Value); @@ -303,6 +309,10 @@ public: /** Returns true if the specified parameter on the stack is nil (indicating an end-of-parameters) */ bool CheckParamEnd(int a_Param); + bool IsParamUserType(int a_Param, AString a_UserType); + + bool IsParamNumber(int a_Param); + /** If the status is nonzero, prints the text on the top of Lua stack and returns true */ bool ReportErrors(int status); diff --git a/src/Bindings/ManualBindings.cpp b/src/Bindings/ManualBindings.cpp index 253d57297..20042a780 100644 --- a/src/Bindings/ManualBindings.cpp +++ b/src/Bindings/ManualBindings.cpp @@ -32,9 +32,10 @@ #include "../WorldStorage/SchematicFileSerializer.h" #include "../CompositeChat.h" #include "../StringCompression.h" +#include "../Broadcaster.h" - +#include <array> // Better error reporting for Lua @@ -253,12 +254,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 +474,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 +511,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; } @@ -1957,6 +1988,11 @@ static int tolua_cPluginManager_CallPlugin(lua_State * tolua_S) { return 0; } + if (Callback.m_NumReturns < 0) + { + // The call has failed, there are zero return values. Do NOT return negative number (Lua considers that a "yield") + return 0; + } return Callback.m_NumReturns; } @@ -1979,6 +2015,60 @@ static int tolua_cPluginManager_FindPlugins(lua_State * tolua_S) +static int tolua_cWorld_BroadcastParticleEffect(lua_State * tolua_S) +{ + cLuaState L(tolua_S); + if ( + !L.CheckParamUserType(1, "cWorld") || + !L.CheckParamString (2) || + !L.CheckParamNumber (3, 10) + ) + { + return 0; + } + + cPluginLua * Plugin = GetLuaPlugin(tolua_S); + if (Plugin == nullptr) + { + return 0; + } + + // Read the params: + cWorld * World = nullptr; + AString Name; + double PosX, PosY, PosZ, OffX, OffY, OffZ; + double ParticleData; + int ParticleAmmount; + L.GetStackValues(1, World, Name, PosX, PosY, PosZ, OffX, OffY, OffZ, ParticleData, ParticleAmmount); + if (World == nullptr) + { + LOGWARNING("World:BroadcastParticleEffect(): invalid world parameter"); + L.LogStackTrace(); + return 0; + } + + std::array<int, 2> data; + + for (int i = 0; (i < 2) && L.IsParamNumber(11 + i); i++) + { + L.GetStackValue(11 + i, data[i]); + } + + cClientHandle * Exclude = nullptr; + + if (L.IsParamUserType(11, "cClientHandle")) + { + L.GetStackValue(11, Exclude); + } + World->GetBroadcaster().BroadcastParticleEffect(Name, Vector3f(PosX, PosY, PosZ), Vector3f(OffX, OffY, OffZ), ParticleData, ParticleAmmount, Exclude); + + return 0; +} + + + + + static int tolua_cWorld_ChunkStay(lua_State * tolua_S) { /* Function signature: @@ -2125,6 +2215,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 +3277,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 +3809,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); @@ -3691,6 +3852,7 @@ void ManualBindings::Bind(lua_State * tolua_S) tolua_endmodule(tolua_S); tolua_beginmodule(tolua_S, "cWorld"); + tolua_function(tolua_S, "BroadcastParticleEffect", tolua_cWorld_BroadcastParticleEffect); tolua_function(tolua_S, "ChunkStay", tolua_cWorld_ChunkStay); tolua_function(tolua_S, "DoWithBlockEntityAt", tolua_DoWithXYZ<cWorld, cBlockEntity, &cWorld::DoWithBlockEntityAt>); tolua_function(tolua_S, "DoWithBeaconAt", tolua_DoWithXYZ<cWorld, cBeaconEntity, &cWorld::DoWithBeaconAt>); @@ -3756,6 +3918,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/Broadcaster.cpp b/src/Broadcaster.cpp new file mode 100644 index 000000000..7f2b65d09 --- /dev/null +++ b/src/Broadcaster.cpp @@ -0,0 +1,47 @@ + +#include "Globals.h" +#include "Broadcaster.h" +#include "World.h" +#include "Chunk.h" + +cBroadcaster::cBroadcaster(cWorld * a_World) : + m_World(a_World) +{ +} + + +void cBroadcaster::BroadcastParticleEffect(const AString & a_ParticleName, const Vector3f a_Src, const Vector3f a_Offset, float a_ParticleData, int a_ParticleAmount, cClientHandle * a_Exclude) +{ + m_World->DoWithChunkAt(a_Src, + [=](cChunk & a_Chunk) -> bool + { + for (auto&& client : a_Chunk.GetAllClients()) + { + if (client == a_Exclude) + { + continue; + } + client->SendParticleEffect(a_ParticleName, a_Src.x, a_Src.y, a_Src.z, a_Offset.x, a_Offset.y, a_Offset.z, a_ParticleData, a_ParticleAmount); + }; + return true; + }); +} + + +void cBroadcaster::BroadcastParticleEffect(const AString & a_ParticleName, const Vector3f a_Src, const Vector3f a_Offset, float a_ParticleData, int a_ParticleAmount, std::array<int, 2> a_Data, cClientHandle * a_Exclude) +{ + m_World->DoWithChunkAt(a_Src, + [=](cChunk & a_Chunk) -> bool + { + for (auto && client : a_Chunk.GetAllClients()) + { + if (client == a_Exclude) + { + continue; + } + client->SendParticleEffect(a_ParticleName, a_Src, a_Offset, a_ParticleData, a_ParticleAmount, a_Data); + }; + return true; + }); +} + diff --git a/src/Broadcaster.h b/src/Broadcaster.h new file mode 100644 index 000000000..27d35fe4d --- /dev/null +++ b/src/Broadcaster.h @@ -0,0 +1,20 @@ + +class cWorld; + +#include <array> + +class cBroadcaster +{ + +public: + + cBroadcaster(cWorld * a_World); + + void BroadcastParticleEffect(const AString & a_ParticleName, const Vector3f a_Src, const Vector3f a_Offset, float a_ParticleData, int a_ParticleAmount, cClientHandle * a_Exclude = nullptr); + + void BroadcastParticleEffect(const AString & a_ParticleName, const Vector3f a_Src, const Vector3f a_Offset, float a_ParticleData, int a_ParticleAmount, std::array<int, 2> a_Data, cClientHandle * a_Exclude = nullptr); + +private: + cWorld * m_World; + +}; diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index b91c4f65a..fd28f3787 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -18,6 +18,7 @@ SET (SRCS BlockArea.cpp BlockID.cpp BlockInfo.cpp + Broadcaster.cpp BoundingBox.cpp ByteBuffer.cpp ChatColor.cpp @@ -77,6 +78,7 @@ SET (HDRS BlockInServerPluginInterface.h BlockInfo.h BlockTracer.h + Broadcaster.h BoundingBox.h BuildInfo.h.cmake ByteBuffer.h @@ -324,4 +326,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/Chunk.h b/src/Chunk.h index e8c60a74b..58f6ba707 100644 --- a/src/Chunk.h +++ b/src/Chunk.h @@ -439,6 +439,9 @@ public: as at least one requests is active the chunk will be ticked). */ void SetAlwaysTicked(bool a_AlwaysTicked); + // Makes a copy of the list + cClientHandleList GetAllClients(void) const {return m_LoadedByClient; } + private: friend class cChunkMap; @@ -530,9 +533,6 @@ private: /** Wakes up each simulator for its specific blocks; through all the blocks in the chunk */ void WakeUpSimulators(void); - - // Makes a copy of the list - cClientHandleList GetAllClients(void) const {return m_LoadedByClient; } /** Sends m_PendingSendBlocks to all clients */ void BroadcastPendingBlockChanges(void); diff --git a/src/ChunkMap.cpp b/src/ChunkMap.cpp index 394dc703b..edff6baf1 100644 --- a/src/ChunkMap.cpp +++ b/src/ChunkMap.cpp @@ -791,6 +791,28 @@ bool cChunkMap::DoWithChunk(int a_ChunkX, int a_ChunkZ, cChunkCallback & a_Callb } +bool cChunkMap::DoWithChunkAt(Vector3i a_BlockPos, std::function<bool(cChunk &)> a_Callback) +{ + int ChunkX, ChunkZ; + cChunkDef::BlockToChunk(a_BlockPos.x, a_BlockPos.z, ChunkX, ChunkZ); + struct cCallBackWrapper : cChunkCallback + { + cCallBackWrapper(std::function<bool(cChunk &)> a_InnerCallback) : + m_Callback(a_InnerCallback) + { + } + + virtual bool Item(cChunk * a_Chunk) + { + return m_Callback(*a_Chunk); + } + + private: + std::function<bool(cChunk &)> m_Callback; + } callback(a_Callback); + return DoWithChunk(ChunkX, ChunkZ, callback); +} + @@ -1880,10 +1902,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/ChunkMap.h b/src/ChunkMap.h index 0fac79c84..e9f1b94c0 100644 --- a/src/ChunkMap.h +++ b/src/ChunkMap.h @@ -104,6 +104,9 @@ public: /** Calls the callback for the chunk specified, with ChunkMapCS locked; returns false if the chunk doesn't exist, otherwise returns the same value as the callback */ bool DoWithChunk(int a_ChunkX, int a_ChunkZ, cChunkCallback & a_Callback); + /** Calls the callback for the chunk at the block position specified, with ChunkMapCS locked; returns false if the chunk doesn't exist, otherwise returns the same value as the callback **/ + bool DoWithChunkAt(Vector3i a_BlockPos, std::function<bool(cChunk &)> a_Callback); + /** Wakes up simulators for the specified block */ void WakeUpSimulators(int a_BlockX, int a_BlockY, int a_BlockZ); diff --git a/src/ClientHandle.cpp b/src/ClientHandle.cpp index 60a2f8873..b12ab419b 100644 --- a/src/ClientHandle.cpp +++ b/src/ClientHandle.cpp @@ -2374,6 +2374,15 @@ void cClientHandle::SendParticleEffect(const AString & a_ParticleName, float a_S +void cClientHandle::SendParticleEffect(const AString & a_ParticleName, const Vector3f a_Src, const Vector3f a_Offset, float a_ParticleData, int a_ParticleAmount, std::array<int, 2> a_Data) +{ + m_Protocol->SendParticleEffect(a_ParticleName, a_Src, a_Offset, a_ParticleData, a_ParticleAmount, a_Data); +} + + + + + void cClientHandle::SendPickupSpawn(const cPickup & a_Pickup) { m_Protocol->SendPickupSpawn(a_Pickup); diff --git a/src/ClientHandle.h b/src/ClientHandle.h index 9e5287985..7992d6bc2 100644 --- a/src/ClientHandle.h +++ b/src/ClientHandle.h @@ -22,6 +22,7 @@ #include "ChunkSender.h" +#include <array> @@ -177,6 +178,7 @@ public: // tolua_export void SendMapInfo (int a_ID, unsigned int a_Scale); void SendPaintingSpawn (const cPainting & a_Painting); void SendParticleEffect (const AString & a_ParticleName, float a_SrcX, float a_SrcY, float a_SrcZ, float a_OffsetX, float a_OffsetY, float a_OffsetZ, float a_ParticleData, int a_ParticleAmount); + void SendParticleEffect (const AString & a_ParticleName, const Vector3f a_Src, const Vector3f a_Offset, float a_ParticleData, int a_ParticleAmount, std::array<int, 2> a_Data); void SendPickupSpawn (const cPickup & a_Pickup); void SendPlayerAbilities (void); void SendPlayerListAddPlayer (const cPlayer & a_Player); 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/Floater.cpp b/src/Entities/Floater.cpp index cf8dd6c6f..0c868270d 100644 --- a/src/Entities/Floater.cpp +++ b/src/Entities/Floater.cpp @@ -6,7 +6,7 @@ #include "Floater.h" #include "Player.h" #include "../ClientHandle.h" - +#include "Broadcaster.h" @@ -145,12 +145,12 @@ void cFloater::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) { LOGD("Started producing particles for floater %i", GetUniqueID()); m_ParticlePos.Set(GetPosX() + (-4 + m_World->GetTickRandomNumber(8)), GetPosY(), GetPosZ() + (-4 + m_World->GetTickRandomNumber(8))); - m_World->BroadcastParticleEffect("splash", (float) m_ParticlePos.x, (float) m_ParticlePos.y, (float) m_ParticlePos.z, 0, 0, 0, 0, 15); + m_World->GetBroadcaster().BroadcastParticleEffect("splash", static_cast<Vector3f>(m_ParticlePos), Vector3f{}, 0, 15); } else if (m_CountDownTime < 20) { m_ParticlePos = (m_ParticlePos + (GetPosition() - m_ParticlePos) / 6); - m_World->BroadcastParticleEffect("splash", (float) m_ParticlePos.x, (float) m_ParticlePos.y, (float) m_ParticlePos.z, 0, 0, 0, 0, 15); + m_World->GetBroadcaster().BroadcastParticleEffect("splash", static_cast<Vector3f>(m_ParticlePos), Vector3f{}, 0, 15); } m_CountDownTime--; 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..9df5bd930 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,89 +121,137 @@ 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, and its minimum is 10. + (Recalculate lots when close, calculate rarely when far) */ + if ( + ((GetPosition() - m_PathFinderDestination).Length() < 0.25) || + ((m_TicksSinceLastPathReset > 10) && (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) + { + if (!EnsureProperDestination(a_Chunk)) + { + StopMovingToPosition(); // Invalid chunks, probably world is loading or something, cancel movement. + return false; + } + 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) + if (DoesPosYRequireJump(FloorC(m_NextWayPointPosition.y))) { - Vector3f Distance = m_FinalDestination - ShortestCoords; - Vector3f Distance2 = m_FinalDestination - *itr; - if (Distance.SqrLength() > Distance2.SqrLength()) + if ( + (IsOnGround() && (GetSpeedX() == 0) && (GetSpeedY() == 0)) || + (IsSwimming() && (m_GiveUpCounter < 15)) + ) { - 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); } } @@ -206,47 +259,92 @@ void cMonster::TickPathFinding() -void cMonster::MoveToPosition(const Vector3d & a_Position) +bool cMonster::EnsureProperDestination(cChunk & a_Chunk) { - FinishPathFinding(); + cChunk * Chunk = a_Chunk.GetNeighborChunk(m_FinalDestination.x, m_FinalDestination.z); + BLOCKTYPE BlockType; + NIBBLETYPE BlockMeta; + + if ((Chunk == nullptr) || !Chunk->IsValid()) + { + return false; + } + + int RelX = m_FinalDestination.x - Chunk->GetPosX() * cChunkDef::Width; + int RelZ = m_FinalDestination.z - Chunk->GetPosZ() * cChunkDef::Width; + + // If destination in the air, go down to the lowest air block. + while (m_FinalDestination.y > 0) + { + Chunk->GetBlockTypeMeta(RelX, m_FinalDestination.y - 1, RelZ, BlockType, BlockMeta); + if (cBlockInfo::IsSolid(BlockType)) + { + break; + } + m_FinalDestination.y -= 1; + } + - m_FinalDestination = a_Position; - m_bMovingToDestination = true; - TickPathFinding(); + // If destination in water, go up to the highest water block. + // If destination in solid, go up to first air block. + bool InWater = false; + while (m_FinalDestination.y < cChunkDef::Height) + { + Chunk->GetBlockTypeMeta(RelX, m_FinalDestination.y, RelZ, BlockType, BlockMeta); + if (BlockType == E_BLOCK_STATIONARY_WATER) + { + InWater = true; + } + else if (cBlockInfo::IsSolid(BlockType)) + { + InWater = false; + } + else + { + break; + } + m_FinalDestination.y += 1; + } + if (InWater) + { + m_FinalDestination.y -= 1; + } + + + return true; } -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,10 +354,11 @@ 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) { - // The mob is dead, but we're still animating the "puff" they leave when they die + // The mob is dead, but we're still animating the "puff" they leave when they die. m_DestroyTimer += a_Dt; if (m_DestroyTimer > std::chrono::seconds(1)) { @@ -268,73 +367,36 @@ 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 I burn in daylight, and I won't burn where I'm standing, and I'll burn in my next position, and at least one of those is true: + 1. I am idle + 2. I was not hurt by a player recently. + Then STOP. */ + if ( + m_BurnsInDaylight && ((m_TicksSinceLastDamaged >= 100) || (m_EMState == IDLE)) && + WouldBurnAt(m_NextWayPointPosition, *Chunk) && + !WouldBurnAt(GetPosition(), *Chunk) + ) { - 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 +407,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 +422,6 @@ void cMonster::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) InStateEscaping(a_Dt); break; } - case ATTACKING: break; } // switch (m_EMState) @@ -370,6 +431,7 @@ void cMonster::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) + void cMonster::SetPitchAndYawFromDestination() { Vector3d FinalDestination = m_FinalDestination; @@ -377,38 +439,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 +520,6 @@ int cMonster::FindFirstNonAirBlockPosition(double a_PosX, double a_PosZ) - bool cMonster::DoTakeDamage(TakeDamageInfo & a_TDI) { if (!super::DoTakeDamage(a_TDI)) @@ -476,6 +535,7 @@ bool cMonster::DoTakeDamage(TakeDamageInfo & a_TDI) if (a_TDI.Attacker != nullptr) { m_Target = a_TDI.Attacker; + m_TicksSinceLastDamaged = 0; } return true; } @@ -641,7 +701,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 +721,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 +746,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 +825,7 @@ AString cMonster::MobTypeToString(eMonsterType a_MobType) return g_MobTypeNames[i].m_lcName; } } - + // Not found: return ""; } @@ -866,7 +920,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 +1095,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 +1103,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 +1111,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,50 +1141,62 @@ 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) +{ + cChunk * Chunk = a_Chunk.GetNeighborChunk(FloorC(m_NextWayPointPosition.x), FloorC(m_NextWayPointPosition.z)); + if ((Chunk == nullptr) || (!Chunk->IsValid())) + { + return false; + } + 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..a2295777a 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,50 @@ 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); + + /** Move in a straight line to the next waypoint in the path, will jump if needed. */ + void MoveToWayPoint(cChunk & a_Chunk); + + /** Ensures the destination is not buried underground or under water. Also ensures the destination is not in the air. + Only the Y coordinate of m_FinalDestination might be changed. + 1. If m_FinalDestination is the position of a water block, m_FinalDestination's Y will be modified to point to the heighest water block in the pool in the current column. + 2. If m_FinalDestination is the position of a solid, m_FinalDestination's Y will be modified to point to the first airblock above the solid in the current column. + 3. If m_FinalDestination is the position of an air block, Y will keep decreasing until hitting either a solid or water. + Now either 1 or 2 is performed. */ + bool EnsureProperDestination(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 +253,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..8abbc4cac --- /dev/null +++ b/src/Mobs/Path.cpp @@ -0,0 +1,365 @@ + +#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; + } + + 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 + Vector3i(0, 0, 1)) || + (CurrentCell->m_Location == m_Destination + Vector3i(1, 0, 0)) || + (CurrentCell->m_Location == m_Destination + Vector3i(-1, 0, 0)) || + (CurrentCell->m_Location == m_Destination + Vector3i(0, 0, -1)) || + (CurrentCell->m_Location == m_Destination + Vector3i(0, -1, 0)) + ) + { + 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 = std::priority_queue<cPathCell *, std::vector<cPathCell *>, compareHeuristics>{}; +} + + + + + +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..0d903adb6 --- /dev/null +++ b/src/Mobs/Path.h @@ -0,0 +1,151 @@ + +#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..3c2ec1520 100644 --- a/src/Mobs/Wolf.cpp +++ b/src/Mobs/Wolf.cpp @@ -5,6 +5,7 @@ #include "../World.h" #include "../Entities/Player.h" #include "../Items/ItemHandler.h" +#include "Broadcaster.h" @@ -83,13 +84,13 @@ void cWolf::OnRightClicked(cPlayer & a_Player) SetIsTame(true); SetOwner(a_Player.GetName(), a_Player.GetUUID()); m_World->BroadcastEntityStatus(*this, esWolfTamed); - m_World->BroadcastParticleEffect("heart", (float) GetPosX(), (float) GetPosY(), (float) GetPosZ(), 0, 0, 0, 0, 5); + m_World->GetBroadcaster().BroadcastParticleEffect("heart", static_cast<Vector3f>(GetPosition()), Vector3f{}, 0, 5); } else { // Taming failed m_World->BroadcastEntityStatus(*this, esWolfTaming); - m_World->BroadcastParticleEffect("smoke", (float) GetPosX(), (float) GetPosY(), (float) GetPosZ(), 0, 0, 0, 0, 5); + m_World->GetBroadcaster().BroadcastParticleEffect("smoke", static_cast<Vector3f>(GetPosition()), Vector3f{}, 0, 5); } } } @@ -137,7 +138,7 @@ void cWolf::OnRightClicked(cPlayer & a_Player) } } } - + m_World->BroadcastEntityMetadata(*this); } @@ -203,7 +204,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/Protocol.h b/src/Protocol/Protocol.h index d8399049e..3bca7551b 100644 --- a/src/Protocol/Protocol.h +++ b/src/Protocol/Protocol.h @@ -16,6 +16,8 @@ #include "../Map.h" #include "../ByteBuffer.h" +#include <array> + @@ -98,6 +100,7 @@ public: virtual void SendPlayerAbilities (void) = 0; virtual void SendEntityAnimation (const cEntity & a_Entity, char a_Animation) = 0; virtual void SendParticleEffect (const AString & a_SoundName, float a_SrcX, float a_SrcY, float a_SrcZ, float a_OffsetX, float a_OffsetY, float a_OffsetZ, float a_ParticleData, int a_ParticleAmount) = 0; + virtual void SendParticleEffect (const AString & a_SoundName, Vector3f a_Src, Vector3f a_Offset, float a_ParticleData, int a_ParticleAmount, std::array<int, 2> a_Data) = 0; virtual void SendPlayerListAddPlayer (const cPlayer & a_Player) = 0; virtual void SendPlayerListRemovePlayer (const cPlayer & a_Player) = 0; virtual void SendPlayerListUpdateGameMode (const cPlayer & a_Player) = 0; diff --git a/src/Protocol/Protocol17x.cpp b/src/Protocol/Protocol17x.cpp index 2bc58e7e5..57631c37d 100644 --- a/src/Protocol/Protocol17x.cpp +++ b/src/Protocol/Protocol17x.cpp @@ -804,6 +804,16 @@ void cProtocol172::SendParticleEffect(const AString & a_ParticleName, float a_Sr +void cProtocol172::SendParticleEffect(const AString & a_ParticleName, Vector3f a_Src, Vector3f a_Offset, float a_ParticleData, int a_ParticleAmount, std::array<int, 2> a_Data) +{ + // 1.72 doesn't support extra data + this->SendParticleEffect(a_ParticleName, a_Src.x, a_Src.y, a_Src.z, a_Offset.x, a_Offset.y, a_Offset.z, a_ParticleData, a_ParticleAmount); +} + + + + + void cProtocol172::SendPlayerListAddPlayer(const cPlayer & a_Player) { ASSERT(m_State == 3); // In game mode? @@ -1728,7 +1738,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 +3180,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/Protocol17x.h b/src/Protocol/Protocol17x.h index 1212cc325..773c39f87 100644 --- a/src/Protocol/Protocol17x.h +++ b/src/Protocol/Protocol17x.h @@ -99,6 +99,7 @@ public: virtual void SendMapInfo (int a_ID, unsigned int a_Scale) override; virtual void SendPaintingSpawn (const cPainting & a_Painting) override; virtual void SendParticleEffect (const AString & a_ParticleName, float a_SrcX, float a_SrcY, float a_SrcZ, float a_OffsetX, float a_OffsetY, float a_OffsetZ, float a_ParticleData, int a_ParticleAmount) override; + virtual void SendParticleEffect (const AString & a_ParticleName, Vector3f a_Src, Vector3f a_Offset, float a_ParticleData, int a_ParticleAmount, std::array<int, 2> a_Data) override; virtual void SendPickupSpawn (const cPickup & a_Pickup) override; virtual void SendPlayerAbilities (void) override; virtual void SendPlayerListAddPlayer (const cPlayer & a_Player) override; diff --git a/src/Protocol/Protocol18x.cpp b/src/Protocol/Protocol18x.cpp index 0baae00de..628d8f528 100644 --- a/src/Protocol/Protocol18x.cpp +++ b/src/Protocol/Protocol18x.cpp @@ -802,6 +802,50 @@ void cProtocol180::SendParticleEffect(const AString & a_ParticleName, float a_Sr +void cProtocol180::SendParticleEffect(const AString & a_ParticleName, Vector3f a_Src, Vector3f a_Offset, float a_ParticleData, int a_ParticleAmount, std::array<int, 2> a_Data) +{ + ASSERT(m_State == 3); // In game mode? + int ParticleID = GetParticleID(a_ParticleName); + + cPacketizer Pkt(*this, 0x2A); + Pkt.WriteBEInt32(ParticleID); + Pkt.WriteBool(false); + Pkt.WriteBEFloat(a_Src.x); + Pkt.WriteBEFloat(a_Src.y); + Pkt.WriteBEFloat(a_Src.z); + Pkt.WriteBEFloat(a_Offset.x); + Pkt.WriteBEFloat(a_Offset.y); + Pkt.WriteBEFloat(a_Offset.z); + Pkt.WriteBEFloat(a_ParticleData); + Pkt.WriteBEInt32(a_ParticleAmount); + switch (ParticleID) + { + // iconcrack + case 36: + { + Pkt.WriteVarInt32(static_cast<UInt32>(a_Data[0])); + Pkt.WriteVarInt32(static_cast<UInt32>(a_Data[1])); + break; + } + // blockcrack + // blockdust + case 37: + case 38: + { + Pkt.WriteVarInt32(static_cast<UInt32>(a_Data[0])); + break; + } + default: + { + break; + } + } +} + + + + + void cProtocol180::SendPlayerListAddPlayer(const cPlayer & a_Player) { ASSERT(m_State == 3); // In game mode? @@ -1970,7 +2014,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 +2354,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/Protocol18x.h b/src/Protocol/Protocol18x.h index 9aa5ed827..21024d702 100644 --- a/src/Protocol/Protocol18x.h +++ b/src/Protocol/Protocol18x.h @@ -97,6 +97,7 @@ public: virtual void SendPlayerAbilities (void) override; virtual void SendEntityAnimation (const cEntity & a_Entity, char a_Animation) override; virtual void SendParticleEffect (const AString & a_ParticleName, float a_SrcX, float a_SrcY, float a_SrcZ, float a_OffsetX, float a_OffsetY, float a_OffsetZ, float a_ParticleData, int a_ParticleAmount) override; + virtual void SendParticleEffect (const AString & a_ParticleName, Vector3f a_Src, Vector3f a_Offset, float a_ParticleData, int a_ParticleAmount, std::array<int, 2> a_Data) override; virtual void SendPlayerListAddPlayer (const cPlayer & a_Player) override; virtual void SendPlayerListRemovePlayer (const cPlayer & a_Player) override; virtual void SendPlayerListUpdateGameMode (const cPlayer & a_Player) override; diff --git a/src/Protocol/ProtocolRecognizer.cpp b/src/Protocol/ProtocolRecognizer.cpp index 36f8bc791..e7f7a4526 100644 --- a/src/Protocol/ProtocolRecognizer.cpp +++ b/src/Protocol/ProtocolRecognizer.cpp @@ -439,6 +439,17 @@ void cProtocolRecognizer::SendParticleEffect(const AString & a_ParticleName, flo + +void cProtocolRecognizer::SendParticleEffect(const AString & a_ParticleName, Vector3f a_Src, Vector3f a_Offset, float a_ParticleData, int a_ParticleAmount, std::array<int, 2> a_Data) +{ + ASSERT(m_Protocol != nullptr); + m_Protocol->SendParticleEffect(a_ParticleName, a_Src, a_Offset, a_ParticleData, a_ParticleAmount, a_Data); +} + + + + + void cProtocolRecognizer::SendPaintingSpawn(const cPainting & a_Painting) { m_Protocol->SendPaintingSpawn(a_Painting); diff --git a/src/Protocol/ProtocolRecognizer.h b/src/Protocol/ProtocolRecognizer.h index d46d31cb1..6c2185d6d 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" @@ -81,6 +81,7 @@ public: virtual void SendMapDecorators (int a_ID, const cMapDecoratorList & a_Decorators, unsigned int m_Scale) override; virtual void SendMapInfo (int a_ID, unsigned int a_Scale) override; virtual void SendParticleEffect (const AString & a_ParticleName, float a_SrcX, float a_SrcY, float a_SrcZ, float a_OffsetX, float a_OffsetY, float a_OffsetZ, float a_ParticleData, int a_ParticleAmount) override; + virtual void SendParticleEffect (const AString & a_ParticleName, Vector3f a_Src, Vector3f a_Offset, float a_ParticleData, int a_ParticleAmount, std::array<int, 2> a_Data) override; virtual void SendPaintingSpawn (const cPainting & a_Painting) override; virtual void SendPickupSpawn (const cPickup & a_Pickup) override; virtual void SendPlayerAbilities (void) override; 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/SetChunkData.cpp b/src/SetChunkData.cpp index 7549b0dbf..c0ae31fd3 100644 --- a/src/SetChunkData.cpp +++ b/src/SetChunkData.cpp @@ -131,8 +131,11 @@ void cSetChunkData::RemoveInvalidBlockEntities(void) ItemTypeToString(EntityBlockType).c_str(), EntityBlockType, ItemTypeToString(WorldBlockType).c_str(), WorldBlockType ); + cBlockEntityList::iterator itr2 = itr; + ++itr2; delete *itr; - itr = m_BlockEntities.erase(itr); + m_BlockEntities.erase(itr); + itr = itr2; } else { diff --git a/src/Vector3.h b/src/Vector3.h index 355ba6a52..ed3f296a6 100644 --- a/src/Vector3.h +++ b/src/Vector3.h @@ -374,6 +374,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..6c2e31965 100644 --- a/src/World.cpp +++ b/src/World.cpp @@ -57,7 +57,7 @@ #include <stdlib.h> #endif - +#include "Broadcaster.h" @@ -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; @@ -1459,6 +1459,15 @@ bool cWorld::DoWithChunk(int a_ChunkX, int a_ChunkZ, cChunkCallback & a_Callback +bool cWorld::DoWithChunkAt(Vector3i a_BlockPos, std::function<bool(cChunk &)> a_Callback) +{ + return m_ChunkMap->DoWithChunkAt(a_BlockPos, a_Callback); +} + + + + + void cWorld::GrowTree(int a_X, int a_Y, int a_Z) { if (GetBlock(a_X, a_Y, a_Z) == E_BLOCK_SAPLING) @@ -2241,14 +2250,6 @@ void cWorld::BroadcastEntityAnimation(const cEntity & a_Entity, char a_Animation -void cWorld::BroadcastParticleEffect(const AString & a_ParticleName, float a_SrcX, float a_SrcY, float a_SrcZ, float a_OffsetX, float a_OffsetY, float a_OffsetZ, float a_ParticleData, int a_ParticleAmount, cClientHandle * a_Exclude) -{ - m_ChunkMap->BroadcastParticleEffect(a_ParticleName, a_SrcX, a_SrcY, a_SrcZ, a_OffsetX, a_OffsetY, a_OffsetZ, a_ParticleData, a_ParticleAmount, a_Exclude); -} - - - - void cWorld::BroadcastPlayerListAddPlayer(const cPlayer & a_Player, const cClientHandle * a_Exclude) { @@ -3770,5 +3771,10 @@ void cWorld::cChunkGeneratorCallbacks::CallHookChunkGenerated (cChunkDesc & a_Ch +cBroadcaster cWorld::GetBroadcaster() +{ + return cBroadcaster(this); +} + diff --git a/src/World.h b/src/World.h index 1de241f60..624262cd3 100644 --- a/src/World.h +++ b/src/World.h @@ -55,6 +55,7 @@ class cMobHeadEntity; class cCompositeChat; class cCuboid; class cSetChunkData; +class cBroadcaster; typedef std::list< cPlayer * > cPlayerList; @@ -243,7 +244,6 @@ public: void BroadcastEntityStatus (const cEntity & a_Entity, char a_Status, const cClientHandle * a_Exclude = nullptr); void BroadcastEntityVelocity (const cEntity & a_Entity, const cClientHandle * a_Exclude = nullptr); virtual void BroadcastEntityAnimation (const cEntity & a_Entity, char a_Animation, const cClientHandle * a_Exclude = nullptr) override; // tolua_export - void BroadcastParticleEffect (const AString & a_ParticleName, float a_SrcX, float a_SrcY, float a_SrcZ, float a_OffsetX, float a_OffsetY, float a_OffsetZ, float a_ParticleData, int a_ParticleAmount, cClientHandle * a_Exclude = nullptr); // tolua_export void BroadcastPlayerListAddPlayer (const cPlayer & a_Player, const cClientHandle * a_Exclude = nullptr); void BroadcastPlayerListRemovePlayer (const cPlayer & a_Player, const cClientHandle * a_Exclude = nullptr); void BroadcastPlayerListUpdateGameMode (const cPlayer & a_Player, const cClientHandle * a_Exclude = nullptr); @@ -610,6 +610,9 @@ public: /** Calls the callback for the chunk specified, with ChunkMapCS locked; returns false if the chunk doesn't exist, otherwise returns the same value as the callback */ bool DoWithChunk(int a_ChunkX, int a_ChunkZ, cChunkCallback & a_Callback); + /** Calls the callback for the chunk at the block position specified, with ChunkMapCS locked; returns false if the chunk doesn't exist, otherwise returns the same value as the callback **/ + bool DoWithChunkAt(Vector3i a_BlockPos, std::function<bool(cChunk &)> a_Callback); + void GrowTreeImage(const sSetBlockVector & a_Blocks); // tolua_begin @@ -828,6 +831,8 @@ public: This function allows nesting and task-concurrency (multiple separate tasks can request ticking and as long as at least one requests is active the chunk will be ticked). */ void SetChunkAlwaysTicked(int a_ChunkX, int a_ChunkZ, bool a_AlwaysTicked = true); // tolua_export + + cBroadcaster GetBroadcaster(); private: 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(); } |