diff --git a/.travis.yml b/.travis.yml index 2fd3bf991..6dde5b62b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,7 +2,7 @@ language: cpp compiler: clang before_install: - - if [ "$TRAVIS_MCSERVER_BUILD_TYPE" == "COVERAGE" ]; then sudo pip install cpp_coveralls; fi +# - if [ "$TRAVIS_MCSERVER_BUILD_TYPE" == "COVERAGE" ]; then sudo pip install cpp_coveralls; fi # g++4.8 - sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test @@ -21,17 +21,17 @@ install: # Build MCServer script: ./CIbuild.sh -after_success: - - ./uploadCoverage.sh +#after_success: +# - ./uploadCoverage.sh env: - TRAVIS_MCSERVER_BUILD_TYPE=RELEASE MCSERVER_PATH=./MCServer - TRAVIS_MCSERVER_BUILD_TYPE=DEBUG MCSERVER_PATH=./MCServer_debug -matrix: - include: - - compiler: gcc - env: TRAVIS_MCSERVER_BUILD_TYPE=COVERAGE MCSERVER_PATH=./MCServer +#matrix: +# include: +# - compiler: gcc +# env: TRAVIS_MCSERVER_BUILD_TYPE=COVERAGE MCSERVER_PATH=./MCServer # Notification Settings notifications: diff --git a/MCServer/Plugins/APIDump/main_APIDump.lua b/MCServer/Plugins/APIDump/main_APIDump.lua index 4ca06b974..e841922b6 100644 --- a/MCServer/Plugins/APIDump/main_APIDump.lua +++ b/MCServer/Plugins/APIDump/main_APIDump.lua @@ -971,7 +971,7 @@ end --- Writes a list of undocumented objects into a file local function ListUndocumentedObjects(API, UndocumentedHooks) - f = io.open("API/_undocumented.lua", "w"); + local f = io.open("API/_undocumented.lua", "w"); if (f ~= nil) then f:write("\n-- This is the list of undocumented API objects, automatically generated by APIDump\n\n"); f:write("g_APIDesc =\n{\n\tClasses =\n\t{\n"); diff --git a/README.md b/README.md index 87c4ee93e..3d013eff5 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,14 @@ -MCServer [![Build Status](http://img.shields.io/travis/mc-server/MCServer/master.svg?style=flat)](https://travis-ci.org/mc-server/MCServer) [![Coverity Scan Build Status](https://scan.coverity.com/projects/1930/badge.svg)](https://scan.coverity.com/projects/1930) [![weekly tips](http://img.shields.io/gratipay/cuberite_team.svg?style=flat)](http://gratipay.com/cuberite_team) [![tip for next commit](http://tip4commit.com/projects/74.svg)](http://tip4commit.com/projects/74) +MCServer [![Build Status](http://img.shields.io/travis/mc-server/MCServer/master.svg?style=flat)](https://travis-ci.org/mc-server/MCServer) [![Coverity Scan Build Status](https://img.shields.io/coverity/scan/1930.svg)](https://scan.coverity.com/projects/1930) [![weekly tips](http://img.shields.io/gratipay/cuberite_team.svg?style=flat)](http://gratipay.com/cuberite_team) ======== -MCServer is a Minecraft server that is written in C++ and designed to be efficient with memory and CPU, as well as having a flexible Lua Plugin API. +MCServer is a Minecraft-compatible multiplayer game server that is written in C++ and designed to be efficient with memory and CPU, as well as having a flexible Lua Plugin API. MCServer is compatible with the vanilla Minecraft client. MCServer can run on Windows, *nix and Android operating systems. This includes Android phones and tablets as well as Raspberry Pis. We currently support Release 1.7 and 1.8 (not beta) Minecraft protocol versions. +Subscribe to [the newsletter](http://newsletter.cuberite.org/subscribe.htm) for important updates and project news. + Installation ------------ Hosted MCServer is available DIY on DigitalOcean: [![Install on DigitalOcean](http://doinstall.bearbin.net/button.svg)](http://doinstall.bearbin.net/install?url=https://github.com/mc-server/MCServer) and [Gamososm](https://gamocosm.com) also offers MCServer support. diff --git a/src/Bindings/LuaState.cpp b/src/Bindings/LuaState.cpp index ccf812417..08c7e19d7 100644 --- a/src/Bindings/LuaState.cpp +++ b/src/Bindings/LuaState.cpp @@ -927,6 +927,9 @@ bool cLuaState::CheckParamTable(int a_StartParam, int a_EndParam) VERIFY(lua_getstack(m_LuaState, 0, &entry)); VERIFY(lua_getinfo (m_LuaState, "n", &entry)); AString ErrMsg = Printf("#ferror in function '%s'.", (entry.name != nullptr) ? entry.name : "?"); + + BreakIntoDebugger(m_LuaState); + tolua_error(m_LuaState, ErrMsg.c_str(), &tolua_err); return false; } // for i - Param @@ -1366,6 +1369,7 @@ int cLuaState::ReportFnCallErrors(lua_State * a_LuaState) { LOGWARNING("LUA: %s", lua_tostring(a_LuaState, -1)); LogStackTrace(a_LuaState, 1); + BreakIntoDebugger(a_LuaState); return 1; // We left the error message on the stack as the return value } @@ -1373,6 +1377,28 @@ int cLuaState::ReportFnCallErrors(lua_State * a_LuaState) +int cLuaState::BreakIntoDebugger(lua_State * a_LuaState) +{ + // Call the BreakIntoDebugger function, if available: + lua_getglobal(a_LuaState, "BreakIntoDebugger"); + if (!lua_isfunction(a_LuaState, -1)) + { + LOGD("LUA: BreakIntoDebugger() not found / not a function"); + lua_pop(a_LuaState, 1); + return 1; + } + lua_insert(a_LuaState, -2); // Copy the string that has been passed to us + LOGD("Calling BreakIntoDebugger()..."); + lua_call(a_LuaState, 1, 0); + LOGD("Returned from BreakIntoDebugger()."); + + return 0; +} + + + + + //////////////////////////////////////////////////////////////////////////////// // cLuaState::cRef: diff --git a/src/Bindings/LuaState.h b/src/Bindings/LuaState.h index b38401fd8..5b4ec3ae4 100644 --- a/src/Bindings/LuaState.h +++ b/src/Bindings/LuaState.h @@ -389,6 +389,9 @@ protected: /** Used as the error reporting function for function calls */ static int ReportFnCallErrors(lua_State * a_LuaState); + + /** Tries to break into the MobDebug debugger, if it is installed. */ + static int BreakIntoDebugger(lua_State * a_LuaState); } ; diff --git a/src/Bindings/ManualBindings.cpp b/src/Bindings/ManualBindings.cpp index 324ca9b89..ff904d74a 100644 --- a/src/Bindings/ManualBindings.cpp +++ b/src/Bindings/ManualBindings.cpp @@ -747,39 +747,38 @@ static int tolua_cPluginManager_AddHook(lua_State * tolua_S) static int tolua_cPluginManager_ForEachCommand(lua_State * tolua_S) { - int NumArgs = lua_gettop(tolua_S) - 1; /* This includes 'self' */ - if (NumArgs != 1) + /* + Function signature: + cPluginManager:Get():ForEachCommand(a_CallbackFn) -> bool + */ + + // Check params: + cLuaState L(tolua_S); + if ( + !L.CheckParamUserType(1, "cPluginManager") || + !L.CheckParamFunction(2) || + !L.CheckParamEnd(3) + ) { - LOGWARN("Error in function call 'ForEachCommand': Requires 1 argument, got %i", NumArgs); return 0; } - cPluginManager * self = (cPluginManager *)tolua_tousertype(tolua_S, 1, nullptr); - if (self == nullptr) - { - LOGWARN("Error in function call 'ForEachCommand': Not called on an object instance"); - return 0; - } - - if (!lua_isfunction(tolua_S, 2)) - { - LOGWARN("Error in function call 'ForEachCommand': Expected a function for parameter #1"); - return 0; - } - - int FuncRef = luaL_ref(tolua_S, LUA_REGISTRYINDEX); - if (FuncRef == LUA_REFNIL) + // Get the params: + cLuaState::cRef FnRef; + L.GetStackValues(2, FnRef); + if (!FnRef.IsValid()) { LOGWARN("Error in function call 'ForEachCommand': Could not get function reference of parameter #1"); return 0; } + // Callback for enumerating all commands: class cLuaCallback : public cPluginManager::cCommandEnumCallback { public: - cLuaCallback(lua_State * a_LuaState, int a_FuncRef): - LuaState(a_LuaState), - FuncRef(a_FuncRef) + cLuaCallback(cLuaState & a_LuaState, cLuaState::cRef & a_FnRef): + m_LuaState(a_LuaState), + m_FnRef(a_FnRef) { } @@ -787,35 +786,16 @@ static int tolua_cPluginManager_ForEachCommand(lua_State * tolua_S) virtual bool Command(const AString & a_Command, const cPlugin * a_Plugin, const AString & a_Permission, const AString & a_HelpString) override { UNUSED(a_Plugin); - - lua_rawgeti(LuaState, LUA_REGISTRYINDEX, FuncRef); /* Push function reference */ - tolua_pushcppstring(LuaState, a_Command); - tolua_pushcppstring(LuaState, a_Permission); - tolua_pushcppstring(LuaState, a_HelpString); - - int s = lua_pcall(LuaState, 3, 1, 0); - if (cLuaState::ReportErrors(LuaState, s)) - { - return true; /* Abort enumeration */ - } - - if (lua_isboolean(LuaState, -1)) - { - return (tolua_toboolean(LuaState, -1, 0) > 0); - } - return false; /* Continue enumeration */ + bool ret = false; + m_LuaState.Call(m_FnRef, a_Command, a_Permission, a_HelpString, cLuaState::Return, ret); + return ret; } - lua_State * LuaState; - int FuncRef; - } Callback(tolua_S, FuncRef); + cLuaState & m_LuaState; + cLuaState::cRef & m_FnRef; + } Callback(L, FnRef); - bool bRetVal = self->ForEachCommand(Callback); - - /* Unreference the values again, so the LUA_REGISTRYINDEX can make place for other references */ - luaL_unref(tolua_S, LUA_REGISTRYINDEX, FuncRef); - - /* Push return value on stack */ - tolua_pushboolean(tolua_S, bRetVal); + // Execute and push the returned value: + L.Push(cPluginManager::Get()->ForEachCommand(Callback)); return 1; } @@ -825,39 +805,38 @@ static int tolua_cPluginManager_ForEachCommand(lua_State * tolua_S) static int tolua_cPluginManager_ForEachConsoleCommand(lua_State * tolua_S) { - int NumArgs = lua_gettop(tolua_S) - 1; /* This includes 'self' */ - if (NumArgs != 1) + /* + Function signature: + cPluginManager:Get():ForEachConsoleCommand(a_CallbackFn) -> bool + */ + + // Check params: + cLuaState L(tolua_S); + if ( + !L.CheckParamUserType(1, "cPluginManager") || + !L.CheckParamFunction(2) || + !L.CheckParamEnd(3) + ) { - LOGWARN("Error in function call 'ForEachConsoleCommand': Requires 1 argument, got %i", NumArgs); return 0; } - cPluginManager * self = (cPluginManager *)tolua_tousertype(tolua_S, 1, nullptr); - if (self == nullptr) - { - LOGWARN("Error in function call 'ForEachConsoleCommand': Not called on an object instance"); - return 0; - } - - if (!lua_isfunction(tolua_S, 2)) - { - LOGWARN("Error in function call 'ForEachConsoleCommand': Expected a function for parameter #1"); - return 0; - } - - int FuncRef = luaL_ref(tolua_S, LUA_REGISTRYINDEX); - if (FuncRef == LUA_REFNIL) + // Get the params: + cLuaState::cRef FnRef; + L.GetStackValues(2, FnRef); + if (!FnRef.IsValid()) { LOGWARN("Error in function call 'ForEachConsoleCommand': Could not get function reference of parameter #1"); return 0; } + // Callback for enumerating all commands: class cLuaCallback : public cPluginManager::cCommandEnumCallback { public: - cLuaCallback(lua_State * a_LuaState, int a_FuncRef): - LuaState(a_LuaState), - FuncRef(a_FuncRef) + cLuaCallback(cLuaState & a_LuaState, cLuaState::cRef & a_FnRef): + m_LuaState(a_LuaState), + m_FnRef(a_FnRef) { } @@ -866,34 +845,16 @@ static int tolua_cPluginManager_ForEachConsoleCommand(lua_State * tolua_S) { UNUSED(a_Plugin); UNUSED(a_Permission); - - lua_rawgeti(LuaState, LUA_REGISTRYINDEX, FuncRef); /* Push function reference */ - tolua_pushcppstring(LuaState, a_Command); - tolua_pushcppstring(LuaState, a_HelpString); - - int s = lua_pcall(LuaState, 2, 1, 0); - if (cLuaState::ReportErrors(LuaState, s)) - { - return true; /* Abort enumeration */ - } - - if (lua_isboolean(LuaState, -1)) - { - return (tolua_toboolean(LuaState, -1, 0) > 0); - } - return false; /* Continue enumeration */ + bool ret = false; + m_LuaState.Call(m_FnRef, a_Command, a_HelpString, cLuaState::Return, ret); + return ret; } - lua_State * LuaState; - int FuncRef; - } Callback(tolua_S, FuncRef); + cLuaState & m_LuaState; + cLuaState::cRef & m_FnRef; + } Callback(L, FnRef); - bool bRetVal = self->ForEachConsoleCommand(Callback); - - /* Unreference the values again, so the LUA_REGISTRYINDEX can make place for other references */ - luaL_unref(tolua_S, LUA_REGISTRYINDEX, FuncRef); - - /* Push return value on stack */ - tolua_pushboolean(tolua_S, bRetVal); + // Execute and push the returned value: + L.Push(cPluginManager::Get()->ForEachConsoleCommand(Callback)); return 1; } diff --git a/src/Blocks/BlockBed.cpp b/src/Blocks/BlockBed.cpp index e56f4bfe0..dfa392d9b 100644 --- a/src/Blocks/BlockBed.cpp +++ b/src/Blocks/BlockBed.cpp @@ -14,7 +14,7 @@ void cBlockBedHandler::OnDestroyed(cChunkInterface & a_ChunkInterface, cWorldInt NIBBLETYPE OldMeta = a_ChunkInterface.GetBlockMeta(a_BlockX, a_BlockY, a_BlockZ); Vector3i ThisPos( a_BlockX, a_BlockY, a_BlockZ); - Vector3i Direction = MetaDataToDirection( OldMeta & 0x7); + Vector3i Direction = MetaDataToDirection( OldMeta & 0x3); if (OldMeta & 0x8) { // Was pillow @@ -111,7 +111,7 @@ void cBlockBedHandler::OnUse(cChunkInterface & a_ChunkInterface, cWorldInterface // Is foot end VERIFY((Meta & 0x4) != 0x4); // Occupied flag should never be set, else our compilator (intended) is broken - PillowDirection = MetaDataToDirection(Meta & 0x7); + PillowDirection = MetaDataToDirection(Meta & 0x3); if (a_ChunkInterface.GetBlock(a_BlockX + PillowDirection.x, a_BlockY, a_BlockZ + PillowDirection.z) == E_BLOCK_BED) // Must always use pillow location for sleeping { a_WorldInterface.GetBroadcastManager().BroadcastUseBed(*a_Player, a_BlockX + PillowDirection.x, a_BlockY, a_BlockZ + PillowDirection.z); diff --git a/src/Blocks/BlockLeaves.h b/src/Blocks/BlockLeaves.h index bd9a7414e..8e44c94ac 100644 --- a/src/Blocks/BlockLeaves.h +++ b/src/Blocks/BlockLeaves.h @@ -41,7 +41,7 @@ public: cFastRandom rand; // Old leaves - 3 bits contain display; new leaves - 1st bit, shifted left two for saplings to understand - if (rand.NextInt(6) == 0) + if (rand.NextInt(20) == 0) { a_Pickups.push_back( cItem( diff --git a/src/FastRandom.cpp b/src/FastRandom.cpp index 737b13535..c1716f026 100644 --- a/src/FastRandom.cpp +++ b/src/FastRandom.cpp @@ -6,12 +6,28 @@ #include "Globals.h" #include "FastRandom.h" +#include + #ifdef _WIN32 - #define thread_local __declspec(thread) + #define thread_local static __declspec(thread) +#elif defined __APPLE__ + #define thread_local static __thread #endif -thread_local unsigned int m_Counter = 0; - +static unsigned int GetRandomSeed() +{ + thread_local bool SeedCounterInitialized = 0; + thread_local unsigned int SeedCounter = 0; + + if (!SeedCounterInitialized) + { + std::random_device rd; + std::uniform_int_distribution dist; + SeedCounter = dist(rd); + SeedCounterInitialized = true; + } + return ++SeedCounter; +} @@ -92,7 +108,7 @@ public: cFastRandom::cFastRandom(void) : - m_LinearRand(m_Counter++) + m_LinearRand(GetRandomSeed()) { } @@ -136,7 +152,7 @@ int cFastRandom::GenerateRandomInteger(int a_Begin, int a_End) // MTRand: MTRand::MTRand() : - m_MersenneRand(m_Counter++) + m_MersenneRand(GetRandomSeed()) { } diff --git a/src/Mobs/AggressiveMonster.cpp b/src/Mobs/AggressiveMonster.cpp index d0fb79f6d..055ff47d2 100644 --- a/src/Mobs/AggressiveMonster.cpp +++ b/src/Mobs/AggressiveMonster.cpp @@ -36,7 +36,6 @@ void cAggressiveMonster::InStateChasing(std::chrono::milliseconds a_Dt) return; } } - MoveToPosition(m_Target->GetPosition()); } } diff --git a/src/Mobs/Monster.cpp b/src/Mobs/Monster.cpp index f3f8c6b24..a29d67d15 100644 --- a/src/Mobs/Monster.cpp +++ b/src/Mobs/Monster.cpp @@ -89,7 +89,7 @@ cMonster::cMonster(const AString & a_ConfigName, eMonsterType a_MobType, const A , m_SoundDeath(a_SoundDeath) , m_AttackRate(3) , m_AttackDamage(1) - , m_AttackRange(2) + , m_AttackRange(1) , m_AttackInterval(0) , m_SightDistance(25) , m_DropChanceWeapon(0.085f) @@ -147,10 +147,15 @@ bool cMonster::TickPathFinding(cChunk & a_Chunk) (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()))) + ((m_TicksSinceLastPathReset > 10) && (m_TicksSinceLastPathReset > (0.4 * (m_FinalDestination - GetPosition()).SqrLength()))) ) { - ResetPathFinding(); + /* Re-calculating is expensive when there's no path to target, and it results in mobs freezing very often as a result of always recalculating. + This is a workaround till we get better path recalculation. */ + if (!m_NoPathToTarget) + { + ResetPathFinding(); + } } } @@ -161,12 +166,21 @@ bool cMonster::TickPathFinding(cChunk & a_Chunk) StopMovingToPosition(); // Invalid chunks, probably world is loading or something, cancel movement. return false; } + m_NoPathToTarget = false; + m_NoMoreWayPoints = false; m_PathFinderDestination = m_FinalDestination; m_Path = new cPath(a_Chunk, GetPosition().Floor(), m_PathFinderDestination.Floor(), 20); } switch (m_Path->Step(a_Chunk)) { + case ePathFinderStatus::NEARBY_FOUND: + { + m_NoPathToTarget = true; + m_Path->AcceptNearbyPath(); + break; + } + case ePathFinderStatus::PATH_NOT_FOUND: { StopMovingToPosition(); // Give up pathfinding to that destination. @@ -179,15 +193,22 @@ bool cMonster::TickPathFinding(cChunk & a_Chunk) } case ePathFinderStatus::PATH_FOUND: { - if (--m_GiveUpCounter == 0) + if (m_NoMoreWayPoints || (--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? + else if (!m_Path->IsLastPoint()) // 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. + if ((m_Path->IsFirstPoint() || ReachedNextWaypoint())) + { + 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. + } + } + else + { + m_NoMoreWayPoints = true; } return true; } @@ -269,21 +290,59 @@ bool cMonster::EnsureProperDestination(cChunk & a_Chunk) { return false; } - + int RelX = FloorC(m_FinalDestination.x) - Chunk->GetPosX() * cChunkDef::Width; int RelZ = FloorC(m_FinalDestination.z) - Chunk->GetPosZ() * cChunkDef::Width; - // If destination in the air, go down to the lowest air block. - while (m_FinalDestination.y > 0) + // If destination in the air, first try to go 1 block north, or east, or west. + // This fixes the player leaning issue. + // If that failed, we instead go down to the lowest air block. + Chunk->GetBlockTypeMeta(RelX, FloorC(m_FinalDestination.y) - 1, RelZ, BlockType, BlockMeta); + if (!cBlockInfo::IsSolid(BlockType)) { - Chunk->GetBlockTypeMeta(RelX, FloorC(m_FinalDestination.y) - 1, RelZ, BlockType, BlockMeta); - if (cBlockInfo::IsSolid(BlockType)) + bool InTheAir = true; + int x, z; + for (z = -1; z <= 1; ++z) { - break; + for (x = -1; x <= 1; ++x) + { + if ((x==0) && (z==0)) + { + continue; + } + Chunk = a_Chunk.GetNeighborChunk(FloorC(m_FinalDestination.x+x), FloorC(m_FinalDestination.z+z)); + if ((Chunk == nullptr) || !Chunk->IsValid()) + { + return false; + } + RelX = FloorC(m_FinalDestination.x+x) - Chunk->GetPosX() * cChunkDef::Width; + RelZ = FloorC(m_FinalDestination.z+z) - Chunk->GetPosZ() * cChunkDef::Width; + Chunk->GetBlockTypeMeta(RelX, FloorC(m_FinalDestination.y) - 1, RelZ, BlockType, BlockMeta); + if (cBlockInfo::IsSolid(BlockType)) + { + m_FinalDestination.x += x; + m_FinalDestination.z += z; + InTheAir = false; + goto breakBothLoops; + } + } } - m_FinalDestination.y -= 1; - } + breakBothLoops: + // Go down to the lowest air block. + if (InTheAir) + { + while (m_FinalDestination.y > 0) + { + Chunk->GetBlockTypeMeta(RelX, FloorC(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. @@ -447,21 +506,29 @@ void cMonster::SetPitchAndYawFromDestination() } } + + + Vector3d BodyDistance = m_NextWayPointPosition - GetPosition(); + double BodyRotation, BodyPitch; + BodyDistance.Normalize(); + VectorToEuler(BodyDistance.x, BodyDistance.y, BodyDistance.z, BodyRotation, BodyPitch); + SetYaw(BodyRotation); + Vector3d Distance = FinalDestination - GetPosition(); { - double Rotation, Pitch; + double HeadRotation, HeadPitch; Distance.Normalize(); - VectorToEuler(Distance.x, Distance.y, Distance.z, Rotation, Pitch); - SetHeadYaw(Rotation); - SetPitch(-Pitch); - } - - { - Vector3d BodyDistance = m_NextWayPointPosition - GetPosition(); - double Rotation, Pitch; - BodyDistance.Normalize(); - VectorToEuler(BodyDistance.x, BodyDistance.y, BodyDistance.z, Rotation, Pitch); - SetYaw(Rotation); + VectorToEuler(Distance.x, Distance.y, Distance.z, HeadRotation, HeadPitch); + if (abs(BodyRotation - HeadRotation) < 120) + { + SetHeadYaw(HeadRotation); + SetPitch(-HeadPitch); + } + else // We're not an owl. If it's more than 120, don't look behind and instead look at where you're walking. + { + SetHeadYaw(BodyRotation); + SetPitch(-BodyPitch); + } } } diff --git a/src/Mobs/Monster.h b/src/Mobs/Monster.h index 5d20ba810..c4043b0e5 100644 --- a/src/Mobs/Monster.h +++ b/src/Mobs/Monster.h @@ -180,6 +180,13 @@ protected: /** Coordinates for the ultimate, final destination last given to the pathfinder. */ Vector3d m_PathFinderDestination; + /** True if there's no path to target and we're walking to an approximated location. */ + bool m_NoPathToTarget; + + /** Whether The mob has finished their path, note that this does not imply reaching the destination, + the destination may sometimes differ from the current path. */ + bool m_NoMoreWayPoints; + /** 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 If current Y is solid, goes up to find first nonsolid block, and returns that. diff --git a/src/Mobs/Path.cpp b/src/Mobs/Path.cpp index ba7d615ae..ba8046a2b 100644 --- a/src/Mobs/Path.cpp +++ b/src/Mobs/Path.cpp @@ -8,7 +8,7 @@ #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 5 // Higher means more CPU load but faster path calculations. +#define CALCULATIONS_PER_STEP 10 // 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}; @@ -44,7 +44,8 @@ cPath::cPath( 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) + m_Chunk(&a_Chunk), + m_BadChunkFound(false) { // TODO: if src not walkable OR dest not walkable, then abort. // Borrow a new "isWalkable" from ProcessIfWalkable, make ProcessIfWalkable also call isWalkable @@ -55,6 +56,7 @@ cPath::cPath( return; } + m_NearestPointToTarget = GetCell(m_Source); m_Status = ePathFinderStatus::CALCULATING; m_StepsLeft = a_MaxSteps; @@ -81,15 +83,20 @@ cPath::~cPath() ePathFinderStatus cPath::Step(cChunk & a_Chunk) { m_Chunk = &a_Chunk; - if (m_Status != ePathFinderStatus::CALCULATING) { return m_Status; } - if (m_StepsLeft == 0) + if (m_BadChunkFound) { FinishCalculation(ePathFinderStatus::PATH_NOT_FOUND); + return m_Status; + } + + if (m_StepsLeft == 0) + { + AttemptToFindAlternative(); } else { @@ -102,9 +109,9 @@ ePathFinderStatus cPath::Step(cChunk & a_Chunk) break; // if we're here, m_Status must have changed either to PATH_FOUND or PATH_NOT_FOUND. } } - } - m_Chunk = nullptr; + m_Chunk = nullptr; + } return m_Status; } @@ -112,6 +119,17 @@ ePathFinderStatus cPath::Step(cChunk & a_Chunk) +Vector3i cPath::AcceptNearbyPath() +{ + ASSERT(m_Status == ePathFinderStatus::NEARBY_FOUND); + m_Status = ePathFinderStatus::PATH_FOUND; + return m_Destination; +} + + + + + bool cPath::IsSolid(const Vector3i & a_Location) { ASSERT(m_Chunk != nullptr); @@ -119,6 +137,7 @@ bool cPath::IsSolid(const Vector3i & a_Location) auto Chunk = m_Chunk->GetNeighborChunk(a_Location.x, a_Location.z); if ((Chunk == nullptr) || !Chunk->IsValid()) { + m_BadChunkFound = true; return true; } m_Chunk = Chunk; @@ -149,34 +168,29 @@ bool cPath::Step_Internal() { cPathCell * CurrentCell = OpenListPop(); - // Path not reachable, open list exauhsted. + // Path not reachable. if (CurrentCell == nullptr) { - FinishCalculation(ePathFinderStatus::PATH_NOT_FOUND); - ASSERT(m_Status == ePathFinderStatus::PATH_NOT_FOUND); + AttemptToFindAlternative(); 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)) - ) + if (CurrentCell->m_Location == m_Destination) { - do - { - m_PathPoints.push_back(CurrentCell->m_Location); // Populate the cPath with points. - CurrentCell = CurrentCell->m_Parent; - } while (CurrentCell != nullptr); - + BuildPath(); FinishCalculation(ePathFinderStatus::PATH_FOUND); return true; } - // Calculation not finished yet, process a currentCell by inspecting all neighbors. + // Calculation not finished yet. + // Check if we have a new NearestPoint. + if (CurrentCell->m_H < m_NearestPointToTarget->m_H) + { + m_NearestPointToTarget = CurrentCell; + } + + // process a currentCell by inspecting all neighbors. // Check North, South, East, West on all 3 different heights. int i; @@ -213,13 +227,40 @@ bool cPath::Step_Internal() +void cPath::AttemptToFindAlternative() +{ + if (m_NearestPointToTarget == GetCell(m_Source)) + { + FinishCalculation(ePathFinderStatus::PATH_NOT_FOUND); + } + else + { + m_Destination = m_NearestPointToTarget->m_Location; + BuildPath(); + FinishCalculation(ePathFinderStatus::NEARBY_FOUND); + } +} + + + + + +void cPath::BuildPath() +{ + cPathCell * CurrentCell = GetCell(m_Destination); + do + { + m_PathPoints.push_back(CurrentCell->m_Location); // Populate the cPath with points. + CurrentCell = CurrentCell->m_Parent; + } while (CurrentCell != nullptr); +} + + + + + void cPath::FinishCalculation() { - for (auto && pair : m_Map) - { - delete pair.second; - } - m_Map.clear(); m_OpenList = std::priority_queue, compareHeuristics>{}; } @@ -230,6 +271,10 @@ void cPath::FinishCalculation() void cPath::FinishCalculation(ePathFinderStatus a_NewStatus) { + if (m_BadChunkFound) + { + a_NewStatus = ePathFinderStatus::PATH_NOT_FOUND; + } m_Status = a_NewStatus; FinishCalculation(); } @@ -255,7 +300,7 @@ cPathCell * cPath::OpenListPop() // Popping from the open list also means addin { 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. + return nullptr; // We've exhausted the search space and nothing was found, this will trigger a PATH_NOT_FOUND or NEARBY_FOUND status. } cPathCell * Ret = m_OpenList.top(); @@ -348,7 +393,7 @@ cPathCell * cPath::GetCell(const Vector3i & a_Location) { Cell = new cPathCell(); Cell->m_Location = a_Location; - m_Map[a_Location] = Cell; + m_Map[a_Location] = UniquePtr(Cell); Cell->m_IsSolid = IsSolid(a_Location); Cell->m_Status = eCellStatus::NOLIST; #ifdef COMPILING_PATHFIND_DEBUGGER @@ -360,6 +405,6 @@ cPathCell * cPath::GetCell(const Vector3i & a_Location) } else { - return m_Map[a_Location]; + return m_Map[a_Location].get(); } } diff --git a/src/Mobs/Path.h b/src/Mobs/Path.h index adae77984..7a4182f17 100644 --- a/src/Mobs/Path.h +++ b/src/Mobs/Path.h @@ -23,7 +23,7 @@ Put this in your .cpp: class cChunk; /* Various little structs and classes */ -enum class ePathFinderStatus {CALCULATING, PATH_FOUND, PATH_NOT_FOUND}; +enum class ePathFinderStatus {CALCULATING, PATH_FOUND, PATH_NOT_FOUND, NEARBY_FOUND}; struct cPathCell; // Defined inside Path.cpp class compareHeuristics { @@ -62,9 +62,17 @@ public: /** Destroys the path and frees its memory. */ ~cPath(); - /** Performs part of the path calculation and returns true if the path computation has finished. */ + /** Performs part of the path calculation and returns the appropriate status. + If NEARBY_FOUND is returned, it means that the destination is not reachable, but a nearby destination + is reachable. If the user likes the alternative destination, they can call AcceptNearbyPath to treat the path as found, + and to make consequent calls to step return PATH_FOUND*/ ePathFinderStatus Step(cChunk & a_Chunk); + /** Called after the PathFinder's step returns NEARBY_FOUND. + Changes the PathFinder status from NEARBY_FOUND to PATH_FOUND, returns the nearby destination that + the PathFinder found a path to. */ + Vector3i AcceptNearbyPath(); + /* Point retrieval functions, inlined for performance. */ /** Returns the next point in the path. */ inline Vector3i GetNextPoint() @@ -93,7 +101,10 @@ public: /** Returns the total number of points this path has. */ inline int GetPointCount() { - ASSERT(m_Status == ePathFinderStatus::PATH_FOUND); + if (m_Status != ePathFinderStatus::PATH_FOUND) + { + return 0; + } return m_PathPoints.size(); } @@ -119,6 +130,8 @@ private: 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. + void AttemptToFindAlternative(); + void BuildPath(); /* Openlist and closedlist management */ void OpenListAdd(cPathCell * a_Cell); @@ -131,10 +144,11 @@ private: /* Pathfinding fields */ std::priority_queue, compareHeuristics> m_OpenList; - std::unordered_map m_Map; + std::unordered_map, VectorHasher> m_Map; Vector3i m_Destination; Vector3i m_Source; int m_StepsLeft; + cPathCell * m_NearestPointToTarget; /* Control fields */ ePathFinderStatus m_Status; @@ -145,6 +159,7 @@ private: /* Interfacing with the world */ cChunk * m_Chunk; // Only valid inside Step()! + bool m_BadChunkFound; #ifdef COMPILING_PATHFIND_DEBUGGER #include "../path_irrlicht.cpp" #endif diff --git a/src/Protocol/Protocol18x.cpp b/src/Protocol/Protocol18x.cpp index 17faca27e..4612af4a5 100644 --- a/src/Protocol/Protocol18x.cpp +++ b/src/Protocol/Protocol18x.cpp @@ -51,7 +51,7 @@ Implements the 1.8.x protocol classes: /** The slot number that the client uses to indicate "outside the window". */ -static const Int16 SLOT_NUM_OUTSIDE = -1; +static const Int16 SLOT_NUM_OUTSIDE = -999; @@ -2265,7 +2265,7 @@ void cProtocol180::HandlePacketCreativeInventoryAction(cByteBuffer & a_ByteBuffe { return; } - m_Client->HandleCreativeInventory(SlotNum, Item, (SlotNum == SLOT_NUM_OUTSIDE) ? caLeftClickOutside : caLeftClick); + m_Client->HandleCreativeInventory(SlotNum, Item, (SlotNum == -1) ? caLeftClickOutside : caLeftClick); }