diff options
36 files changed, 558 insertions, 135 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 76b91e642..2bb780eba 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -78,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 f27451351..82f09b6bf 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,7 +1,27 @@ -Code Stuff +Code Conventions ---------- +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/MCServer/Plugins/APIDump/APIDesc.lua b/MCServer/Plugins/APIDump/APIDesc.lua index 79340434d..2c26ccecc 100644 --- a/MCServer/Plugins/APIDump/APIDesc.lua +++ b/MCServer/Plugins/APIDump/APIDesc.lua @@ -8,6 +8,7 @@ g_APIDesc = { Classes = + { --[[ -- What the APIDump plugin understands / how to document stuff: ExampleClassName = 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/crafting.txt b/MCServer/crafting.txt index 1e1f06156..e7d11ab92 100644 --- a/MCServer/crafting.txt +++ b/MCServer/crafting.txt @@ -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/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/src/Bindings/LuaState.cpp b/src/Bindings/LuaState.cpp index ed31e678f..9c1e2865c 100644 --- a/src/Bindings/LuaState.cpp +++ b/src/Bindings/LuaState.cpp @@ -1133,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 @@ -1415,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); @@ -1494,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; } @@ -1502,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; @@ -1513,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 6bedbf5ec..3f2e828f3 100644 --- a/src/Bindings/LuaState.h +++ b/src/Bindings/LuaState.h @@ -76,6 +76,7 @@ typedef cPluginManager * pPluginManager; typedef cRoot * pRoot; typedef cScoreboard * pScoreboard; typedef cWorld * pWorld; +typedef cClientHandle * pClientHandle; @@ -254,6 +255,7 @@ public: 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); @@ -307,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 f25800d5f..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 @@ -1987,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; } @@ -2009,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: @@ -3792,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>); 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 e04e6311f..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 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 b84b8dc3d..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); +} + 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/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/Mobs/Monster.cpp b/src/Mobs/Monster.cpp index 37774b08f..9df5bd930 100644 --- a/src/Mobs/Monster.cpp +++ b/src/Mobs/Monster.cpp @@ -156,6 +156,11 @@ bool cMonster::TickPathFinding(cChunk & a_Chunk) 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); } @@ -199,10 +204,12 @@ void cMonster::MoveToWayPoint(cChunk & a_Chunk) { if (m_JumpCoolDown == 0) { - // We're not moving (or barely moving), and waypoint is above us, it means we are hitting something and we should jump. - if ((GetSpeedX() < 0.1) && (GetSpeedZ() < 0.1) && DoesPosYRequireJump(FloorC(m_NextWayPointPosition.y))) + if (DoesPosYRequireJump(FloorC(m_NextWayPointPosition.y))) { - if (IsOnGround() || IsSwimming()) + if ( + (IsOnGround() && (GetSpeedX() == 0) && (GetSpeedY() == 0)) || + (IsSwimming() && (m_GiveUpCounter < 15)) + ) { m_bOnGround = false; m_JumpCoolDown = 20; @@ -252,6 +259,65 @@ void cMonster::MoveToWayPoint(cChunk & a_Chunk) +bool cMonster::EnsureProperDestination(cChunk & a_Chunk) +{ + 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; + } + + + // 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; +} + + + + + void cMonster::MoveToPosition(const Vector3d & a_Position) { m_FinalDestination = a_Position; @@ -292,7 +358,7 @@ void cMonster::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) 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)) { @@ -310,11 +376,19 @@ void cMonster::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) m_Target = nullptr; } - // Process the undead burning in daylight + // Process the undead burning in daylight. HandleDaylightBurning(*Chunk, WouldBurnAt(GetPosition(), *Chunk)); if (TickPathFinding(*Chunk)) { - if (m_BurnsInDaylight && WouldBurnAt(m_NextWayPointPosition, *Chunk->GetNeighborChunk(FloorC(m_NextWayPointPosition.x), FloorC(m_NextWayPointPosition.z))) && !IsOnFire() && (m_TicksSinceLastDamaged == 100)) + /* 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 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(); @@ -1098,6 +1172,11 @@ void cMonster::HandleDaylightBurning(cChunk & a_Chunk, bool WouldBurn) 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; @@ -1121,7 +1200,3 @@ cMonster::eFamily cMonster::GetMobFamily(void) const { return FamilyFromType(m_MobType); } - - - - diff --git a/src/Mobs/Monster.h b/src/Mobs/Monster.h index c7f38c9f7..a2295777a 100644 --- a/src/Mobs/Monster.h +++ b/src/Mobs/Monster.h @@ -203,8 +203,18 @@ protected: 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. */ diff --git a/src/Mobs/Path.cpp b/src/Mobs/Path.cpp index 60f88f525..8abbc4cac 100644 --- a/src/Mobs/Path.cpp +++ b/src/Mobs/Path.cpp @@ -1,3 +1,4 @@ + #include "Globals.h" #include <cmath> @@ -54,31 +55,6 @@ cPath::cPath( return; } - // If destination in water, set water surface as destination. - cChunk * Chunk = m_Chunk->GetNeighborChunk(m_Destination.x, m_Destination.z); - if ((Chunk != nullptr) && Chunk->IsValid()) - { - BLOCKTYPE BlockType; - NIBBLETYPE BlockMeta; - int RelX = m_Destination.x - Chunk->GetPosX() * cChunkDef::Width; - int RelZ = m_Destination.z - Chunk->GetPosZ() * cChunkDef::Width; - bool inwater = false; - for (;;) - { - Chunk->GetBlockTypeMeta(RelX, m_Destination.y, RelZ, BlockType, BlockMeta); - if (BlockType != E_BLOCK_STATIONARY_WATER) - { - break; - } - inwater = true; - m_Destination+=Vector3d(0, 1, 0); - } - if (inwater) - { - m_Destination+=Vector3d(0, -1, 0); - } - } - m_Status = ePathFinderStatus::CALCULATING; m_StepsLeft = a_MaxSteps; diff --git a/src/Mobs/Path.h b/src/Mobs/Path.h index 9e893f1d7..0d903adb6 100644 --- a/src/Mobs/Path.h +++ b/src/Mobs/Path.h @@ -1,3 +1,4 @@ + #pragma once /* Wanna use the pathfinder? Put this in your header file: diff --git a/src/Mobs/Wolf.cpp b/src/Mobs/Wolf.cpp index c66763f17..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); } } } 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 8e7d526ef..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? 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 4429ca683..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? 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 13be9478f..6c2185d6d 100644 --- a/src/Protocol/ProtocolRecognizer.h +++ b/src/Protocol/ProtocolRecognizer.h @@ -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/SetChunkData.cpp b/src/SetChunkData.cpp index f2b58570d..c0ae31fd3 100644 --- a/src/SetChunkData.cpp +++ b/src/SetChunkData.cpp @@ -103,7 +103,7 @@ void cSetChunkData::CalculateHeightMap(void) int index = cChunkDef::MakeIndexNoCheck(x, y, z); if (m_BlockTypes[index] != E_BLOCK_AIR) { - m_HeightMap[x + z * cChunkDef::Width] = (HEIGHTTYPE)y; + m_HeightMap[x + z * cChunkDef::Width] = static_cast<HEIGHTTYPE>(y); break; } } // for y diff --git a/src/Tracer.cpp b/src/Tracer.cpp index e604f4a5b..b6b0fd634 100644 --- a/src/Tracer.cpp +++ b/src/Tracer.cpp @@ -12,17 +12,32 @@ +const float FLOAT_EPSILON = 0.0001f; // TODO: Stash this in some header where it can be reused + + +const std::array<const Vector3f, 6>& cTracer::m_NormalTable(void) +{ + static std::array<const Vector3f, 6>* table = + new std::array<const Vector3f, 6> + { + { + Vector3f(-1, 0, 0), // 1: -x + Vector3f( 0, 0, -1), // 2: -z + Vector3f( 1, 0, 0), // 3: +x + Vector3f( 0, 0, 1), // 4: +z + Vector3f( 0, 1, 0), // 5: +y + Vector3f( 0, -1, 0) // 6: -y + } + }; + + return *table; +}; + cTracer::cTracer(cWorld * a_World): m_World(a_World) { - m_NormalTable[0].Set(-1, 0, 0); - m_NormalTable[1].Set( 0, 0, -1); - m_NormalTable[2].Set( 1, 0, 0); - m_NormalTable[3].Set( 0, 0, 1); - m_NormalTable[4].Set( 0, 1, 0); - m_NormalTable[5].Set( 0, -1, 0); } @@ -37,7 +52,7 @@ cTracer::~cTracer() -float cTracer::SigNum(float a_Num) +int cTracer::SigNum(float a_Num) { if (a_Num < 0.f) { @@ -56,26 +71,28 @@ float cTracer::SigNum(float a_Num) void cTracer::SetValues(const Vector3f & a_Start, const Vector3f & a_Direction) { + // Since this method should only be called by Trace, zero length vectors should already have been taken care of + ASSERT(a_Direction.HasNonZeroLength()); + // calculate the direction of the ray (linear algebra) dir = a_Direction; // decide which direction to start walking in - step.x = (int) SigNum(dir.x); - step.y = (int) SigNum(dir.y); - step.z = (int) SigNum(dir.z); + step.x = SigNum(dir.x); + step.y = SigNum(dir.y); + step.z = SigNum(dir.z); + // normalize the direction vector - if (dir.SqrLength() > 0.f) - { - dir.Normalize(); - } + dir.Normalize(); + // how far we must move in the ray direction before // we encounter a new voxel in x-direction // same but y-direction if (dir.x != 0.f) { - tDelta.x = 1 / fabs(dir.x); + tDelta.x = 1 / std::abs(dir.x); } else { @@ -83,7 +100,7 @@ void cTracer::SetValues(const Vector3f & a_Start, const Vector3f & a_Direction) } if (dir.y != 0.f) { - tDelta.y = 1 / fabs(dir.y); + tDelta.y = 1 / std::abs(dir.y); } else { @@ -91,44 +108,45 @@ void cTracer::SetValues(const Vector3f & a_Start, const Vector3f & a_Direction) } if (dir.z != 0.f) { - tDelta.z = 1 / fabs(dir.z); + tDelta.z = 1 / std::abs(dir.z); } else { tDelta.z = 0; } + // start voxel coordinates - pos.x = (int)floorf(a_Start.x); - pos.y = (int)floorf(a_Start.y); - pos.z = (int)floorf(a_Start.z); + pos.x = static_cast<int>(floorf(a_Start.x)); + pos.y = static_cast<int>(floorf(a_Start.y)); + pos.z = static_cast<int>(floorf(a_Start.z)); // calculate distance to first intersection in the voxel we start from if (dir.x < 0) { - tMax.x = ((float)pos.x - a_Start.x) / dir.x; + tMax.x = (static_cast<float>(pos.x) - a_Start.x) / dir.x; } else { - tMax.x = (((float)pos.x + 1) - a_Start.x) / dir.x; + tMax.x = (static_cast<float>(pos.x + 1) - a_Start.x) / dir.x; // TODO: Possible division by zero } if (dir.y < 0) { - tMax.y = ((float)pos.y - a_Start.y) / dir.y; + tMax.y = (static_cast<float>(pos.y) - a_Start.y) / dir.y; } else { - tMax.y = (((float)pos.y + 1) - a_Start.y) / dir.y; + tMax.y = (static_cast<float>(pos.y + 1) - a_Start.y) / dir.y; // TODO: Possible division by zero } if (dir.z < 0) { - tMax.z = ((float)pos.z - a_Start.z) / dir.z; + tMax.z = (static_cast<float>(pos.z) - a_Start.z) / dir.z; } else { - tMax.z = (((float)pos.z + 1) - a_Start.z) / dir.z; + tMax.z = (static_cast<float>(pos.z + 1) - a_Start.z) / dir.z; // TODO: Possible division by zero } } @@ -138,6 +156,11 @@ void cTracer::SetValues(const Vector3f & a_Start, const Vector3f & a_Direction) bool cTracer::Trace(const Vector3f & a_Start, const Vector3f & a_Direction, int a_Distance, bool a_LineOfSight) { + if (!a_Direction.HasNonZeroLength()) + { + return false; + } + if ((a_Start.y < 0) || (a_Start.y >= cChunkDef::Height)) { LOGD("%s: Start Y is outside the world (%.2f), not tracing.", __FUNCTION__, a_Start.y); @@ -146,18 +169,18 @@ bool cTracer::Trace(const Vector3f & a_Start, const Vector3f & a_Direction, int SetValues(a_Start, a_Direction); - Vector3f End = a_Start + (dir * (float)a_Distance); + Vector3f End = a_Start + (dir * static_cast<float>(a_Distance)); if (End.y < 0) { - float dist = -a_Start.y / dir.y; + float dist = -a_Start.y / dir.y; // No division by 0 possible End = a_Start + (dir * dist); } // end voxel coordinates - end1.x = (int)floorf(End.x); - end1.y = (int)floorf(End.y); - end1.z = (int)floorf(End.z); + end1.x = static_cast<int>(floorf(End.x)); + end1.y = static_cast<int>(floorf(End.y)); + end1.z = static_cast<int>(floorf(End.z)); // check if first is occupied if (pos.Equals(end1)) @@ -241,7 +264,7 @@ bool cTracer::Trace(const Vector3f & a_Start, const Vector3f & a_Direction, int int Normal = GetHitNormal(a_Start, End, pos); if (Normal > 0) { - HitNormal = m_NormalTable[Normal-1]; + HitNormal = m_NormalTable()[Normal - 1]; } return true; } @@ -295,8 +318,7 @@ int cTracer::intersect3D_SegmentPlane(const Vector3f & a_Origin, const Vector3f float D = a_PlaneNormal.Dot(u); // dot(Pn.n, u); float N = -(a_PlaneNormal.Dot(w)); // -dot(a_Plane.n, w); - const float EPSILON = 0.0001f; - if (fabs(D) < EPSILON) + if (std::abs(D) < FLOAT_EPSILON) { // segment is parallel to plane if (N == 0.0) diff --git a/src/Tracer.h b/src/Tracer.h index ec87d449e..31531719f 100644 --- a/src/Tracer.h +++ b/src/Tracer.h @@ -3,6 +3,8 @@ #include "Vector3.h" +#include <array> + @@ -61,10 +63,11 @@ private: /// Return 1 through 6 for the following block faces, repectively: -x, -z, x, z, y, -y int GetHitNormal( const Vector3f & start, const Vector3f & end, const Vector3i & a_BlockPos); - float SigNum( float a_Num); + /// Signum function + int SigNum( float a_Num); cWorld* m_World; - Vector3f m_NormalTable[6]; + static const std::array<const Vector3f, 6> & m_NormalTable(void); Vector3f dir; Vector3f tDelta; diff --git a/src/Vector3.h b/src/Vector3.h index c5431438e..ed3f296a6 100644 --- a/src/Vector3.h +++ b/src/Vector3.h @@ -78,6 +78,20 @@ public: ); } + inline bool HasNonZeroLength(void) const + { + #ifdef __clang__ + #pragma clang diagnostics push + #pragma clang diagnostics ignored "-Wfloat-equal" + #endif + + return ((x != 0) || (y != 0) || (z != 0)); + + #ifdef __clang__ + #pragma clang diagnostics pop + #endif + } + inline double Length(void) const { return sqrt(static_cast<double>(x * x + y * y + z * z)); @@ -119,13 +133,19 @@ public: inline bool Equals(const Vector3<T> & a_Rhs) const { - // Perform a bitwise comparison of the contents - we want to know whether this object is exactly equal + // Perform a strict comparison of the contents - we want to know whether this object is exactly equal // To perform EPS-based comparison, use the EqualsEps() function - return ( - (memcmp(&x, &a_Rhs.x, sizeof(x)) == 0) && - (memcmp(&y, &a_Rhs.y, sizeof(y)) == 0) && - (memcmp(&z, &a_Rhs.z, sizeof(z)) == 0) - ); + + #ifdef __clang__ + #pragma clang diagnostics push + #pragma clang diagnostics ignored "-Wfloat-equal" + #endif + + return !((x != a_Rhs.x) || (y != a_Rhs.y) || (z != a_Rhs.z)); + + #ifdef __clang__ + #pragma clang diagnostics pop + #endif } inline bool EqualsEps(const Vector3<T> & a_Rhs, T a_Eps) const diff --git a/src/World.cpp b/src/World.cpp index 87209e4c2..6c2e31965 100644 --- a/src/World.cpp +++ b/src/World.cpp @@ -57,7 +57,7 @@ #include <stdlib.h> #endif - +#include "Broadcaster.h" @@ -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: |