diff options
Diffstat (limited to 'src')
109 files changed, 5667 insertions, 1727 deletions
diff --git a/src/Authenticator.cpp b/src/Authenticator.cpp index 4cbe3beed..bd6db1c11 100644 --- a/src/Authenticator.cpp +++ b/src/Authenticator.cpp @@ -43,7 +43,6 @@ cAuthenticator::~cAuthenticator() -/// Read custom values from INI void cAuthenticator::ReadINI(cIniFile & IniFile) { m_Server = IniFile.GetValueSet("Authentication", "Server", DEFAULT_AUTH_SERVER); @@ -55,7 +54,6 @@ void cAuthenticator::ReadINI(cIniFile & IniFile) -/// Queues a request for authenticating a user. If the auth fails, the user is kicked void cAuthenticator::Authenticate(int a_ClientID, const AString & a_UserName, const AString & a_ServerHash) { if (!m_ShouldAuthenticate) @@ -141,7 +139,9 @@ bool cAuthenticator::AuthFromAddress(const AString & a_Server, const AString & a cBlockingTCPLink Link; if (!Link.Connect(a_Server.c_str(), 80)) { - LOGERROR("cAuthenticator: cannot connect to auth server \"%s\", kicking user \"%s\"", a_Server.c_str(), a_Server.c_str()); + LOGWARNING("%s: cannot connect to auth server \"%s\", kicking user \"%s\"", + __FUNCTION__, a_Server.c_str(), a_UserName.c_str() + ); return false; } @@ -170,7 +170,7 @@ bool cAuthenticator::AuthFromAddress(const AString & a_Server, const AString & a if (code == 302) { // redirect blabla - LOGINFO("Need to redirect!"); + LOGD("%s: Need to redirect, current level %d!", __FUNCTION__, a_Level); if (a_Level > MAX_REDIRECTS) { LOGERROR("cAuthenticator: received too many levels of redirection from auth server \"%s\" for user \"%s\", bailing out and kicking the user", a_Server.c_str(), a_UserName.c_str()); diff --git a/src/Bindings/AllToLua.bat b/src/Bindings/AllToLua.bat index b2a192880..f085af9e9 100644 --- a/src/Bindings/AllToLua.bat +++ b/src/Bindings/AllToLua.bat @@ -4,17 +4,21 @@ :: When called without any parameters, it will pause for a keypress at the end :: Call with any parameter to disable the wait (for buildserver use) +@echo off + :: Regenerate the files: +echo Regenerating LUA bindings . . . "tolua++.exe" -L virtual_method_hooks.lua -o Bindings.cpp -H Bindings.h AllToLua.pkg : Wait for keypress, if no param given: +echo. if %ALLTOLUA_WAIT%N == N pause diff --git a/src/Bindings/CMakeLists.txt b/src/Bindings/CMakeLists.txt deleted file mode 100644 index 50b81e42a..000000000 --- a/src/Bindings/CMakeLists.txt +++ /dev/null @@ -1,24 +0,0 @@ - -cmake_minimum_required (VERSION 2.6) -project (MCServer) - -# NOTE: This CMake file is processed only for Unix builds; Windows(MSVC) builds handle all the subfolders in /src in a single file, /src/CMakeLists.txt - -include_directories ("${PROJECT_SOURCE_DIR}/../") - -ADD_CUSTOM_COMMAND( - # add any new generated bindings here - OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/Bindings.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Bindings.h - - # command execuded to regerate bindings - COMMAND tolua -L virtual_method_hooks.lua -o Bindings.cpp -H Bindings.h AllToLua.pkg - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} - - # add any new generation dependencies here - DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/virtual_method_hooks.lua ${CMAKE_CURRENT_SOURCE_DIR}/AllToLua.pkg tolua -) - -#add cpp files here -add_library(Bindings PluginManager LuaState WebPlugin Bindings ManualBindings LuaWindow Plugin PluginLua WebPlugin) - -target_link_libraries(Bindings lua sqlite tolualib) diff --git a/src/Bindings/LuaState.cpp b/src/Bindings/LuaState.cpp index bfee1d037..d49cd8ef3 100644 --- a/src/Bindings/LuaState.cpp +++ b/src/Bindings/LuaState.cpp @@ -235,7 +235,7 @@ bool cLuaState::PushFunction(const char * a_FunctionName) if (!lua_isfunction(m_LuaState, -1)) { LOGWARNING("Error in %s: Could not find function %s()", m_SubsystemName.c_str(), a_FunctionName); - lua_pop(m_LuaState, 1); + lua_pop(m_LuaState, 2); return false; } m_CurrentFunctionName.assign(a_FunctionName); @@ -258,7 +258,7 @@ bool cLuaState::PushFunction(int a_FnRef) lua_rawgeti(m_LuaState, LUA_REGISTRYINDEX, a_FnRef); // same as lua_getref() if (!lua_isfunction(m_LuaState, -1)) { - lua_pop(m_LuaState, 1); + lua_pop(m_LuaState, 2); return false; } m_CurrentFunctionName = "<callback>"; @@ -282,16 +282,20 @@ bool cLuaState::PushFunction(const cTableRef & a_TableRef) if (!lua_istable(m_LuaState, -1)) { // Not a table, bail out - lua_pop(m_LuaState, 1); + lua_pop(m_LuaState, 2); return false; } lua_getfield(m_LuaState, -1, a_TableRef.GetFnName()); if (lua_isnil(m_LuaState, -1) || !lua_isfunction(m_LuaState, -1)) { // Not a valid function, bail out - lua_pop(m_LuaState, 2); + lua_pop(m_LuaState, 3); return false; } + + // Pop the table off the stack: + lua_remove(m_LuaState, -2); + Printf(m_CurrentFunctionName, "<table-callback %s>", a_TableRef.GetFnName()); m_NumCurrentFunctionArgs = 0; return true; @@ -742,6 +746,10 @@ bool cLuaState::CallFunction(int a_NumResults) } m_NumCurrentFunctionArgs = -1; m_CurrentFunctionName.clear(); + + // Remove the error handler from the stack: + lua_remove(m_LuaState, -a_NumResults - 1); + return true; } @@ -1025,21 +1033,184 @@ void cLuaState::LogStackTrace(lua_State * a_LuaState) AString cLuaState::GetTypeText(int a_StackPos) { - int Type = lua_type(m_LuaState, a_StackPos); - switch (Type) + return lua_typename(m_LuaState, lua_type(m_LuaState, a_StackPos)); +} + + + + + +int cLuaState::CallFunctionWithForeignParams( + const AString & a_FunctionName, + cLuaState & a_SrcLuaState, + int a_SrcParamStart, + int a_SrcParamEnd +) +{ + ASSERT(IsValid()); + ASSERT(a_SrcLuaState.IsValid()); + + // Store the stack position before any changes + int OldTop = lua_gettop(m_LuaState); + + // Push the function to call, including the error handler: + if (!PushFunction(a_FunctionName.c_str())) + { + LOGWARNING("Function '%s' not found", a_FunctionName.c_str()); + lua_pop(m_LuaState, 2); + return -1; + } + + // Copy the function parameters to the target state + if (CopyStackFrom(a_SrcLuaState, a_SrcParamStart, a_SrcParamEnd) < 0) + { + // Something went wrong, fix the stack and exit + lua_pop(m_LuaState, 2); + return -1; + } + + // Call the function, with an error handler: + int s = lua_pcall(m_LuaState, a_SrcParamEnd - a_SrcParamStart + 1, LUA_MULTRET, OldTop); + 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); + } + return -1; + } + + // Reset the internal checking mechanisms: + m_NumCurrentFunctionArgs = -1; + + // Remove the error handler from the stack: + lua_remove(m_LuaState, OldTop + 1); + + // Return the number of return values: + return lua_gettop(m_LuaState) - OldTop; +} + + + + + +int cLuaState::CopyStackFrom(cLuaState & a_SrcLuaState, int a_SrcStart, int a_SrcEnd) +{ + /* + // DEBUG: + LOGD("Copying stack values from %d to %d", a_SrcStart, a_SrcEnd); + a_SrcLuaState.LogStack("Src stack before copying:"); + LogStack("Dst stack before copying:"); + */ + for (int i = a_SrcStart; i <= a_SrcEnd; ++i) + { + int t = lua_type(a_SrcLuaState, i); + switch (t) + { + case LUA_TNIL: + { + lua_pushnil(m_LuaState); + break; + } + case LUA_TSTRING: + { + AString s; + a_SrcLuaState.ToString(i, s); + Push(s); + break; + } + case LUA_TBOOLEAN: + { + bool b = (tolua_toboolean(a_SrcLuaState, i, false) != 0); + Push(b); + break; + } + case LUA_TNUMBER: + { + lua_Number d = tolua_tonumber(a_SrcLuaState, i, 0); + Push(d); + break; + } + case LUA_TUSERDATA: + { + // Get the class name: + const char * type = NULL; + if (lua_getmetatable(a_SrcLuaState, i) == 0) + { + LOGWARNING("%s: Unknown class in pos %d, cannot copy.", __FUNCTION__, i); + lua_pop(m_LuaState, i - a_SrcStart); + return -1; + } + lua_rawget(a_SrcLuaState, LUA_REGISTRYINDEX); // Stack +1 + type = lua_tostring(a_SrcLuaState, -1); + lua_pop(a_SrcLuaState, 1); // Stack -1 + + // Copy the value: + void * ud = tolua_touserdata(a_SrcLuaState, i, NULL); + tolua_pushusertype(m_LuaState, ud, type); + } + default: + { + LOGWARNING("%s: Unsupported value: '%s' at stack position %d. Can only copy numbers, strings, bools and classes!", + __FUNCTION__, lua_typename(a_SrcLuaState, t), i + ); + a_SrcLuaState.LogStack("Stack where copying failed:"); + lua_pop(m_LuaState, i - a_SrcStart); + return -1; + } + } + } + return a_SrcEnd - a_SrcStart + 1; +} + + + + + +void cLuaState::ToString(int a_StackPos, AString & a_String) +{ + size_t len; + const char * s = lua_tolstring(m_LuaState, a_StackPos, &len); + if (s != NULL) { - case LUA_TNONE: return "TNONE"; - case LUA_TNIL: return "TNIL"; - case LUA_TBOOLEAN: return "TBOOLEAN"; - case LUA_TLIGHTUSERDATA: return "TLIGHTUSERDATA"; - case LUA_TNUMBER: return "TNUMBER"; - case LUA_TSTRING: return "TSTRING"; - case LUA_TTABLE: return "TTABLE"; - case LUA_TFUNCTION: return "TFUNCTION"; - case LUA_TUSERDATA: return "TUSERDATA"; - case LUA_TTHREAD: return "TTHREAD"; + a_String.assign(s, len); } - return Printf("Unknown (%d)", Type); +} + + + + + +void cLuaState::LogStack(const char * a_Header) +{ + LogStack(m_LuaState, a_Header); +} + + + + + +void cLuaState::LogStack(lua_State * a_LuaState, const char * a_Header) +{ + LOGD((a_Header != NULL) ? a_Header : "Lua C API Stack contents:"); + for (int i = lua_gettop(a_LuaState); i >= 0; i--) + { + AString Value; + int Type = lua_type(a_LuaState, i); + switch (Type) + { + case LUA_TBOOLEAN: Value.assign((lua_toboolean(a_LuaState, i) != 0) ? "true" : "false"); break; + case LUA_TLIGHTUSERDATA: Printf(Value, "%p", lua_touserdata(a_LuaState, i)); break; + case LUA_TNUMBER: Printf(Value, "%f", (double)lua_tonumber(a_LuaState, i)); break; + case LUA_TSTRING: Printf(Value, "%s", lua_tostring(a_LuaState, i)); break; + default: break; + } + LOGD(" Idx %d: type %d (%s) %s", i, Type, lua_typename(a_LuaState, Type), Value.c_str()); + } // for i - stack idx } diff --git a/src/Bindings/LuaState.h b/src/Bindings/LuaState.h index f8b67f5cd..dda45bb28 100644 --- a/src/Bindings/LuaState.h +++ b/src/Bindings/LuaState.h @@ -60,23 +60,23 @@ class cBlockEntity; -/// Encapsulates a Lua state and provides some syntactic sugar for common operations +/** Encapsulates a Lua state and provides some syntactic sugar for common operations */ class cLuaState { public: - /// Used for storing references to object in the global registry + /** Used for storing references to object in the global registry */ class cRef { public: - /// Creates a reference in the specified LuaState for object at the specified StackPos + /** Creates a reference in the specified LuaState for object at the specified StackPos */ cRef(cLuaState & a_LuaState, int a_StackPos); ~cRef(); - /// Returns true if the reference is valid + /** Returns true if the reference is valid */ bool IsValid(void) const {return (m_Ref != LUA_REFNIL); } - /// Allows to use this class wherever an int (i. e. ref) is to be used + /** Allows to use this class wherever an int (i. e. ref) is to be used */ operator int(void) const { return m_Ref; } protected: @@ -102,7 +102,7 @@ public: } ; - /// A dummy class that's used only to delimit function args from return values for cLuaState::Call() + /** A dummy class that's used only to delimit function args from return values for cLuaState::Call() */ class cRet { } ; @@ -123,22 +123,22 @@ public: ~cLuaState(); - /// Allows this object to be used in the same way as a lua_State *, for example in the LuaLib functions + /** Allows this object to be used in the same way as a lua_State *, for example in the LuaLib functions */ operator lua_State * (void) { return m_LuaState; } - /// Creates the m_LuaState, if not closed already. This state will be automatically closed in the destructor + /** Creates the m_LuaState, if not closed already. This state will be automatically closed in the destructor */ void Create(void); - /// Closes the m_LuaState, if not closed already + /** Closes the m_LuaState, if not closed already */ void Close(void); - /// Attaches the specified state. Operations will be carried out on this state, but it will not be closed in the destructor + /** Attaches the specified state. Operations will be carried out on this state, but it will not be closed in the destructor */ void Attach(lua_State * a_State); - /// Detaches a previously attached state. + /** Detaches a previously attached state. */ void Detach(void); - /// Returns true if the m_LuaState is valid + /** Returns true if the m_LuaState is valid */ bool IsValid(void) const { return (m_LuaState != NULL); } /** Loads the specified file @@ -147,7 +147,7 @@ public: */ bool LoadFile(const AString & a_FileName); - /// Returns true if a_FunctionName is a valid Lua function that can be called + /** Returns true if a_FunctionName is a valid Lua function that can be called */ bool HasFunction(const char * a_FunctionName); // Push a value onto the stack @@ -182,7 +182,7 @@ public: void Push(cHopperEntity * a_Hopper); void Push(cBlockEntity * a_BlockEntity); - /// Call any 0-param 0-return Lua function in a single line: + /** Call any 0-param 0-return Lua function in a single line: */ template <typename FnT> bool Call(FnT a_FnName) { @@ -193,7 +193,7 @@ public: return CallFunction(0); } - /// Call any 1-param 0-return Lua function in a single line: + /** Call any 1-param 0-return Lua function in a single line: */ template< typename FnT, typename ArgT1 @@ -208,7 +208,7 @@ public: return CallFunction(0); } - /// Call any 2-param 0-return Lua function in a single line: + /** Call any 2-param 0-return Lua function in a single line: */ template< typename FnT, typename ArgT1, typename ArgT2 > @@ -223,7 +223,7 @@ public: return CallFunction(0); } - /// Call any 3-param 0-return Lua function in a single line: + /** Call any 3-param 0-return Lua function in a single line: */ template< typename FnT, typename ArgT1, typename ArgT2, typename ArgT3 > @@ -239,7 +239,7 @@ public: return CallFunction(0); } - /// Call any 0-param 1-return Lua function in a single line: + /** Call any 0-param 1-return Lua function in a single line: */ template< typename FnT, typename RetT1 > @@ -259,12 +259,13 @@ public: return true; } - /// Call any 1-param 1-return Lua function in a single line: + /** Call any 1-param 1-return Lua function in a single line: */ template< typename FnT, typename ArgT1, typename RetT1 > bool Call(FnT a_FnName, ArgT1 a_Arg1, const cRet & a_Mark, RetT1 & a_Ret1) { + int InitialTop = lua_gettop(m_LuaState); UNUSED(a_Mark); if (!PushFunction(a_FnName)) { @@ -277,10 +278,11 @@ public: } GetReturn(-1, a_Ret1); lua_pop(m_LuaState, 1); + ASSERT(InitialTop == lua_gettop(m_LuaState)); return true; } - /// Call any 2-param 1-return Lua function in a single line: + /** Call any 2-param 1-return Lua function in a single line: */ template< typename FnT, typename ArgT1, typename ArgT2, typename RetT1 > @@ -302,7 +304,7 @@ public: return true; } - /// Call any 3-param 1-return Lua function in a single line: + /** Call any 3-param 1-return Lua function in a single line: */ template< typename FnT, typename ArgT1, typename ArgT2, typename ArgT3, typename RetT1 > @@ -325,7 +327,7 @@ public: return true; } - /// Call any 4-param 1-return Lua function in a single line: + /** Call any 4-param 1-return Lua function in a single line: */ template< typename FnT, typename ArgT1, typename ArgT2, typename ArgT3, typename ArgT4, typename RetT1 > @@ -349,7 +351,7 @@ public: return true; } - /// Call any 5-param 1-return Lua function in a single line: + /** Call any 5-param 1-return Lua function in a single line: */ template< typename FnT, typename ArgT1, typename ArgT2, typename ArgT3, typename ArgT4, typename ArgT5, typename RetT1 > @@ -374,7 +376,7 @@ public: return true; } - /// Call any 6-param 1-return Lua function in a single line: + /** Call any 6-param 1-return Lua function in a single line: */ template< typename FnT, typename ArgT1, typename ArgT2, typename ArgT3, typename ArgT4, typename ArgT5, typename ArgT6, typename RetT1 @@ -401,7 +403,7 @@ public: return true; } - /// Call any 7-param 1-return Lua function in a single line: + /** Call any 7-param 1-return Lua function in a single line: */ template< typename FnT, typename ArgT1, typename ArgT2, typename ArgT3, typename ArgT4, typename ArgT5, typename ArgT6, typename ArgT7, typename RetT1 @@ -429,7 +431,7 @@ public: return true; } - /// Call any 8-param 1-return Lua function in a single line: + /** Call any 8-param 1-return Lua function in a single line: */ template< typename FnT, typename ArgT1, typename ArgT2, typename ArgT3, typename ArgT4, typename ArgT5, typename ArgT6, typename ArgT7, typename ArgT8, typename RetT1 @@ -458,7 +460,7 @@ public: return true; } - /// Call any 9-param 1-return Lua function in a single line: + /** Call any 9-param 1-return Lua function in a single line: */ template< typename FnT, typename ArgT1, typename ArgT2, typename ArgT3, typename ArgT4, typename ArgT5, typename ArgT6, typename ArgT7, typename ArgT8, typename ArgT9, typename RetT1 @@ -488,7 +490,7 @@ public: return true; } - /// Call any 10-param 1-return Lua function in a single line: + /** Call any 10-param 1-return Lua function in a single line: */ template< typename FnT, typename ArgT1, typename ArgT2, typename ArgT3, typename ArgT4, typename ArgT5, typename ArgT6, typename ArgT7, typename ArgT8, typename ArgT9, typename ArgT10, typename RetT1 @@ -519,7 +521,7 @@ public: return true; } - /// Call any 1-param 2-return Lua function in a single line: + /** Call any 1-param 2-return Lua function in a single line: */ template< typename FnT, typename ArgT1, typename RetT1, typename RetT2 > @@ -541,7 +543,7 @@ public: return true; } - /// Call any 2-param 2-return Lua function in a single line: + /** Call any 2-param 2-return Lua function in a single line: */ template< typename FnT, typename ArgT1, typename ArgT2, typename RetT1, typename RetT2 > @@ -564,7 +566,7 @@ public: return true; } - /// Call any 3-param 2-return Lua function in a single line: + /** Call any 3-param 2-return Lua function in a single line: */ template< typename FnT, typename ArgT1, typename ArgT2, typename ArgT3, typename RetT1, typename RetT2 @@ -589,7 +591,7 @@ public: return true; } - /// Call any 4-param 2-return Lua function in a single line: + /** Call any 4-param 2-return Lua function in a single line: */ template< typename FnT, typename ArgT1, typename ArgT2, typename ArgT3, typename ArgT4, typename RetT1, typename RetT2 @@ -615,7 +617,7 @@ public: return true; } - /// Call any 5-param 2-return Lua function in a single line: + /** Call any 5-param 2-return Lua function in a single line: */ template< typename FnT, typename ArgT1, typename ArgT2, typename ArgT3, typename ArgT4, typename ArgT5, typename RetT1, typename RetT2 @@ -642,7 +644,7 @@ public: return true; } - /// Call any 6-param 2-return Lua function in a single line: + /** Call any 6-param 2-return Lua function in a single line: */ template< typename FnT, typename ArgT1, typename ArgT2, typename ArgT3, typename ArgT4, typename ArgT5, typename ArgT6, @@ -671,7 +673,7 @@ public: return true; } - /// Call any 7-param 2-return Lua function in a single line: + /** Call any 7-param 2-return Lua function in a single line: */ template< typename FnT, typename ArgT1, typename ArgT2, typename ArgT3, typename ArgT4, typename ArgT5, typename ArgT6, typename ArgT7, @@ -701,7 +703,7 @@ public: return true; } - /// Call any 7-param 3-return Lua function in a single line: + /** Call any 7-param 3-return Lua function in a single line: */ template< typename FnT, typename ArgT1, typename ArgT2, typename ArgT3, typename ArgT4, typename ArgT5, typename ArgT6, typename ArgT7, @@ -732,7 +734,7 @@ public: return true; } - /// Call any 8-param 3-return Lua function in a single line: + /** Call any 8-param 3-return Lua function in a single line: */ template< typename FnT, typename ArgT1, typename ArgT2, typename ArgT3, typename ArgT4, typename ArgT5, typename ArgT6, typename ArgT7, typename ArgT8, @@ -764,7 +766,7 @@ public: return true; } - /// Call any 9-param 5-return Lua function in a single line: + /** Call any 9-param 5-return Lua function in a single line: */ template< typename FnT, typename ArgT1, typename ArgT2, typename ArgT3, typename ArgT4, typename ArgT5, typename ArgT6, typename ArgT7, typename ArgT8, typename ArgT9, @@ -800,46 +802,71 @@ public: } - /// Returns true if the specified parameters on the stack are of the specified usertable type; also logs warning if not. Used for static functions + /** Returns true if the specified parameters on the stack are of the specified usertable type; also logs warning if not. Used for static functions */ bool CheckParamUserTable(int a_StartParam, const char * a_UserTable, int a_EndParam = -1); - /// Returns true if the specified parameters on the stack are of the specified usertype; also logs warning if not. Used for regular functions + /** Returns true if the specified parameters on the stack are of the specified usertype; also logs warning if not. Used for regular functions */ bool CheckParamUserType(int a_StartParam, const char * a_UserType, int a_EndParam = -1); - /// Returns true if the specified parameters on the stack are tables; also logs warning if not + /** Returns true if the specified parameters on the stack are tables; also logs warning if not */ bool CheckParamTable(int a_StartParam, int a_EndParam = -1); - /// Returns true if the specified parameters on the stack are numbers; also logs warning if not + /** Returns true if the specified parameters on the stack are numbers; also logs warning if not */ bool CheckParamNumber(int a_StartParam, int a_EndParam = -1); - /// Returns true if the specified parameters on the stack are strings; also logs warning if not + /** Returns true if the specified parameters on the stack are strings; also logs warning if not */ bool CheckParamString(int a_StartParam, int a_EndParam = -1); - /// Returns true if the specified parameters on the stack are functions; also logs warning if not + /** Returns true if the specified parameters on the stack are functions; also logs warning if not */ bool CheckParamFunction(int a_StartParam, int a_EndParam = -1); - /// Returns true if the specified parameter on the stack is nil (indicating an end-of-parameters) + /** Returns true if the specified parameter on the stack is nil (indicating an end-of-parameters) */ bool CheckParamEnd(int a_Param); - /// If the status is nonzero, prints the text on the top of Lua stack and returns true + /** If the status is nonzero, prints the text on the top of Lua stack and returns true */ bool ReportErrors(int status); - /// If the status is nonzero, prints the text on the top of Lua stack and returns true + /** If the status is nonzero, prints the text on the top of Lua stack and returns true */ static bool ReportErrors(lua_State * a_LuaState, int status); - /// Logs all items in the current stack trace to the server console + /** Logs all items in the current stack trace to the server console */ void LogStackTrace(void); - /// Logs all items in the current stack trace to the server console + /** Logs all items in the current stack trace to the server console */ static void LogStackTrace(lua_State * a_LuaState); - /// Returns the type of the item on the specified position in the stack + /** Returns the type of the item on the specified position in the stack */ AString GetTypeText(int a_StackPos); + /** Calls the function specified by its name, with arguments copied off the foreign state. + If successful, keeps the return values on the stack and returns their number. + If unsuccessful, returns a negative number and keeps the stack position unchanged. */ + int CallFunctionWithForeignParams( + const AString & a_FunctionName, + cLuaState & a_SrcLuaState, + int a_SrcParamStart, + int a_SrcParamEnd + ); + + /** Copies objects on the stack from the specified state. + Only numbers, bools, strings and userdatas are copied. + If successful, returns the number of objects copied. + If failed, returns a negative number and rewinds the stack position. */ + int CopyStackFrom(cLuaState & a_SrcLuaState, int a_SrcStart, int a_SrcEnd); + + /** Reads the value at the specified stack position as a string and sets it to a_String. */ + void ToString(int a_StackPos, AString & a_String); + + /** Logs all the elements' types on the API stack, with an optional header for the listing. */ + void LogStack(const char * a_Header); + + /** Logs all the elements' types on the API stack, with an optional header for the listing. */ + static void LogStack(lua_State * a_LuaState, const char * a_Header = NULL); + protected: lua_State * m_LuaState; - /// If true, the state is owned by this object and will be auto-Closed. False => attached state + /** If true, the state is owned by this object and will be auto-Closed. False => attached state */ bool m_IsOwned; /** The subsystem name is used for reporting errors to the console, it is either "plugin %s" or "LuaScript" @@ -847,10 +874,10 @@ protected: */ AString m_SubsystemName; - /// Name of the currently pushed function (for the Push / Call chain) + /** Name of the currently pushed function (for the Push / Call chain) */ AString m_CurrentFunctionName; - /// Number of arguments currently pushed (for the Push / Call chain) + /** Number of arguments currently pushed (for the Push / Call chain) */ int m_NumCurrentFunctionArgs; @@ -869,19 +896,19 @@ protected: */ bool PushFunction(const cTableRef & a_TableRef); - /// Pushes a usertype of the specified class type onto the stack + /** Pushes a usertype of the specified class type onto the stack */ void PushUserType(void * a_Object, const char * a_Type); - /// Retrieve value returned at a_StackPos, if it is a valid bool. If not, a_ReturnedVal is unchanged + /** Retrieve value returned at a_StackPos, if it is a valid bool. If not, a_ReturnedVal is unchanged */ void GetReturn(int a_StackPos, bool & a_ReturnedVal); - /// Retrieve value returned at a_StackPos, if it is a valid string. If not, a_ReturnedVal is unchanged + /** Retrieve value returned at a_StackPos, if it is a valid string. If not, a_ReturnedVal is unchanged */ void GetReturn(int a_StackPos, AString & a_ReturnedVal); - /// Retrieve value returned at a_StackPos, if it is a valid number. If not, a_ReturnedVal is unchanged + /** Retrieve value returned at a_StackPos, if it is a valid number. If not, a_ReturnedVal is unchanged */ void GetReturn(int a_StackPos, int & a_ReturnedVal); - /// Retrieve value returned at a_StackPos, if it is a valid number. If not, a_ReturnedVal is unchanged + /** Retrieve value returned at a_StackPos, if it is a valid number. If not, a_ReturnedVal is unchanged */ void GetReturn(int a_StackPos, double & a_ReturnedVal); /** diff --git a/src/Bindings/ManualBindings.cpp b/src/Bindings/ManualBindings.cpp index 2206dd371..dbaf32756 100644 --- a/src/Bindings/ManualBindings.cpp +++ b/src/Bindings/ManualBindings.cpp @@ -13,6 +13,7 @@ #include "../Entities/Player.h" #include "../WebAdmin.h" #include "../ClientHandle.h" +#include "../BlockArea.h" #include "../BlockEntities/ChestEntity.h" #include "../BlockEntities/CommandBlockEntity.h" #include "../BlockEntities/DispenserEntity.h" @@ -22,6 +23,7 @@ #include "../BlockEntities/NoteEntity.h" #include "md5/md5.h" #include "../LineBlockTracer.h" +#include "../WorldStorage/SchematicFileSerializer.h" @@ -1427,7 +1429,10 @@ static int tolua_cPluginManager_BindCommand(lua_State * L) // Read the arguments to this API call: tolua_Error tolua_err; int idx = 1; - if (tolua_isusertype(L, 1, "cPluginManager", 0, &tolua_err)) + if ( + tolua_isusertype (L, 1, "cPluginManager", 0, &tolua_err) || + tolua_isusertable(L, 1, "cPluginManager", 0, &tolua_err) + ) { idx++; } @@ -1540,6 +1545,85 @@ static int tolua_cPluginManager_BindConsoleCommand(lua_State * L) +static int tolua_cPluginManager_CallPlugin(lua_State * tolua_S) +{ + /* + Function signature: + cPluginManager:CallPlugin("PluginName", "FunctionName", args...) + */ + + // Check the parameters: + cLuaState L(tolua_S); + if ( + !L.CheckParamUserTable(1, "cPluginManager") || + !L.CheckParamString(2, 3)) + { + return 0; + } + + // Retrieve the plugin name and function name + AString PluginName, FunctionName; + L.ToString(2, PluginName); + L.ToString(3, FunctionName); + if (PluginName.empty() || FunctionName.empty()) + { + LOGWARNING("cPluginManager:CallPlugin(): Invalid plugin name or function name"); + L.LogStackTrace(); + return 0; + } + + // If requesting calling the current plugin, refuse: + cPluginLua * ThisPlugin = GetLuaPlugin(L); + if (ThisPlugin == NULL) + { + return 0; + } + if (ThisPlugin->GetName() == PluginName) + { + LOGWARNING("cPluginManager::CallPlugin(): Calling self is not implemented (why would it?)"); + L.LogStackTrace(); + return 0; + } + + // Call the destination plugin using a plugin callback: + class cCallback : + public cPluginManager::cPluginCallback + { + public: + int m_NumReturns; + + cCallback(const AString & a_FunctionName, cLuaState & a_SrcLuaState) : + m_FunctionName(a_FunctionName), + m_SrcLuaState(a_SrcLuaState), + m_NumReturns(0) + { + } + protected: + const AString & m_FunctionName; + cLuaState & m_SrcLuaState; + + virtual bool Item(cPlugin * a_Plugin) override + { + m_NumReturns = ((cPluginLua *)a_Plugin)->CallFunctionFromForeignState( + m_FunctionName, m_SrcLuaState, 4, lua_gettop(m_SrcLuaState) + ); + return true; + } + } Callback(FunctionName, L); + if (!cPluginManager::Get()->DoWithPlugin(PluginName, Callback)) + { + // TODO 2014_01_20 _X: This might be too much logging, plugins cannot know if other plugins are loaded (async) + LOGWARNING("cPluginManager::CallPlugin: No such plugin name (\"%s\")", PluginName.c_str()); + L.LogStackTrace(); + return 0; + } + return Callback.m_NumReturns; +} + + + + + static int tolua_cPlayer_GetGroups(lua_State* tolua_S) { cPlayer* self = (cPlayer*) tolua_tousertype(tolua_S,1,0); @@ -1734,112 +1818,28 @@ static int tolua_cPluginLua_AddTab(lua_State* tolua_S) -// Perhaps use this as well for copying tables https://github.com/keplerproject/rings/pull/1 -static int copy_lua_values(lua_State * a_Source, lua_State * a_Destination, int i, int top) -{ - for(; i <= top; ++i ) - { - int t = lua_type(a_Source, i); - switch (t) { - case LUA_TSTRING: /* strings */ - { - const char * s = lua_tostring(a_Source, i); - LOGD("%i push string: %s", i, s); - tolua_pushstring(a_Destination, s); - } - break; - case LUA_TBOOLEAN: /* booleans */ - { - int b = tolua_toboolean(a_Source, i, false); - LOGD("%i push bool: %i", i, b); - tolua_pushboolean(a_Destination, b ); - } - break; - case LUA_TNUMBER: /* numbers */ - { - lua_Number d = tolua_tonumber(a_Source, i, 0); - LOGD("%i push number: %0.2f", i, d); - tolua_pushnumber(a_Destination, d ); - } - break; - case LUA_TUSERDATA: - { - const char * type = 0; - if (lua_getmetatable(a_Source,i)) - { - lua_rawget(a_Source, LUA_REGISTRYINDEX); - type = lua_tostring(a_Source, -1); - lua_pop(a_Source, 1); // Pop.. something?! I don't knooow~~ T_T - } - - // don't need tolua_tousertype we already have the type - void * ud = tolua_touserdata(a_Source, i, 0); - LOGD("%i push usertype: %p of type '%s'", i, ud, type); - if( type == 0 ) - { - LOGERROR("Call(): Something went wrong when trying to get usertype name!"); - return 0; - } - tolua_pushusertype(a_Destination, ud, type); - } - break; - default: /* other values */ - LOGERROR("Call(): Unsupported value: '%s'. Can only use numbers and strings!", lua_typename(a_Source, t)); - return 0; - } - } - return 1; -} - - - - -static int tolua_cPlugin_Call(lua_State* tolua_S) +static int tolua_cPlugin_Call(lua_State * tolua_S) { - cPluginLua * self = (cPluginLua *) tolua_tousertype(tolua_S, 1, 0); - lua_State* targetState = self->GetLuaState(); - int targetTop = lua_gettop(targetState); - - int top = lua_gettop(tolua_S); - LOGD("total in stack: %i", top ); - - std::string funcName = tolua_tostring(tolua_S, 2, ""); - LOGD("Func name: %s", funcName.c_str() ); - - lua_getglobal(targetState, funcName.c_str()); - if(!lua_isfunction(targetState,-1)) - { - LOGWARN("Error could not find function '%s' in plugin '%s'", funcName.c_str(), self->GetName().c_str() ); - lua_pop(targetState,1); - return 0; - } - - if( copy_lua_values(tolua_S, targetState, 3, top) == 0 ) // Start at 3 because 1 and 2 are the plugin and function name respectively - { - // something went wrong, exit - return 0; - } + cLuaState L(tolua_S); - int s = lua_pcall(targetState, top - 2, LUA_MULTRET, 0); - if (cLuaState::ReportErrors(targetState, s)) - { - LOGWARN("Error while calling function '%s' in plugin '%s'", funcName.c_str(), self->GetName().c_str() ); - return 0; - } - - int nresults = lua_gettop(targetState) - targetTop; - LOGD("num results: %i", nresults); - int ttop = lua_gettop(targetState); - if( copy_lua_values(targetState, tolua_S, targetTop+1, ttop) == 0 ) // Start at targetTop+1 and I have no idea why xD + // Log the obsoletion warning: + LOGWARNING("cPlugin:Call() is obsolete and unsafe, use cPluginManager:CallPlugin() instead."); + L.LogStackTrace(); + + // Retrieve the params: plugin and the function name to call + cPluginLua * TargetPlugin = (cPluginLua *) tolua_tousertype(tolua_S, 1, 0); + AString FunctionName = tolua_tostring(tolua_S, 2, ""); + + // Call the function: + int NumReturns = TargetPlugin->CallFunctionFromForeignState(FunctionName, L, 3, lua_gettop(L)); + if (NumReturns < 0) { - // something went wrong, exit + LOGWARNING("cPlugin::Call() failed to call destination function"); + L.LogStackTrace(); return 0; } - - lua_pop(targetState, nresults); // I have no idea what I'm doing, but it works - - return nresults; + return NumReturns; } @@ -2131,26 +2131,40 @@ protected: static int tolua_cLineBlockTracer_Trace(lua_State * tolua_S) { - // cLineBlockTracer.Trace(World, Callbacks, StartX, StartY, StartZ, EndX, EndY, EndZ) + /* Supported function signatures: + cLineBlockTracer:Trace(World, Callbacks, StartX, StartY, StartZ, EndX, EndY, EndZ) // Canonical + cLineBlockTracer.Trace(World, Callbacks, StartX, StartY, StartZ, EndX, EndY, EndZ) + */ + + // If the first param is the cLineBlockTracer class, shift param index by one: + int idx = 1; + tolua_Error err; + if (tolua_isusertable(tolua_S, 1, "cLineBlockTracer", 0, &err)) + { + idx = 2; + } + + // Check params: cLuaState L(tolua_S); if ( - !L.CheckParamUserType(1, "cWorld") || - !L.CheckParamTable (2) || - !L.CheckParamNumber (3, 8) || - !L.CheckParamEnd (9) + !L.CheckParamUserType(idx, "cWorld") || + !L.CheckParamTable (idx + 1) || + !L.CheckParamNumber (idx + 2, idx + 7) || + !L.CheckParamEnd (idx + 8) ) { return 0; } - cWorld * World = (cWorld *)tolua_tousertype(L, 1, NULL); - cLuaBlockTracerCallbacks Callbacks(L, 2); - double StartX = tolua_tonumber(L, 3, 0); - double StartY = tolua_tonumber(L, 4, 0); - double StartZ = tolua_tonumber(L, 5, 0); - double EndX = tolua_tonumber(L, 6, 0); - double EndY = tolua_tonumber(L, 7, 0); - double EndZ = tolua_tonumber(L, 8, 0); + // Trace: + cWorld * World = (cWorld *)tolua_tousertype(L, idx, NULL); + cLuaBlockTracerCallbacks Callbacks(L, idx + 1); + double StartX = tolua_tonumber(L, idx + 2, 0); + double StartY = tolua_tonumber(L, idx + 3, 0); + double StartZ = tolua_tonumber(L, idx + 4, 0); + double EndX = tolua_tonumber(L, idx + 5, 0); + double EndY = tolua_tonumber(L, idx + 6, 0); + double EndZ = tolua_tonumber(L, idx + 7, 0); bool res = cLineBlockTracer::Trace(*World, Callbacks, StartX, StartY, StartZ, EndX, EndY, EndZ); tolua_pushboolean(L, res ? 1 : 0); return 1; @@ -2239,6 +2253,63 @@ static int tolua_cHopperEntity_GetOutputBlockPos(lua_State * tolua_S) + +static int tolua_cBlockArea_LoadFromSchematicFile(lua_State * tolua_S) +{ + // function cBlockArea::LoadFromSchematicFile + // Exported manually because function has been moved to SchematicFileSerilizer.cpp + cLuaState L(tolua_S); + if ( + !L.CheckParamUserType(1, "cBlockArea") || + !L.CheckParamString (2) || + !L.CheckParamEnd (3) + ) + { + return 0; + } + cBlockArea * self = (cBlockArea *)tolua_tousertype(tolua_S, 1, 0); + if (self == NULL) + { + tolua_error(tolua_S, "invalid 'self' in function 'cBlockArea::LoadFromSchematicFile'", NULL); + return 0; + } + + AString Filename = tolua_tostring(tolua_S, 2, 0); + bool res = cSchematicFileSerializer::LoadFromSchematicFile(*self,Filename); + tolua_pushboolean(tolua_S, res); + return 1; +} + + + + +static int tolua_cBlockArea_SaveToSchematicFile(lua_State * tolua_S) +{ + // function cBlockArea::SaveToSchematicFile + // Exported manually because function has been moved to SchematicFileSerilizer.cpp + cLuaState L(tolua_S); + if ( + !L.CheckParamUserType(1, "cBlockArea") || + !L.CheckParamString (2) || + !L.CheckParamEnd (3) + ) + { + return 0; + } + cBlockArea * self = (cBlockArea *)tolua_tousertype(tolua_S, 1, 0); + if (self == NULL) + { + tolua_error(tolua_S, "invalid 'self' in function 'cBlockArea::SaveToSchematicFile'", NULL); + return 0; + } + AString Filename = tolua_tostring(tolua_S, 2, 0); + bool res = cSchematicFileSerializer::SaveToSchematicFile(*self,Filename); + tolua_pushboolean(tolua_S, res); + return 1; +} + + + void ManualBindings::Bind(lua_State * tolua_S) { tolua_beginmodule(tolua_S, NULL); @@ -2254,6 +2325,11 @@ void ManualBindings::Bind(lua_State * tolua_S) tolua_function(tolua_S, "GetFolderContents", tolua_cFile_GetFolderContents); tolua_endmodule(tolua_S); + tolua_beginmodule(tolua_S, "cBlockArea"); + tolua_function(tolua_S, "LoadFromSchematicFile", tolua_cBlockArea_LoadFromSchematicFile); + tolua_function(tolua_S, "SaveToSchematicFile", tolua_cBlockArea_SaveToSchematicFile); + tolua_endmodule(tolua_S); + tolua_beginmodule(tolua_S, "cHopperEntity"); tolua_function(tolua_S, "GetOutputBlockPos", tolua_cHopperEntity_GetOutputBlockPos); tolua_endmodule(tolua_S); @@ -2305,6 +2381,7 @@ void ManualBindings::Bind(lua_State * tolua_S) tolua_function(tolua_S, "AddHook", tolua_cPluginManager_AddHook); tolua_function(tolua_S, "BindCommand", tolua_cPluginManager_BindCommand); tolua_function(tolua_S, "BindConsoleCommand", tolua_cPluginManager_BindConsoleCommand); + tolua_function(tolua_S, "CallPlugin", tolua_cPluginManager_CallPlugin); tolua_function(tolua_S, "ForEachCommand", tolua_cPluginManager_ForEachCommand); tolua_function(tolua_S, "ForEachConsoleCommand", tolua_cPluginManager_ForEachConsoleCommand); tolua_function(tolua_S, "GetAllPlugins", tolua_cPluginManager_GetAllPlugins); diff --git a/src/Bindings/PluginLua.cpp b/src/Bindings/PluginLua.cpp index 7fc1db391..773d522c4 100644 --- a/src/Bindings/PluginLua.cpp +++ b/src/Bindings/PluginLua.cpp @@ -88,36 +88,55 @@ bool cPluginLua::Initialize(void) std::string PluginPath = FILE_IO_PREFIX + GetLocalFolder() + "/"; - // Load all files for this plugin, and execute them + // List all Lua files for this plugin. Info.lua has a special handling - make it the last to load: AStringVector Files = cFile::GetFolderContents(PluginPath.c_str()); - - int numFiles = 0; - for (AStringVector::const_iterator itr = Files.begin(); itr != Files.end(); ++itr) + AStringVector LuaFiles; + bool HasInfoLua = false; + for (AStringVector::const_iterator itr = Files.begin(), end = Files.end(); itr != end; ++itr) { - if (itr->rfind(".lua") == AString::npos) + if (itr->rfind(".lua") != AString::npos) { - continue; + if (*itr == "Info.lua") + { + HasInfoLua = true; + } + else + { + LuaFiles.push_back(*itr); + } } + } + std::sort(LuaFiles.begin(), LuaFiles.end()); + + // Warn if there are no Lua files in the plugin folder: + if (LuaFiles.empty()) + { + LOGWARNING("No lua files found: plugin %s is missing.", GetName().c_str()); + Close(); + return false; + } + + // Load all files in the list, including the Info.lua as last, if it exists: + for (AStringVector::const_iterator itr = LuaFiles.begin(), end = LuaFiles.end(); itr != end; ++itr) + { AString Path = PluginPath + *itr; if (!m_LuaState.LoadFile(Path)) { Close(); return false; - } - else - { - numFiles++; } } // for itr - Files[] - - if (numFiles == 0) + if (HasInfoLua) { - LOGWARNING("No lua files found: plugin %s is missing.", GetName().c_str()); - Close(); - return false; + AString Path = PluginPath + "Info.lua"; + if (!m_LuaState.LoadFile(Path)) + { + Close(); + return false; + } } - // Call intialize function + // Call the Initialize function: bool res = false; if (!m_LuaState.Call("Initialize", this, cLuaState::Return, res)) { @@ -125,7 +144,6 @@ bool cPluginLua::Initialize(void) Close(); return false; } - if (!res) { LOGINFO("Plugin %s: Initialize() call failed, plugin is temporarily disabled.", GetName().c_str()); @@ -1489,6 +1507,40 @@ bool cPluginLua::AddHookRef(int a_HookType, int a_FnRefIdx) +int cPluginLua::CallFunctionFromForeignState( + const AString & a_FunctionName, + cLuaState & a_ForeignState, + int a_ParamStart, + int a_ParamEnd +) +{ + cCSLock Lock(m_CriticalSection); + + // Call the function: + int NumReturns = m_LuaState.CallFunctionWithForeignParams(a_FunctionName, a_ForeignState, a_ParamStart, a_ParamEnd); + if (NumReturns < 0) + { + // The call has failed, an error has already been output to the log, so just silently bail out with the same error + return NumReturns; + } + + // Copy all the return values: + int Top = lua_gettop(m_LuaState); + int res = a_ForeignState.CopyStackFrom(m_LuaState, Top - NumReturns + 1, Top); + + // Remove the return values off this stack: + if (NumReturns > 0) + { + lua_pop(m_LuaState, NumReturns); + } + + return res; +} + + + + + AString cPluginLua::HandleWebRequest(const HTTPRequest * a_Request ) { cCSLock Lock(m_CriticalSection); diff --git a/src/Bindings/PluginLua.h b/src/Bindings/PluginLua.h index 589de66fa..ad0cfbe5a 100644 --- a/src/Bindings/PluginLua.h +++ b/src/Bindings/PluginLua.h @@ -106,7 +106,7 @@ public: virtual void ClearConsoleCommands(void) override; - /// Returns true if the plugin contains the function for the specified hook type, using the old-style registration (#121) + /** Returns true if the plugin contains the function for the specified hook type, using the old-style registration (#121) */ bool CanAddOldStyleHook(int a_HookType); // cWebPlugin override @@ -116,26 +116,26 @@ public: virtual AString HandleWebRequest(const HTTPRequest * a_Request ) override; bool AddWebTab(const AString & a_Title, lua_State * a_LuaState, int a_FunctionReference); // >> EXPORTED IN MANUALBINDINGS << - /// Binds the command to call the function specified by a Lua function reference. Simply adds to CommandMap. + /** Binds the command to call the function specified by a Lua function reference. Simply adds to CommandMap. */ void BindCommand(const AString & a_Command, int a_FnRef); - /// Binds the console command to call the function specified by a Lua function reference. Simply adds to CommandMap. + /** Binds the console command to call the function specified by a Lua function reference. Simply adds to CommandMap. */ void BindConsoleCommand(const AString & a_Command, int a_FnRef); cLuaState & GetLuaState(void) { return m_LuaState; } cCriticalSection & GetCriticalSection(void) { return m_CriticalSection; } - /// Removes a previously referenced object (luaL_unref()) + /** Removes a previously referenced object (luaL_unref()) */ void Unreference(int a_LuaRef); - /// Calls the plugin-specified "cLuaWindow closing" callback. Returns true only if the callback returned true + /** Calls the plugin-specified "cLuaWindow closing" callback. Returns true only if the callback returned true */ bool CallbackWindowClosing(int a_FnRef, cWindow & a_Window, cPlayer & a_Player, bool a_CanRefuse); - /// Calls the plugin-specified "cLuaWindow slot changed" callback. + /** Calls the plugin-specified "cLuaWindow slot changed" callback. */ void CallbackWindowSlotChanged(int a_FnRef, cWindow & a_Window, int a_SlotNum); - /// Returns the name of Lua function that should handle the specified hook type in the older (#121) API + /** Returns the name of Lua function that should handle the specified hook type in the older (#121) API */ static const char * GetHookFnName(int a_HookType); /** Adds a Lua function to be called for the specified hook. @@ -144,37 +144,47 @@ public: */ bool AddHookRef(int a_HookType, int a_FnRefIdx); + /** Calls a function in this plugin's LuaState with parameters copied over from a_ForeignState. + The values that the function returns are placed onto a_ForeignState. + Returns the number of values returned, if successful, or negative number on failure. */ + int CallFunctionFromForeignState( + const AString & a_FunctionName, + cLuaState & a_ForeignState, + int a_ParamStart, + int a_ParamEnd + ); + // The following templates allow calls to arbitrary Lua functions residing in the plugin: - /// Call a Lua function with 0 args + /** Call a Lua function with 0 args */ template <typename FnT> bool Call(FnT a_Fn) { cCSLock Lock(m_CriticalSection); return m_LuaState.Call(a_Fn); } - /// Call a Lua function with 1 arg + /** Call a Lua function with 1 arg */ template <typename FnT, typename ArgT0> bool Call(FnT a_Fn, ArgT0 a_Arg0) { cCSLock Lock(m_CriticalSection); return m_LuaState.Call(a_Fn, a_Arg0); } - /// Call a Lua function with 2 args + /** Call a Lua function with 2 args */ template <typename FnT, typename ArgT0, typename ArgT1> bool Call(FnT a_Fn, ArgT0 a_Arg0, ArgT1 a_Arg1) { cCSLock Lock(m_CriticalSection); return m_LuaState.Call(a_Fn, a_Arg0, a_Arg1); } - /// Call a Lua function with 3 args + /** Call a Lua function with 3 args */ template <typename FnT, typename ArgT0, typename ArgT1, typename ArgT2> bool Call(FnT a_Fn, ArgT0 a_Arg0, ArgT1 a_Arg1, ArgT2 a_Arg2) { cCSLock Lock(m_CriticalSection); return m_LuaState.Call(a_Fn, a_Arg0, a_Arg1, a_Arg2); } - /// Call a Lua function with 4 args + /** Call a Lua function with 4 args */ template <typename FnT, typename ArgT0, typename ArgT1, typename ArgT2, typename ArgT3> bool Call(FnT a_Fn, ArgT0 a_Arg0, ArgT1 a_Arg1, ArgT2 a_Arg2, ArgT3 a_Arg3) { cCSLock Lock(m_CriticalSection); @@ -182,13 +192,13 @@ public: } protected: - /// Maps command name into Lua function reference + /** Maps command name into Lua function reference */ typedef std::map<AString, int> CommandMap; - /// Provides an array of Lua function references + /** Provides an array of Lua function references */ typedef std::vector<cLuaState::cRef *> cLuaRefs; - /// Maps hook types into arrays of Lua function references to call for each hook type + /** Maps hook types into arrays of Lua function references to call for each hook type */ typedef std::map<int, cLuaRefs> cHookMap; cCriticalSection m_CriticalSection; @@ -199,7 +209,7 @@ protected: cHookMap m_HookMap; - /// Releases all Lua references and closes the LuaState + /** Releases all Lua references and closes the LuaState */ void Close(void); } ; // tolua_export diff --git a/src/Bindings/PluginManager.cpp b/src/Bindings/PluginManager.cpp index 9e318554a..a20583550 100644 --- a/src/Bindings/PluginManager.cpp +++ b/src/Bindings/PluginManager.cpp @@ -1757,6 +1757,21 @@ bool cPluginManager::IsValidHookType(int a_HookType) +bool cPluginManager::DoWithPlugin(const AString & a_PluginName, cPluginCallback & a_Callback) +{ + // TODO: Implement locking for plugins + PluginMap::iterator itr = m_Plugins.find(a_PluginName); + if ((itr == m_Plugins.end()) || (itr->second == NULL)) + { + return false; + } + return a_Callback.Item(itr->second); +} + + + + + bool cPluginManager::AddPlugin(cPlugin * a_Plugin) { m_Plugins[a_Plugin->GetDirectory()] = a_Plugin; diff --git a/src/Bindings/PluginManager.h b/src/Bindings/PluginManager.h index a700ef6ce..c78bceda1 100644 --- a/src/Bindings/PluginManager.h +++ b/src/Bindings/PluginManager.h @@ -123,7 +123,7 @@ public: // tolua_export } ; // tolua_end - /// Used as a callback for enumerating bound commands + /** Used as a callback for enumerating bound commands */ class cCommandEnumCallback { public: @@ -133,7 +133,11 @@ public: // tolua_export virtual bool Command(const AString & a_Command, const cPlugin * a_Plugin, const AString & a_Permission, const AString & a_HelpString) = 0; } ; - /// Returns the instance of the Plugin Manager (there is only ever one) + /** The interface used for enumerating and extern-calling plugins */ + typedef cItemCallback<cPlugin> cPluginCallback; + + + /** Returns the instance of the Plugin Manager (there is only ever one) */ static cPluginManager * Get(void); // tolua_export typedef std::map< AString, cPlugin * > PluginMap; @@ -144,7 +148,7 @@ public: // tolua_export void FindPlugins(); // tolua_export void ReloadPlugins(); // tolua_export - /// Adds the plugin to the list of plugins called for the specified hook type. Handles multiple adds as a single add + /** Adds the plugin to the list of plugins called for the specified hook type. Handles multiple adds as a single add */ void AddHook(cPlugin * a_Plugin, int a_HookType); unsigned int GetNumPlugins() const; // tolua_export @@ -208,46 +212,46 @@ public: // tolua_export bool DisablePlugin(const AString & a_PluginName); // tolua_export bool LoadPlugin (const AString & a_PluginName); // tolua_export - /// Removes all hooks the specified plugin has registered + /** Removes all hooks the specified plugin has registered */ void RemoveHooks(cPlugin * a_Plugin); - /// Removes the plugin from the internal structures and deletes its object. + /** Removes the plugin from the internal structures and deletes its object. */ void RemovePlugin(cPlugin * a_Plugin); - /// Removes all command bindings that the specified plugin has made + /** Removes all command bindings that the specified plugin has made */ void RemovePluginCommands(cPlugin * a_Plugin); - /// Binds a command to the specified plugin. Returns true if successful, false if command already bound. + /** Binds a command to the specified plugin. Returns true if successful, false if command already bound. */ bool BindCommand(const AString & a_Command, cPlugin * a_Plugin, const AString & a_Permission, const AString & a_HelpString); // Exported in ManualBindings.cpp, without the a_Plugin param - /// Calls a_Callback for each bound command, returns true if all commands were enumerated + /** Calls a_Callback for each bound command, returns true if all commands were enumerated */ bool ForEachCommand(cCommandEnumCallback & a_Callback); // Exported in ManualBindings.cpp - /// Returns true if the command is in the command map + /** Returns true if the command is in the command map */ bool IsCommandBound(const AString & a_Command); // tolua_export - /// Returns the permission needed for the specified command; empty string if command not found + /** Returns the permission needed for the specified command; empty string if command not found */ AString GetCommandPermission(const AString & a_Command); // tolua_export - /// Executes the command, as if it was requested by a_Player. Checks permissions first. Returns true if executed. + /** Executes the command, as if it was requested by a_Player. Checks permissions first. Returns true if executed. */ bool ExecuteCommand(cPlayer * a_Player, const AString & a_Command); // tolua_export - /// Executes the command, as if it was requested by a_Player. Permisssions are not checked. Returns true if executed (false if not found) + /** Executes the command, as if it was requested by a_Player. Permisssions are not checked. Returns true if executed (false if not found) */ bool ForceExecuteCommand(cPlayer * a_Player, const AString & a_Command); // tolua_export - /// Removes all console command bindings that the specified plugin has made + /** Removes all console command bindings that the specified plugin has made */ void RemovePluginConsoleCommands(cPlugin * a_Plugin); - /// Binds a console command to the specified plugin. Returns true if successful, false if command already bound. + /** Binds a console command to the specified plugin. Returns true if successful, false if command already bound. */ bool BindConsoleCommand(const AString & a_Command, cPlugin * a_Plugin, const AString & a_HelpString); // Exported in ManualBindings.cpp, without the a_Plugin param - /// Calls a_Callback for each bound console command, returns true if all commands were enumerated + /** Calls a_Callback for each bound console command, returns true if all commands were enumerated */ bool ForEachConsoleCommand(cCommandEnumCallback & a_Callback); // Exported in ManualBindings.cpp - /// Returns true if the console command is in the command map + /** Returns true if the console command is in the command map */ bool IsConsoleCommandBound(const AString & a_Command); // tolua_export - /// Executes the command split into a_Split, as if it was given on the console. Returns true if executed. Output is sent to the a_Output callback + /** Executes the command split into a_Split, as if it was given on the console. Returns true if executed. Output is sent to the a_Output callback */ bool ExecuteConsoleCommand(const AStringVector & a_Split, cCommandOutputCallback & a_Output); /** Appends all commands beginning with a_Text (case-insensitive) into a_Results. @@ -255,9 +259,13 @@ public: // tolua_export */ void TabCompleteCommand(const AString & a_Text, AStringVector & a_Results, cPlayer * a_Player); - /// Returns true if the specified hook type is within the allowed range + /** Returns true if the specified hook type is within the allowed range */ static bool IsValidHookType(int a_HookType); + /** Calls the specified callback with the plugin object of the specified plugin. + Returns false if plugin not found, and the value that the callback has returned otherwise. */ + bool DoWithPlugin(const AString & a_PluginName, cPluginCallback & a_Callback); + private: friend class cRoot; @@ -283,22 +291,22 @@ private: cPluginManager(); virtual ~cPluginManager(); - /// Reloads all plugins, defaulting to settings.ini for settings location + /** Reloads all plugins, defaulting to settings.ini for settings location */ void ReloadPluginsNow(void); - /// Reloads all plugins with a cIniFile object expected to be initialised to settings.ini + /** Reloads all plugins with a cIniFile object expected to be initialised to settings.ini */ void ReloadPluginsNow(cIniFile & a_SettingsIni); - /// Unloads all plugins + /** Unloads all plugins */ void UnloadPluginsNow(void); - /// Handles writing default plugins if 'Plugins' key not found using a cIniFile object expected to be intialised to settings.ini + /** Handles writing default plugins if 'Plugins' key not found using a cIniFile object expected to be intialised to settings.ini */ void InsertDefaultPlugins(cIniFile & a_SettingsIni); - /// Adds the plugin into the internal list of plugins and initializes it. If initialization fails, the plugin is removed again. + /** Adds the plugin into the internal list of plugins and initializes it. If initialization fails, the plugin is removed again. */ bool AddPlugin(cPlugin * a_Plugin); - /// Tries to match a_Command to the internal table of commands, if a match is found, the corresponding plugin is called. Returns true if the command is handled. + /** Tries to match a_Command to the internal table of commands, if a match is found, the corresponding plugin is called. Returns true if the command is handled. */ bool HandleCommand(cPlayer * a_Player, const AString & a_Command, bool a_ShouldCheckPermissions, bool & a_WasCommandForbidden); bool HandleCommand(cPlayer * a_Player, const AString & a_Command, bool a_ShouldCheckPermissions) { diff --git a/src/BlockArea.cpp b/src/BlockArea.cpp index 910661f60..194e2d68a 100644 --- a/src/BlockArea.cpp +++ b/src/BlockArea.cpp @@ -6,9 +6,7 @@ #include "Globals.h" #include "BlockArea.h" -#include "World.h" #include "OSSupport/GZipFile.h" -#include "WorldStorage/FastNBT.h" #include "Blocks/BlockHandler.h" @@ -266,7 +264,7 @@ void cBlockArea::SetOrigin(int a_OriginX, int a_OriginY, int a_OriginZ) -bool cBlockArea::Read(cWorld * a_World, int a_MinBlockX, int a_MaxBlockX, int a_MinBlockY, int a_MaxBlockY, int a_MinBlockZ, int a_MaxBlockZ, int a_DataTypes) +bool cBlockArea::Read(cForEachChunkProvider * a_ForEachChunkProvider, int a_MinBlockX, int a_MaxBlockX, int a_MinBlockY, int a_MaxBlockY, int a_MinBlockZ, int a_MaxBlockZ, int a_DataTypes) { // Normalize the coords: if (a_MinBlockX > a_MaxBlockX) @@ -327,7 +325,7 @@ bool cBlockArea::Read(cWorld * a_World, int a_MinBlockX, int a_MaxBlockX, int a_ cChunkDef::AbsoluteToRelative(a_MaxBlockX, a_MaxBlockY, a_MaxBlockZ, MaxChunkX, MaxChunkZ); // Query block data: - if (!a_World->ForEachChunkInRect(MinChunkX, MaxChunkX, MinChunkZ, MaxChunkZ, Reader)) + if (!a_ForEachChunkProvider->ForEachChunkInRect(MinChunkX, MaxChunkX, MinChunkZ, MaxChunkZ, Reader)) { Clear(); return false; @@ -340,7 +338,7 @@ bool cBlockArea::Read(cWorld * a_World, int a_MinBlockX, int a_MaxBlockX, int a_ -bool cBlockArea::Write(cWorld * a_World, int a_MinBlockX, int a_MinBlockY, int a_MinBlockZ, int a_DataTypes) +bool cBlockArea::Write(cForEachChunkProvider * a_ForEachChunkProvider, int a_MinBlockX, int a_MinBlockY, int a_MinBlockZ, int a_DataTypes) { ASSERT((a_DataTypes & GetDataTypes()) == a_DataTypes); // Are you requesting only the data that I have? a_DataTypes = a_DataTypes & GetDataTypes(); // For release builds, silently cut off the datatypes that I don't have @@ -357,7 +355,7 @@ bool cBlockArea::Write(cWorld * a_World, int a_MinBlockX, int a_MinBlockY, int a a_MinBlockY = cChunkDef::Height - m_SizeY; } - return a_World->WriteBlockArea(*this, a_MinBlockX, a_MinBlockY, a_MinBlockZ, a_DataTypes); + return a_ForEachChunkProvider->WriteBlockArea(*this, a_MinBlockX, a_MinBlockY, a_MinBlockZ, a_DataTypes); } @@ -448,85 +446,12 @@ void cBlockArea::DumpToRawFile(const AString & a_FileName) -bool cBlockArea::LoadFromSchematicFile(const AString & a_FileName) -{ - // Un-GZip the contents: - AString Contents; - cGZipFile File; - if (!File.Open(a_FileName, cGZipFile::fmRead)) - { - LOG("Cannot open the schematic file \"%s\".", a_FileName.c_str()); - return false; - } - int NumBytesRead = File.ReadRestOfFile(Contents); - if (NumBytesRead < 0) - { - LOG("Cannot read GZipped data in the schematic file \"%s\", error %d", a_FileName.c_str(), NumBytesRead); - return false; - } - File.Close(); - - // Parse the NBT: - cParsedNBT NBT(Contents.data(), Contents.size()); - if (!NBT.IsValid()) - { - LOG("Cannot parse the NBT in the schematic file \"%s\".", a_FileName.c_str()); - return false; - } - - return LoadFromSchematicNBT(NBT); -} -bool cBlockArea::SaveToSchematicFile(const AString & a_FileName) -{ - cFastNBTWriter Writer("Schematic"); - Writer.AddShort("Width", m_SizeX); - Writer.AddShort("Height", m_SizeY); - Writer.AddShort("Length", m_SizeZ); - Writer.AddString("Materials", "Alpha"); - if (HasBlockTypes()) - { - Writer.AddByteArray("Blocks", (const char *)m_BlockTypes, GetBlockCount()); - } - else - { - AString Dummy(GetBlockCount(), 0); - Writer.AddByteArray("Blocks", Dummy.data(), Dummy.size()); - } - if (HasBlockMetas()) - { - Writer.AddByteArray("Data", (const char *)m_BlockMetas, GetBlockCount()); - } - else - { - AString Dummy(GetBlockCount(), 0); - Writer.AddByteArray("Data", Dummy.data(), Dummy.size()); - } - // TODO: Save entities and block entities - Writer.BeginList("Entities", TAG_Compound); - Writer.EndList(); - Writer.BeginList("TileEntities", TAG_Compound); - Writer.EndList(); - Writer.Finish(); - - // Save to file - cGZipFile File; - if (!File.Open(a_FileName, cGZipFile::fmWrite)) - { - LOG("Cannot open file \"%s\" for writing.", a_FileName.c_str()); - return false; - } - if (!File.Write(Writer.GetResult())) - { - LOG("Cannot write data to file \"%s\".", a_FileName.c_str()); - return false; - } - return true; -} + @@ -2018,89 +1943,6 @@ void cBlockArea::ExpandNibbles(NIBBLEARRAY & a_Array, int a_SubMinX, int a_AddMa } - - - -bool cBlockArea::LoadFromSchematicNBT(cParsedNBT & a_NBT) -{ - int TMaterials = a_NBT.FindChildByName(a_NBT.GetRoot(), "Materials"); - if ((TMaterials > 0) && (a_NBT.GetType(TMaterials) == TAG_String)) - { - AString Materials = a_NBT.GetString(TMaterials); - if (Materials.compare("Alpha") != 0) - { - LOG("Materials tag is present and \"%s\" instead of \"Alpha\". Possibly a wrong-format schematic file.", Materials.c_str()); - return false; - } - } - int TSizeX = a_NBT.FindChildByName(a_NBT.GetRoot(), "Width"); - int TSizeY = a_NBT.FindChildByName(a_NBT.GetRoot(), "Height"); - int TSizeZ = a_NBT.FindChildByName(a_NBT.GetRoot(), "Length"); - if ( - (TSizeX < 0) || (TSizeY < 0) || (TSizeZ < 0) || - (a_NBT.GetType(TSizeX) != TAG_Short) || - (a_NBT.GetType(TSizeY) != TAG_Short) || - (a_NBT.GetType(TSizeZ) != TAG_Short) - ) - { - LOG("Dimensions are missing from the schematic file (%d, %d, %d), (%d, %d, %d)", - TSizeX, TSizeY, TSizeZ, - a_NBT.GetType(TSizeX), a_NBT.GetType(TSizeY), a_NBT.GetType(TSizeZ) - ); - return false; - } - - int SizeX = a_NBT.GetShort(TSizeX); - int SizeY = a_NBT.GetShort(TSizeY); - int SizeZ = a_NBT.GetShort(TSizeZ); - if ((SizeX < 1) || (SizeY < 1) || (SizeZ < 1)) - { - LOG("Dimensions are invalid in the schematic file: %d, %d, %d", SizeX, SizeY, SizeZ); - return false; - } - - int TBlockTypes = a_NBT.FindChildByName(a_NBT.GetRoot(), "Blocks"); - int TBlockMetas = a_NBT.FindChildByName(a_NBT.GetRoot(), "Data"); - if ((TBlockTypes < 0) || (a_NBT.GetType(TBlockTypes) != TAG_ByteArray)) - { - LOG("BlockTypes are invalid in the schematic file: %d", TBlockTypes); - return false; - } - bool AreMetasPresent = (TBlockMetas > 0) && (a_NBT.GetType(TBlockMetas) == TAG_ByteArray); - - Clear(); - SetSize(SizeX, SizeY, SizeZ, AreMetasPresent ? (baTypes | baMetas) : baTypes); - - // Copy the block types and metas: - int NumBytes = m_SizeX * m_SizeY * m_SizeZ; - if (a_NBT.GetDataLength(TBlockTypes) < NumBytes) - { - LOG("BlockTypes truncated in the schematic file (exp %d, got %d bytes). Loading partial.", - NumBytes, a_NBT.GetDataLength(TBlockTypes) - ); - NumBytes = a_NBT.GetDataLength(TBlockTypes); - } - memcpy(m_BlockTypes, a_NBT.GetData(TBlockTypes), NumBytes); - - if (AreMetasPresent) - { - int NumBytes = m_SizeX * m_SizeY * m_SizeZ; - if (a_NBT.GetDataLength(TBlockMetas) < NumBytes) - { - LOG("BlockMetas truncated in the schematic file (exp %d, got %d bytes). Loading partial.", - NumBytes, a_NBT.GetDataLength(TBlockMetas) - ); - NumBytes = a_NBT.GetDataLength(TBlockMetas); - } - memcpy(m_BlockMetas, a_NBT.GetData(TBlockMetas), NumBytes); - } - - return true; -} - - - - void cBlockArea::RelSetData( int a_RelX, int a_RelY, int a_RelZ, int a_DataTypes, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta, diff --git a/src/BlockArea.h b/src/BlockArea.h index 075cc99ec..59bc0f241 100644 --- a/src/BlockArea.h +++ b/src/BlockArea.h @@ -12,18 +12,7 @@ #pragma once - - - - -// fwd: World.h -class cWorld; - -// fwd: FastNBT.h -class cParsedNBT; - - - +#include "ForEachChunkProvider.h" // tolua_begin @@ -68,13 +57,13 @@ public: void SetOrigin(int a_OriginX, int a_OriginY, int a_OriginZ); /// Reads an area of blocks specified. Returns true if successful. All coords are inclusive. - bool Read(cWorld * a_World, int a_MinBlockX, int a_MaxBlockX, int a_MinBlockY, int a_MaxBlockY, int a_MinBlockZ, int a_MaxBlockZ, int a_DataTypes = baTypes | baMetas); + bool Read(cForEachChunkProvider * a_ForEachChunkProvider, int a_MinBlockX, int a_MaxBlockX, int a_MinBlockY, int a_MaxBlockY, int a_MinBlockZ, int a_MaxBlockZ, int a_DataTypes = baTypes | baMetas); // TODO: Write() is not too good an interface: if it fails, there's no way to repeat only for the parts that didn't write // A better way may be to return a list of cBlockAreas for each part that didn't succeed writing, so that the caller may try again /// Writes the area back into cWorld at the coords specified. Returns true if successful in all chunks, false if only partially / not at all - bool Write(cWorld * a_World, int a_MinBlockX, int a_MinBlockY, int a_MinBlockZ, int a_DataTypes = baTypes | baMetas); + bool Write(cForEachChunkProvider * a_ForEachChunkProvider, int a_MinBlockX, int a_MinBlockY, int a_MinBlockZ, int a_DataTypes = baTypes | baMetas); /// Copies this object's contents into the specified BlockArea. void CopyTo(cBlockArea & a_Into) const; @@ -85,12 +74,6 @@ public: /// For testing purposes only, dumps the area into a file. void DumpToRawFile(const AString & a_FileName); - /// Loads an area from a .schematic file. Returns true if successful - bool LoadFromSchematicFile(const AString & a_FileName); - - /// Saves the area into a .schematic file. Returns true if successful - bool SaveToSchematicFile(const AString & a_FileName); - /// Crops the internal contents by the specified amount of blocks from each border. void Crop(int a_AddMinX, int a_SubMaxX, int a_AddMinY, int a_SubMaxY, int a_AddMinZ, int a_SubMaxZ); @@ -233,6 +216,7 @@ public: protected: friend class cChunkDesc; + friend class cSchematicFileSerializer; class cChunkReader : public cChunkDataCallback @@ -291,9 +275,6 @@ protected: // Expand helpers: void ExpandBlockTypes(int a_SubMinX, int a_AddMaxX, int a_SubMinY, int a_AddMaxY, int a_SubMinZ, int a_AddMaxZ); void ExpandNibbles (NIBBLEARRAY & a_Array, int a_SubMinX, int a_AddMaxX, int a_SubMinY, int a_AddMaxY, int a_SubMinZ, int a_AddMaxZ); - - /// Loads the area from a schematic file uncompressed and parsed into a NBT tree. Returns true if successful. - bool LoadFromSchematicNBT(cParsedNBT & a_NBT); /// Sets the specified datatypes at the specified location. void RelSetData( diff --git a/src/BlockEntities/CommandBlockEntity.cpp b/src/BlockEntities/CommandBlockEntity.cpp index 7e9015d33..0bc6ca359 100644 --- a/src/BlockEntities/CommandBlockEntity.cpp +++ b/src/BlockEntities/CommandBlockEntity.cpp @@ -151,9 +151,13 @@ void cCommandBlockEntity::SendTo(cClientHandle & a_Client) bool cCommandBlockEntity::LoadFromJson(const Json::Value & a_Value) { - m_Command = a_Value.get("Command", "").asString(); + m_PosX = a_Value.get("x", 0).asInt(); + m_PosY = a_Value.get("y", 0).asInt(); + m_PosZ = a_Value.get("z", 0).asInt(); - m_LastOutput = a_Value.get("LastOutput", "").asString(); + m_Command = a_Value.get("Command", "").asString(); + m_LastOutput = a_Value.get("LastOutput", "").asString(); + m_Result = a_Value.get("SuccessCount", 0).asInt(); return true; } @@ -164,9 +168,13 @@ bool cCommandBlockEntity::LoadFromJson(const Json::Value & a_Value) void cCommandBlockEntity::SaveToJson(Json::Value & a_Value) { - a_Value["Command"] = m_Command; + a_Value["x"] = m_PosX; + a_Value["y"] = m_PosY; + a_Value["z"] = m_PosZ; - a_Value["LastOutput"] = m_LastOutput; + a_Value["Command"] = m_Command; + a_Value["LastOutput"] = m_LastOutput; + a_Value["SuccessCount"] = m_Result; } @@ -175,18 +183,24 @@ void cCommandBlockEntity::SaveToJson(Json::Value & a_Value) void cCommandBlockEntity::Execute() { + if (m_World != NULL) + { + if (!m_World->AreCommandBlocksEnabled()) + { + return; + } + } + class CommandBlockOutCb : public cCommandOutputCallback { - cCommandBlockEntity* m_CmdBlock; + cCommandBlockEntity * m_CmdBlock; public: - CommandBlockOutCb(cCommandBlockEntity* a_CmdBlock) : m_CmdBlock(a_CmdBlock) {} + CommandBlockOutCb(cCommandBlockEntity * a_CmdBlock) : m_CmdBlock(a_CmdBlock) {} virtual void Out(const AString & a_Text) { - ASSERT(m_CmdBlock != NULL); - // Overwrite field m_CmdBlock->SetLastOutput(a_Text); } @@ -194,7 +208,7 @@ void cCommandBlockEntity::Execute() LOGD("cCommandBlockEntity: Executing command %s", m_Command.c_str()); - cServer* Server = cRoot::Get()->GetServer(); + cServer * Server = cRoot::Get()->GetServer(); Server->ExecuteConsoleCommand(m_Command, CmdBlockOutCb); diff --git a/src/BlockID.cpp b/src/BlockID.cpp index 095865d07..fbb5a0720 100644 --- a/src/BlockID.cpp +++ b/src/BlockID.cpp @@ -767,6 +767,7 @@ public: g_BlockIsSolid[E_BLOCK_MELON_STEM] = false; g_BlockIsSolid[E_BLOCK_NETHER_PORTAL] = false; g_BlockIsSolid[E_BLOCK_PISTON_EXTENSION] = false; + g_BlockIsSolid[E_BLOCK_POTATOES] = false; g_BlockIsSolid[E_BLOCK_POWERED_RAIL] = false; g_BlockIsSolid[E_BLOCK_RAIL] = false; g_BlockIsSolid[E_BLOCK_REDSTONE_TORCH_OFF] = false; diff --git a/src/Blocks/BlockRail.h b/src/Blocks/BlockRail.h index 55cadfa48..da3783d08 100644 --- a/src/Blocks/BlockRail.h +++ b/src/Blocks/BlockRail.h @@ -186,25 +186,35 @@ public: } if (RailsCnt > 1) { - if (Neighbors[3] && Neighbors[0]) return E_META_RAIL_CURVED_ZP_XP; - else if (Neighbors[3] && Neighbors[1]) return E_META_RAIL_CURVED_ZP_XM; - else if (Neighbors[2] && Neighbors[0]) return E_META_RAIL_CURVED_ZM_XP; - else if (Neighbors[2] && Neighbors[1]) return E_META_RAIL_CURVED_ZM_XM; + if (Neighbors[3] && Neighbors[0] && CanThisRailCurve()) return E_META_RAIL_CURVED_ZP_XP; + else if (Neighbors[3] && Neighbors[1] && CanThisRailCurve()) return E_META_RAIL_CURVED_ZP_XM; + else if (Neighbors[2] && Neighbors[0] && CanThisRailCurve()) return E_META_RAIL_CURVED_ZM_XP; + else if (Neighbors[2] && Neighbors[1] && CanThisRailCurve()) return E_META_RAIL_CURVED_ZM_XM; else if (Neighbors[7] && Neighbors[2]) return E_META_RAIL_ASCEND_ZP; else if (Neighbors[3] && Neighbors[6]) return E_META_RAIL_ASCEND_ZM; else if (Neighbors[5] && Neighbors[0]) return E_META_RAIL_ASCEND_XM; else if (Neighbors[4] && Neighbors[1]) return E_META_RAIL_ASCEND_XP; else if (Neighbors[0] && Neighbors[1]) return E_META_RAIL_XM_XP; else if (Neighbors[2] && Neighbors[3]) return E_META_RAIL_ZM_ZP; - ASSERT(!"Weird neighbor count"); + + if (CanThisRailCurve()) + { + ASSERT(!"Weird neighbor count"); + } } return Meta; } + inline bool CanThisRailCurve(void) + { + return m_BlockType == E_BLOCK_RAIL; + } + + bool IsUnstable(cWorld * a_World, int a_BlockX, int a_BlockY, int a_BlockZ) { - if (a_World->GetBlock(a_BlockX, a_BlockY, a_BlockZ) != E_BLOCK_RAIL) + if (!IsBlockRail(a_World->GetBlock(a_BlockX, a_BlockY, a_BlockZ))) { return false; } @@ -339,11 +349,11 @@ public: { AddFaceDirection(a_BlockX, a_BlockY, a_BlockZ, a_BlockFace, false); NIBBLETYPE Meta; - if (a_World->GetBlock(a_BlockX, a_BlockY, a_BlockZ) != E_BLOCK_RAIL) + if (!IsBlockRail(a_World->GetBlock(a_BlockX, a_BlockY, a_BlockZ))) { - if ((a_World->GetBlock(a_BlockX, a_BlockY + 1, a_BlockZ) != E_BLOCK_RAIL) || (a_Pure != E_PURE_UPDOWN)) + if (!IsBlockRail(a_World->GetBlock(a_BlockX, a_BlockY + 1, a_BlockZ)) || (a_Pure != E_PURE_UPDOWN)) { - if ((a_World->GetBlock(a_BlockX, a_BlockY - 1, a_BlockZ) != E_BLOCK_RAIL) || (a_Pure == E_PURE_NONE)) + if (!IsBlockRail(a_World->GetBlock(a_BlockX, a_BlockY - 1, a_BlockZ)) || (a_Pure == E_PURE_NONE)) { return true; } diff --git a/src/ByteBuffer.cpp b/src/ByteBuffer.cpp index e06f63a0e..d2d3beb97 100644 --- a/src/ByteBuffer.cpp +++ b/src/ByteBuffer.cpp @@ -54,6 +54,7 @@ public: { TestRead(); TestWrite(); + TestWrap(); } void TestRead(void) @@ -61,11 +62,11 @@ public: cByteBuffer buf(50); buf.Write("\x05\xac\x02\x00", 4); UInt32 v1; - ASSERT(buf.ReadVarInt(v1) && (v1 == 5)); + assert(buf.ReadVarInt(v1) && (v1 == 5)); UInt32 v2; - ASSERT(buf.ReadVarInt(v2) && (v2 == 300)); + assert(buf.ReadVarInt(v2) && (v2 == 300)); UInt32 v3; - ASSERT(buf.ReadVarInt(v3) && (v3 == 0)); + assert(buf.ReadVarInt(v3) && (v3 == 0)); } void TestWrite(void) @@ -76,9 +77,30 @@ public: buf.WriteVarInt(0); AString All; buf.ReadAll(All); - ASSERT(All.size() == 4); - ASSERT(memcmp(All.data(), "\x05\xac\x02\x00", All.size()) == 0); + assert(All.size() == 4); + assert(memcmp(All.data(), "\x05\xac\x02\x00", All.size()) == 0); } + + void TestWrap(void) + { + cByteBuffer buf(3); + for (int i = 0; i < 1000; i++) + { + int FreeSpace = buf.GetFreeSpace(); + assert(buf.GetReadableSpace() == 0); + assert(FreeSpace > 0); + assert(buf.Write("a", 1)); + assert(buf.CanReadBytes(1)); + assert(buf.GetReadableSpace() == 1); + unsigned char v = 0; + assert(buf.ReadByte(v)); + assert(v == 'a'); + assert(buf.GetReadableSpace() == 0); + buf.CommitRead(); + assert(buf.GetFreeSpace() == FreeSpace); // We're back to normal + } + } + } g_ByteBufferTest; #endif @@ -159,7 +181,7 @@ bool cByteBuffer::Write(const char * a_Bytes, int a_Count) int CurReadableSpace = GetReadableSpace(); int WrittenBytes = 0; - if (GetFreeSpace() < a_Count) + if (CurFreeSpace < a_Count) { return false; } @@ -864,3 +886,4 @@ void cByteBuffer::CheckValid(void) const + diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 275099540..944150a44 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -4,13 +4,106 @@ project (MCServer) include_directories (SYSTEM "${PROJECT_SOURCE_DIR}/../lib/") include_directories (SYSTEM "${PROJECT_SOURCE_DIR}/../lib/jsoncpp/include") +include_directories (SYSTEM "${PROJECT_SOURCE_DIR}/../lib/polarssl/include") -set(FOLDERS OSSupport HTTPServer Bindings Items Blocks Protocol Generating) +set(FOLDERS OSSupport HTTPServer Items Blocks Protocol Generating) set(FOLDERS ${FOLDERS} WorldStorage Mobs Entities Simulator UI BlockEntities) if (NOT MSVC) + + #Bindings needs to reference other folders so are done here + + #lib dependecies are not included + + set(BINDING_DEPENDECIES + ${CMAKE_CURRENT_SOURCE_DIR}/Bindings/virtual_method_hooks.lua + ${CMAKE_CURRENT_SOURCE_DIR}/Bindings/AllToLua.pkg + ChunkDef.h + BiomeDef.h + OSSupport/File.h + Bindings/LuaFunctions.h + Bindings/PluginManager.h + Bindings/Plugin.h + Bindings/PluginLua.h + Bindings/WebPlugin.h + Bindings/LuaWindow.h + BlockID.h + StringUtils.h + Defines.h + ChatColor.h + ClientHandle.h + Entities/Entity.h + Entities/Floater.h + Entities/Pawn.h + Entities/Player.h + Entities/Pickup.h + Entities/ProjectileEntity.h + Entities/TNTEntity.h + Entities/Effects.h + Server.h + World.h + Inventory.h + Enchantments.h + Item.h + ItemGrid.h + BlockEntities/BlockEntity.h + BlockEntities/BlockEntityWithItems.h + BlockEntities/ChestEntity.h + BlockEntities/DropSpenserEntity.h + BlockEntities/DispenserEntity.h + BlockEntities/DropperEntity.h + BlockEntities/FurnaceEntity.h + BlockEntities/HopperEntity.h + BlockEntities/JukeboxEntity.h + BlockEntities/NoteEntity.h + BlockEntities/SignEntity.h + WebAdmin.h + Root.h + Vector3f.h + Vector3d.h + Vector3i.h + Matrix4f.h + Cuboid.h + BoundingBox.h + Tracer.h + Group.h + BlockArea.h + Generating/ChunkDesc.h + CraftingRecipes.h + UI/Window.h + Mobs/Monster.h + ) + + include_directories(Bindings) + include_directories(.) + + ADD_CUSTOM_COMMAND( + # add any new generated bindings here + OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/Bindings/Bindings.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Bindings/Bindings.h + + # command execuded to regerate bindings + COMMAND tolua -L virtual_method_hooks.lua -o Bindings.cpp -H Bindings.h AllToLua.pkg + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/Bindings/ + + # add any new generation dependencies here + DEPENDS ${BINDING_DEPENDECIES} + ) + #add cpp files here + add_library(Bindings Bindings/PluginManager Bindings/LuaState Bindings/WebPlugin Bindings/Bindings Bindings/ManualBindings Bindings/LuaWindow Bindings/Plugin Bindings/PluginLua Bindings/WebPlugin) + + target_link_libraries(Bindings lua sqlite tolualib) + + #clear file + file(WRITE ${CMAKE_CURRENT_SOURCE_DIR}/Bindings/BindingDependecies.txt) + foreach(dependecy ${BINDING_DEPENDECIES}) + #write each dependecy on a seperate line + file(APPEND ${CMAKE_CURRENT_SOURCE_DIR}/Bindings/BindingDependecies.txt "${dependecy}\n") + endforeach() + + set_directory_properties(PROPERTIES ADDITIONAL_MAKE_CLEAN_FILES "Bindings.cpp Bindings.h") + foreach(folder ${FOLDERS}) add_subdirectory(${folder}) endforeach(folder) @@ -44,6 +137,7 @@ else () # Add all subfolders as solution-folders: list(APPEND FOLDERS "Resources") + list(APPEND FOLDERS "Bindings") function(includefolder PATH) FILE(GLOB FOLDER_FILES "${PATH}/*.cpp" @@ -78,6 +172,13 @@ else () "StackWalker.cpp LeakFinder.h" PROPERTIES COMPILE_FLAGS "/Yc\"Globals.h\"" ) list(APPEND SOURCE "Resources/MCServer.rc") + + # Make MSVC generate the PDB files even for the release build: + set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /Zi") + set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} /Zi") + set(CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE} /DEBUG") + set(CMAKE_SHARED_LINKER_FLAGS_RELEASE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE} /DEBUG") + set(CMAKE_MODULE_LINKER_FLAGS_RELEASE "${CMAKE_MODULE_LINKER_FLAGS_RELEASE} /DEBUG") endif() set(EXECUTABLE MCServer) @@ -120,4 +221,4 @@ endif () if (WIN32) target_link_libraries(${EXECUTABLE} expat tolualib ws2_32.lib Psapi.lib) endif() -target_link_libraries(${EXECUTABLE} md5 luaexpat iniFile jsoncpp cryptopp zlib lua sqlite) +target_link_libraries(${EXECUTABLE} md5 luaexpat iniFile jsoncpp polarssl zlib lua sqlite) diff --git a/src/ClientHandle.cpp b/src/ClientHandle.cpp index 74d192129..ad3f15adc 100644 --- a/src/ClientHandle.cpp +++ b/src/ClientHandle.cpp @@ -148,15 +148,6 @@ cClientHandle::~cClientHandle() SendDisconnect("Server shut down? Kthnxbai"); } - // Queue all remaining outgoing packets to cSocketThreads: - { - cCSLock Lock(m_CSOutgoingData); - AString Data; - m_OutgoingData.ReadAll(Data); - m_OutgoingData.CommitRead(); - cRoot::Get()->GetServer()->WriteToClient(this, Data); - } - // Close the socket as soon as it sends all outgoing data: cRoot::Get()->GetServer()->RemoveClient(this); @@ -263,6 +254,12 @@ void cClientHandle::Authenticate(void) m_Player->Initialize(World); m_State = csAuthenticated; + // Query player team + m_Player->UpdateTeam(); + + // Send scoreboard data + World->GetScoreBoard().SendTo(*this); + cRoot::Get()->GetPluginManager()->CallHookPlayerSpawned(*m_Player); } @@ -604,25 +601,18 @@ void cClientHandle::HandleCommandBlockMessage(const char* a_Data, unsigned int a } } - class cUpdateCommandBlock : - public cCommandBlockCallback - { - AString m_Command; - public: - cUpdateCommandBlock(const AString & a_Command) : m_Command(a_Command) {} - - virtual bool Item(cCommandBlockEntity * a_CommandBlock) override - { - a_CommandBlock->SetCommand(m_Command); - return false; - } - } CmdBlockCB (Command); - cWorld * World = m_Player->GetWorld(); - World->DoWithCommandBlockAt(BlockX, BlockY, BlockZ, CmdBlockCB); + if (World->AreCommandBlocksEnabled()) + { + World->SetCommandBlockCommand(BlockX, BlockY, BlockZ, Command); - SendChat(Printf("%s[INFO]%s Successfully set command block command", cChatColor::Green.c_str(), cChatColor::White.c_str())); + SendChat(Printf("%s[INFO]%s Successfully set command block command", cChatColor::Green.c_str(), cChatColor::White.c_str())); + } + else + { + SendChat(Printf("%s[INFO]%s Command blocks are not enabled on this server", cChatColor::Yellow.c_str(), cChatColor::White.c_str())); + } } @@ -658,7 +648,8 @@ void cClientHandle::HandleLeftClick(int a_BlockX, int a_BlockY, int a_BlockZ, ch // A plugin doesn't agree with the tossing. The plugin itself is responsible for handling the consequences (possible inventory mismatch) return; } - m_Player->TossItem(false); + + m_Player->TossEquippedItem(); return; } @@ -713,7 +704,7 @@ void cClientHandle::HandleLeftClick(int a_BlockX, int a_BlockY, int a_BlockZ, ch // A plugin doesn't agree with the tossing. The plugin itself is responsible for handling the consequences (possible inventory mismatch) return; } - m_Player->TossItem(false, 64); // Toss entire slot - if there aren't enough items, the maximum will be ejected + m_Player->TossEquippedItem(64); // Toss entire slot - if there aren't enough items, the maximum will be ejected return; } @@ -2111,6 +2102,33 @@ void cClientHandle::SendExperienceOrb(const cExpOrb & a_ExpOrb) +void cClientHandle::SendScoreboardObjective(const AString & a_Name, const AString & a_DisplayName, Byte a_Mode) +{ + m_Protocol->SendScoreboardObjective(a_Name, a_DisplayName, a_Mode); +} + + + + + +void cClientHandle::SendScoreUpdate(const AString & a_Objective, const AString & a_Player, cObjective::Score a_Score, Byte a_Mode) +{ + m_Protocol->SendScoreUpdate(a_Objective, a_Player, a_Score, a_Mode); +} + + + + + +void cClientHandle::SendDisplayObjective(const AString & a_Objective, cScoreboard::eDisplaySlot a_Display) +{ + m_Protocol->SendDisplayObjective(a_Objective, a_Display); +} + + + + + void cClientHandle::SendSoundEffect(const AString & a_SoundName, int a_SrcX, int a_SrcY, int a_SrcZ, float a_Volume, float a_Pitch) { m_Protocol->SendSoundEffect(a_SoundName, a_SrcX, a_SrcY, a_SrcZ, a_Volume, a_Pitch); diff --git a/src/ClientHandle.h b/src/ClientHandle.h index 4add022a6..e1f326543 100644 --- a/src/ClientHandle.h +++ b/src/ClientHandle.h @@ -16,6 +16,7 @@ #include "OSSupport/SocketThreads.h" #include "ChunkDef.h" #include "ByteBuffer.h" +#include "Scoreboard.h" @@ -125,6 +126,9 @@ public: void SendRespawn (void); void SendExperience (void); void SendExperienceOrb (const cExpOrb & a_ExpOrb); + void SendScoreboardObjective (const AString & a_Name, const AString & a_DisplayName, Byte a_Mode); + void SendScoreUpdate (const AString & a_Objective, const AString & a_Player, cObjective::Score a_Score, Byte a_Mode); + void SendDisplayObjective (const AString & a_Objective, cScoreboard::eDisplaySlot a_Display); void SendSoundEffect (const AString & a_SoundName, int a_SrcX, int a_SrcY, int a_SrcZ, float a_Volume, float a_Pitch); // a_Src coords are Block * 8 void SendSoundParticleEffect (int a_EffectID, int a_SrcX, int a_SrcY, int a_SrcZ, int a_Data); void SendSpawnFallingBlock (const cFallingBlock & a_FallingBlock); diff --git a/src/Crypto.cpp b/src/Crypto.cpp new file mode 100644 index 000000000..7a06d7fa3 --- /dev/null +++ b/src/Crypto.cpp @@ -0,0 +1,509 @@ + +// Crypto.cpp + +// Implements classes that wrap the cryptographic code library + +#include "Globals.h" +#include "Crypto.h" + +#include "polarssl/pk.h" + + + + + +/* +// Self-test the hash formatting for known values: +// sha1(Notch) : 4ed1f46bbe04bc756bcb17c0c7ce3e4632f06a48 +// sha1(jeb_) : -7c9d5b0044c130109a5d7b5fb5c317c02b4e28c1 +// sha1(simon) : 88e16a1019277b15d58faf0541e11910eb756f6 + +class Test +{ +public: + Test(void) + { + AString DigestNotch, DigestJeb, DigestSimon; + Byte Digest[20]; + cSHA1Checksum Checksum; + Checksum.Update((const Byte *)"Notch", 5); + Checksum.Finalize(Digest); + cSHA1Checksum::DigestToJava(Digest, DigestNotch); + Checksum.Restart(); + Checksum.Update((const Byte *)"jeb_", 4); + Checksum.Finalize(Digest); + cSHA1Checksum::DigestToJava(Digest, DigestJeb); + Checksum.Restart(); + Checksum.Update((const Byte *)"simon", 5); + Checksum.Finalize(Digest); + cSHA1Checksum::DigestToJava(Digest, DigestSimon); + printf("Notch: \"%s\"\n", DigestNotch.c_str()); + printf("jeb_: \"%s\"\n", DigestJeb.c_str()); + printf("simon: \"%s\"\n", DigestSimon.c_str()); + assert(DigestNotch == "4ed1f46bbe04bc756bcb17c0c7ce3e4632f06a48"); + assert(DigestJeb == "-7c9d5b0044c130109a5d7b5fb5c317c02b4e28c1"); + assert(DigestSimon == "88e16a1019277b15d58faf0541e11910eb756f6"); + } +} test; +*/ + + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cRSAPrivateKey: + +cRSAPrivateKey::cRSAPrivateKey(void) +{ + rsa_init(&m_Rsa, RSA_PKCS_V15, 0); + InitRnd(); +} + + + + + +cRSAPrivateKey::cRSAPrivateKey(const cRSAPrivateKey & a_Other) +{ + rsa_init(&m_Rsa, RSA_PKCS_V15, 0); + rsa_copy(&m_Rsa, &a_Other.m_Rsa); + InitRnd(); +} + + + + + +cRSAPrivateKey::~cRSAPrivateKey() +{ + entropy_free(&m_Entropy); + rsa_free(&m_Rsa); +} + + + + + +void cRSAPrivateKey::InitRnd(void) +{ + entropy_init(&m_Entropy); + const unsigned char pers[] = "rsa_genkey"; + ctr_drbg_init(&m_Ctr_drbg, entropy_func, &m_Entropy, pers, sizeof(pers) - 1); +} + + + + + +bool cRSAPrivateKey::Generate(unsigned a_KeySizeBits) +{ + if (rsa_gen_key(&m_Rsa, ctr_drbg_random, &m_Ctr_drbg, a_KeySizeBits, 65537) != 0) + { + // Key generation failed + return false; + } + + return true; +} + + + + + +AString cRSAPrivateKey::GetPubKeyDER(void) +{ + class cPubKey + { + public: + cPubKey(rsa_context * a_Rsa) : + m_IsValid(false) + { + pk_init(&m_Key); + if (pk_init_ctx(&m_Key, pk_info_from_type(POLARSSL_PK_RSA)) != 0) + { + ASSERT(!"Cannot init PrivKey context"); + return; + } + if (rsa_copy(pk_rsa(m_Key), a_Rsa) != 0) + { + ASSERT(!"Cannot copy PrivKey to PK context"); + return; + } + m_IsValid = true; + } + + ~cPubKey() + { + if (m_IsValid) + { + pk_free(&m_Key); + } + } + + operator pk_context * (void) { return &m_Key; } + + protected: + bool m_IsValid; + pk_context m_Key; + } PkCtx(&m_Rsa); + + unsigned char buf[3000]; + int res = pk_write_pubkey_der(PkCtx, buf, sizeof(buf)); + if (res < 0) + { + return AString(); + } + return AString((const char *)(buf + sizeof(buf) - res), (size_t)res); +} + + + + + +int cRSAPrivateKey::Decrypt(const Byte * a_EncryptedData, size_t a_EncryptedLength, Byte * a_DecryptedData, size_t a_DecryptedMaxLength) +{ + if (a_EncryptedLength < m_Rsa.len) + { + LOGD("%s: Invalid a_EncryptedLength: got %u, exp at least %u", + __FUNCTION__, (unsigned)a_EncryptedLength, (unsigned)(m_Rsa.len) + ); + ASSERT(!"Invalid a_DecryptedMaxLength!"); + return -1; + } + if (a_DecryptedMaxLength < m_Rsa.len) + { + LOGD("%s: Invalid a_DecryptedMaxLength: got %u, exp at least %u", + __FUNCTION__, (unsigned)a_EncryptedLength, (unsigned)(m_Rsa.len) + ); + ASSERT(!"Invalid a_DecryptedMaxLength!"); + return -1; + } + size_t DecryptedLength; + int res = rsa_pkcs1_decrypt( + &m_Rsa, ctr_drbg_random, &m_Ctr_drbg, RSA_PRIVATE, &DecryptedLength, + a_EncryptedData, a_DecryptedData, a_DecryptedMaxLength + ); + if (res != 0) + { + return -1; + } + return (int)DecryptedLength; +} + + + + + +int cRSAPrivateKey::Encrypt(const Byte * a_PlainData, size_t a_PlainLength, Byte * a_EncryptedData, size_t a_EncryptedMaxLength) +{ + if (a_EncryptedMaxLength < m_Rsa.len) + { + LOGD("%s: Invalid a_EncryptedMaxLength: got %u, exp at least %u", + __FUNCTION__, (unsigned)a_EncryptedMaxLength, (unsigned)(m_Rsa.len) + ); + ASSERT(!"Invalid a_DecryptedMaxLength!"); + return -1; + } + if (a_EncryptedMaxLength < m_Rsa.len) + { + LOGD("%s: Invalid a_PlainLength: got %u, exp at least %u", + __FUNCTION__, (unsigned)a_PlainLength, (unsigned)(m_Rsa.len) + ); + ASSERT(!"Invalid a_PlainLength!"); + return -1; + } + int res = rsa_pkcs1_encrypt( + &m_Rsa, ctr_drbg_random, &m_Ctr_drbg, RSA_PRIVATE, + a_PlainLength, a_PlainData, a_EncryptedData + ); + if (res != 0) + { + return -1; + } + return (int)m_Rsa.len; +} + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cPublicKey: + +cPublicKey::cPublicKey(const AString & a_PublicKeyDER) +{ + pk_init(&m_Pk); + if (pk_parse_public_key(&m_Pk, (const Byte *)a_PublicKeyDER.data(), a_PublicKeyDER.size()) != 0) + { + ASSERT(!"Cannot parse PubKey"); + return; + } + InitRnd(); +} + + + + + +cPublicKey::~cPublicKey() +{ + pk_free(&m_Pk); +} + + + + + +int cPublicKey::Decrypt(const Byte * a_EncryptedData, size_t a_EncryptedLength, Byte * a_DecryptedData, size_t a_DecryptedMaxLength) +{ + size_t DecryptedLen = a_DecryptedMaxLength; + int res = pk_decrypt(&m_Pk, + a_EncryptedData, a_EncryptedLength, + a_DecryptedData, &DecryptedLen, a_DecryptedMaxLength, + ctr_drbg_random, &m_Ctr_drbg + ); + if (res != 0) + { + return res; + } + return (int)DecryptedLen; +} + + + + + +int cPublicKey::Encrypt(const Byte * a_PlainData, size_t a_PlainLength, Byte * a_EncryptedData, size_t a_EncryptedMaxLength) +{ + size_t EncryptedLength = a_EncryptedMaxLength; + int res = pk_encrypt(&m_Pk, + a_PlainData, a_PlainLength, a_EncryptedData, &EncryptedLength, a_EncryptedMaxLength, + ctr_drbg_random, &m_Ctr_drbg + ); + if (res != 0) + { + return res; + } + return (int)EncryptedLength; +} + + + + + +void cPublicKey::InitRnd(void) +{ + entropy_init(&m_Entropy); + const unsigned char pers[] = "rsa_genkey"; + ctr_drbg_init(&m_Ctr_drbg, entropy_func, &m_Entropy, pers, sizeof(pers) - 1); +} + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cAESCFBDecryptor: + +cAESCFBDecryptor::cAESCFBDecryptor(void) : + m_IsValid(false), + m_IVOffset(0) +{ +} + + + + + +cAESCFBDecryptor::~cAESCFBDecryptor() +{ + // Clear the leftover in-memory data, so that they can't be accessed by a backdoor + memset(&m_Aes, 0, sizeof(m_Aes)); +} + + + + + +void cAESCFBDecryptor::Init(const Byte a_Key[16], const Byte a_IV[16]) +{ + ASSERT(!IsValid()); // Cannot Init twice + + memcpy(m_IV, a_IV, 16); + aes_setkey_enc(&m_Aes, a_Key, 128); + m_IsValid = true; +} + + + + + +void cAESCFBDecryptor::ProcessData(Byte * a_DecryptedOut, const Byte * a_EncryptedIn, size_t a_Length) +{ + ASSERT(IsValid()); // Must Init() first + + // PolarSSL doesn't support AES-CFB8, need to implement it manually: + for (size_t i = 0; i < a_Length; i++) + { + Byte Buffer[sizeof(m_IV)]; + aes_crypt_ecb(&m_Aes, AES_ENCRYPT, m_IV, Buffer); + for (size_t idx = 0; idx < sizeof(m_IV) - 1; idx++) + { + m_IV[idx] = m_IV[idx + 1]; + } + m_IV[sizeof(m_IV) - 1] = a_EncryptedIn[i]; + a_DecryptedOut[i] = a_EncryptedIn[i] ^ Buffer[0]; + } +} + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cAESCFBEncryptor: + +cAESCFBEncryptor::cAESCFBEncryptor(void) : + m_IsValid(false), + m_IVOffset(0) +{ +} + + + + + +cAESCFBEncryptor::~cAESCFBEncryptor() +{ + // Clear the leftover in-memory data, so that they can't be accessed by a backdoor + memset(&m_Aes, 0, sizeof(m_Aes)); +} + + + + + +void cAESCFBEncryptor::Init(const Byte a_Key[16], const Byte a_IV[16]) +{ + ASSERT(!IsValid()); // Cannot Init twice + ASSERT(m_IVOffset == 0); + + memcpy(m_IV, a_IV, 16); + aes_setkey_enc(&m_Aes, a_Key, 128); + m_IsValid = true; +} + + + + + +void cAESCFBEncryptor::ProcessData(Byte * a_EncryptedOut, const Byte * a_PlainIn, size_t a_Length) +{ + ASSERT(IsValid()); // Must Init() first + + // PolarSSL doesn't do AES-CFB8, so we need to implement it ourselves: + for (size_t i = 0; i < a_Length; i++) + { + Byte Buffer[sizeof(m_IV)]; + aes_crypt_ecb(&m_Aes, AES_ENCRYPT, m_IV, Buffer); + for (size_t idx = 0; idx < sizeof(m_IV) - 1; idx++) + { + m_IV[idx] = m_IV[idx + 1]; + } + a_EncryptedOut[i] = a_PlainIn[i] ^ Buffer[0]; + m_IV[sizeof(m_IV) - 1] = a_EncryptedOut[i]; + } +} + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cSHA1Checksum: + +cSHA1Checksum::cSHA1Checksum(void) : + m_DoesAcceptInput(true) +{ + sha1_starts(&m_Sha1); +} + + + + + +void cSHA1Checksum::Update(const Byte * a_Data, size_t a_Length) +{ + ASSERT(m_DoesAcceptInput); // Not Finalize()-d yet, or Restart()-ed + + sha1_update(&m_Sha1, a_Data, a_Length); +} + + + + + +void cSHA1Checksum::Finalize(cSHA1Checksum::Checksum & a_Output) +{ + ASSERT(m_DoesAcceptInput); // Not Finalize()-d yet, or Restart()-ed + + sha1_finish(&m_Sha1, a_Output); + m_DoesAcceptInput = false; +} + + + + + +void cSHA1Checksum::DigestToJava(const Checksum & a_Digest, AString & a_Out) +{ + Checksum Digest; + memcpy(Digest, a_Digest, sizeof(Digest)); + + bool IsNegative = (Digest[0] >= 0x80); + if (IsNegative) + { + // Two's complement: + bool carry = true; // Add one to the whole number + for (int i = 19; i >= 0; i--) + { + Digest[i] = ~Digest[i]; + if (carry) + { + carry = (Digest[i] == 0xff); + Digest[i]++; + } + } + } + a_Out.clear(); + a_Out.reserve(40); + for (int i = 0; i < 20; i++) + { + AppendPrintf(a_Out, "%02x", Digest[i]); + } + while ((a_Out.length() > 0) && (a_Out[0] == '0')) + { + a_Out.erase(0, 1); + } + if (IsNegative) + { + a_Out.insert(0, "-"); + } +} + + + + + + +void cSHA1Checksum::Restart(void) +{ + sha1_starts(&m_Sha1); + m_DoesAcceptInput = true; +} + + + + diff --git a/src/Crypto.h b/src/Crypto.h new file mode 100644 index 000000000..d68f7ec24 --- /dev/null +++ b/src/Crypto.h @@ -0,0 +1,200 @@ + +// Crypto.h + +// Declares classes that wrap the cryptographic code library + + + + + +#pragma once + +#include "polarssl/rsa.h" +#include "polarssl/aes.h" +#include "polarssl/entropy.h" +#include "polarssl/ctr_drbg.h" +#include "polarssl/sha1.h" +#include "polarssl/pk.h" + + + + + +/** Encapsulates an RSA private key used in PKI cryptography */ +class cRSAPrivateKey +{ +public: + /** Creates a new empty object, the key is not assigned */ + cRSAPrivateKey(void); + + /** Deep-copies the key from a_Other */ + cRSAPrivateKey(const cRSAPrivateKey & a_Other); + + ~cRSAPrivateKey(); + + /** Generates a new key within this object, with the specified size in bits. + Returns true on success, false on failure. */ + bool Generate(unsigned a_KeySizeBits = 1024); + + /** Returns the public key part encoded in ASN1 DER encoding */ + AString GetPubKeyDER(void); + + /** Decrypts the data using RSAES-PKCS#1 algorithm. + Both a_EncryptedData and a_DecryptedData must be at least <KeySizeBytes> bytes large. + Returns the number of bytes decrypted, or negative number for error. */ + int Decrypt(const Byte * a_EncryptedData, size_t a_EncryptedLength, Byte * a_DecryptedData, size_t a_DecryptedMaxLength); + + /** Encrypts the data using RSAES-PKCS#1 algorithm. + Both a_EncryptedData and a_DecryptedData must be at least <KeySizeBytes> bytes large. + Returns the number of bytes decrypted, or negative number for error. */ + int Encrypt(const Byte * a_PlainData, size_t a_PlainLength, Byte * a_EncryptedData, size_t a_EncryptedMaxLength); + +protected: + rsa_context m_Rsa; + entropy_context m_Entropy; + ctr_drbg_context m_Ctr_drbg; + + /** Initializes the m_Entropy and m_Ctr_drbg contexts + Common part of this object's construction, called from all constructors. */ + void InitRnd(void); +} ; + + + + + +class cPublicKey +{ +public: + cPublicKey(const AString & a_PublicKeyDER); + ~cPublicKey(); + + /** Decrypts the data using the stored public key + Both a_EncryptedData and a_DecryptedData must be at least <KeySizeBytes> bytes large. + Returns the number of bytes decrypted, or negative number for error. */ + int Decrypt(const Byte * a_EncryptedData, size_t a_EncryptedLength, Byte * a_DecryptedData, size_t a_DecryptedMaxLength); + + /** Encrypts the data using the stored public key + Both a_EncryptedData and a_DecryptedData must be at least <KeySizeBytes> bytes large. + Returns the number of bytes decrypted, or negative number for error. */ + int Encrypt(const Byte * a_PlainData, size_t a_PlainLength, Byte * a_EncryptedData, size_t a_EncryptedMaxLength); + +protected: + pk_context m_Pk; + entropy_context m_Entropy; + ctr_drbg_context m_Ctr_drbg; + + /** Initializes the m_Entropy and m_Ctr_drbg contexts + Common part of this object's construction, called from all constructors. */ + void InitRnd(void); +} ; + + + + + +/** Decrypts data using the AES / CFB (128) algorithm */ +class cAESCFBDecryptor +{ +public: + Byte test; + + cAESCFBDecryptor(void); + ~cAESCFBDecryptor(); + + /** Initializes the decryptor with the specified Key / IV */ + void Init(const Byte a_Key[16], const Byte a_IV[16]); + + /** Decrypts a_Length bytes of the encrypted data; produces a_Length output bytes */ + void ProcessData(Byte * a_DecryptedOut, const Byte * a_EncryptedIn, size_t a_Length); + + /** Returns true if the object has been initialized with the Key / IV */ + bool IsValid(void) const { return m_IsValid; } + +protected: + aes_context m_Aes; + + /** The InitialVector, used by the CFB mode decryption */ + Byte m_IV[16]; + + /** Current offset in the m_IV, used by the CFB mode decryption */ + size_t m_IVOffset; + + /** Indicates whether the object has been initialized with the Key / IV */ + bool m_IsValid; +} ; + + + + + +/** Encrypts data using the AES / CFB (128) algorithm */ +class cAESCFBEncryptor +{ +public: + Byte test; + + cAESCFBEncryptor(void); + ~cAESCFBEncryptor(); + + /** Initializes the decryptor with the specified Key / IV */ + void Init(const Byte a_Key[16], const Byte a_IV[16]); + + /** Encrypts a_Length bytes of the plain data; produces a_Length output bytes */ + void ProcessData(Byte * a_EncryptedOut, const Byte * a_PlainIn, size_t a_Length); + + /** Returns true if the object has been initialized with the Key / IV */ + bool IsValid(void) const { return m_IsValid; } + +protected: + aes_context m_Aes; + + /** The InitialVector, used by the CFB mode encryption */ + Byte m_IV[16]; + + /** Current offset in the m_IV, used by the CFB mode encryption */ + size_t m_IVOffset; + + /** Indicates whether the object has been initialized with the Key / IV */ + bool m_IsValid; +} ; + + + + + +/** Calculates a SHA1 checksum for data stream */ +class cSHA1Checksum +{ +public: + typedef Byte Checksum[20]; // The type used for storing the checksum + + cSHA1Checksum(void); + + /** Adds the specified data to the checksum */ + void Update(const Byte * a_Data, size_t a_Length); + + /** Calculates and returns the final checksum */ + void Finalize(Checksum & a_Output); + + /** Returns true if the object is accepts more input data, false if Finalize()-d (need to Restart()) */ + bool DoesAcceptInput(void) const { return m_DoesAcceptInput; } + + /** Converts a raw 160-bit SHA1 digest into a Java Hex representation + According to http://wiki.vg/wiki/index.php?title=Protocol_Encryption&oldid=2802 + */ + static void DigestToJava(const Checksum & a_Digest, AString & a_JavaOut); + + /** Clears the current context and start a new checksum calculation */ + void Restart(void); + +protected: + /** True if the object is accepts more input data, false if Finalize()-d (need to Restart()) */ + bool m_DoesAcceptInput; + + sha1_context m_Sha1; +} ; + + + + diff --git a/src/Defines.h b/src/Defines.h index 7a86f499e..3a26f4be6 100644 --- a/src/Defines.h +++ b/src/Defines.h @@ -5,8 +5,6 @@ -typedef unsigned char Byte; - /// List of slot numbers, used for inventory-painting typedef std::vector<int> cSlotNums; @@ -282,6 +280,24 @@ inline bool IsBlockLiquid(BLOCKTYPE a_BlockType) +inline bool IsBlockRail(BLOCKTYPE a_BlockType) +{ + switch (a_BlockType) + { + case E_BLOCK_RAIL: + case E_BLOCK_ACTIVATOR_RAIL: + case E_BLOCK_DETECTOR_RAIL: + case E_BLOCK_POWERED_RAIL: + { + return true; + } + default: return false; + } +} + + + + inline bool IsBlockTypeOfDirt(BLOCKTYPE a_BlockType) { diff --git a/src/Entities/Entity.cpp b/src/Entities/Entity.cpp index 565c78dfd..e22f689d9 100644 --- a/src/Entities/Entity.cpp +++ b/src/Entities/Entity.cpp @@ -4,9 +4,7 @@ #include "../World.h" #include "../Server.h" #include "../Root.h" -#include "../Vector3d.h" #include "../Matrix4f.h" -#include "../ReferenceManager.h" #include "../ClientHandle.h" #include "../Chunk.h" #include "../Simulator/FluidSimulator.h" @@ -32,8 +30,6 @@ cEntity::cEntity(eEntityType a_EntityType, double a_X, double a_Y, double a_Z, d , m_MaxHealth(1) , m_AttachedTo(NULL) , m_Attachee(NULL) - , m_Referencers(new cReferenceManager(cReferenceManager::RFMNGR_REFERENCERS)) - , m_References(new cReferenceManager(cReferenceManager::RFMNGR_REFERENCES)) , m_bDirtyHead(true) , m_bDirtyOrientation(true) , m_bDirtyPosition(true) @@ -61,6 +57,8 @@ cEntity::cEntity(eEntityType a_EntityType, double a_X, double a_Y, double a_Z, d , m_Mass (0.001) // Default 1g , m_Width(a_Width) , m_Height(a_Height) + , m_IsSubmerged(false) + , m_IsSwimming(false) { cCSLock Lock(m_CSCount); m_EntityCount++; @@ -73,7 +71,8 @@ cEntity::cEntity(eEntityType a_EntityType, double a_X, double a_Y, double a_Z, d cEntity::~cEntity() { - ASSERT(!m_World->HasEntity(m_UniqueID)); // Before deleting, the entity needs to have been removed from the world + // Before deleting, the entity needs to have been removed from the world, if ever added + ASSERT((m_World == NULL) || !m_World->HasEntity(m_UniqueID)); /* // DEBUG: @@ -99,8 +98,6 @@ cEntity::~cEntity() LOGWARNING("ERROR: Entity deallocated without being destroyed"); ASSERT(!"Entity deallocated without being destroyed or unlinked"); } - delete m_Referencers; - delete m_References; } @@ -534,7 +531,17 @@ void cEntity::Tick(float a_Dt, cChunk & a_Chunk) { TickInVoid(a_Chunk); } - else { m_TicksSinceLastVoidDamage = 0; } + else + m_TicksSinceLastVoidDamage = 0; + + if (IsMob() || IsPlayer()) + { + // Set swimming state + SetSwimState(a_Chunk); + + // Handle drowning + HandleAir(); + } } @@ -912,6 +919,87 @@ void cEntity::TickInVoid(cChunk & a_Chunk) +void cEntity::SetSwimState(cChunk & a_Chunk) +{ + int RelY = (int)floor(m_LastPosY + 0.1); + if ((RelY < 0) || (RelY >= cChunkDef::Height - 1)) + { + m_IsSwimming = false; + m_IsSubmerged = false; + return; + } + + BLOCKTYPE BlockIn; + int RelX = (int)floor(m_LastPosX) - a_Chunk.GetPosX() * cChunkDef::Width; + int RelZ = (int)floor(m_LastPosZ) - a_Chunk.GetPosZ() * cChunkDef::Width; + + // Check if the player is swimming: + // Use Unbounded, because we're being called *after* processing super::Tick(), which could have changed our chunk + if (!a_Chunk.UnboundedRelGetBlockType(RelX, RelY, RelZ, BlockIn)) + { + // This sometimes happens on Linux machines + // Ref.: http://forum.mc-server.org/showthread.php?tid=1244 + LOGD("SetSwimState failure: RelX = %d, RelZ = %d, LastPos = {%.02f, %.02f}, Pos = %.02f, %.02f}", + RelX, RelY, m_LastPosX, m_LastPosZ, GetPosX(), GetPosZ() + ); + m_IsSwimming = false; + m_IsSubmerged = false; + return; + } + m_IsSwimming = IsBlockWater(BlockIn); + + // Check if the player is submerged: + VERIFY(a_Chunk.UnboundedRelGetBlockType(RelX, RelY + 1, RelZ, BlockIn)); + m_IsSubmerged = IsBlockWater(BlockIn); +} + + + + + +void cEntity::HandleAir(void) +{ + // Ref.: http://www.minecraftwiki.net/wiki/Chunk_format + // See if the entity is /submerged/ water (block above is water) + // Get the type of block the entity is standing in: + + if (IsSubmerged()) + { + SetSpeedY(1); // Float in the water + + // Either reduce air level or damage player + if (m_AirLevel < 1) + { + if (m_AirTickTimer < 1) + { + // Damage player + TakeDamage(dtDrowning, NULL, 1, 1, 0); + // Reset timer + m_AirTickTimer = DROWNING_TICKS; + } + else + { + m_AirTickTimer -= 1; + } + } + else + { + // Reduce air supply + m_AirLevel -= 1; + } + } + else + { + // Set the air back to maximum + m_AirLevel = MAX_AIR_LEVEL; + m_AirTickTimer = DROWNING_TICKS; + } +} + + + + + /// Called when the entity starts burning void cEntity::OnStartedBurning(void) { @@ -1429,33 +1517,3 @@ void cEntity::SetPosZ(double a_PosZ) - -////////////////////////////////////////////////////////////////////////// -// Reference stuffs -void cEntity::AddReference(cEntity * & a_EntityPtr) -{ - m_References->AddReference(a_EntityPtr); - a_EntityPtr->ReferencedBy(a_EntityPtr); -} - - - - - -void cEntity::ReferencedBy(cEntity * & a_EntityPtr) -{ - m_Referencers->AddReference(a_EntityPtr); -} - - - - - -void cEntity::Dereference(cEntity * & a_EntityPtr) -{ - m_Referencers->Dereference(a_EntityPtr); -} - - - - diff --git a/src/Entities/Entity.h b/src/Entities/Entity.h index 91463bfd6..b2edfc2b4 100644 --- a/src/Entities/Entity.h +++ b/src/Entities/Entity.h @@ -4,6 +4,7 @@ #include "../Item.h" #include "../Vector3d.h" #include "../Vector3f.h" +#include "../Vector3i.h" @@ -28,12 +29,16 @@ return super::GetClass(); \ } +#define POSX_TOINT (int)floor(GetPosX()) +#define POSY_TOINT (int)floor(GetPosY()) +#define POSZ_TOINT (int)floor(GetPosZ()) +#define POS_TOINT Vector3i(POSXTOINT, POSYTOINT, POSZTOINT) + class cWorld; -class cReferenceManager; class cClientHandle; class cPlayer; class cChunk; @@ -110,6 +115,8 @@ public: BURN_TICKS_PER_DAMAGE = 20, ///< How many ticks to wait between damaging an entity when it is burning BURN_DAMAGE = 1, ///< How much damage to deal when the entity is burning BURN_TICKS = 200, ///< How long to keep an entity burning after it has stood in lava / fire + MAX_AIR_LEVEL = 300, ///< Maximum air an entity can have + DROWNING_TICKS = 20, ///< Number of ticks per heart of damage } ; cEntity(eEntityType a_EntityType, double a_X, double a_Y, double a_Z, double a_Width, double a_Height); @@ -344,7 +351,14 @@ public: virtual bool IsRiding (void) const {return false; } virtual bool IsSprinting(void) const {return false; } virtual bool IsRclking (void) const {return false; } - virtual bool IsInvisible(void) const {return false; } + virtual bool IsInvisible(void) const { return false; } + + /** Returns whether the player is swimming or not */ + virtual bool IsSwimming(void) const{ return m_IsSwimming; } + /** Return whether the player is under water or not */ + virtual bool IsSubmerged(void) const{ return m_IsSubmerged; } + /** Gets remaining air of a monster */ + int GetAirLevel(void) const { return m_AirLevel; } // tolua_end @@ -373,9 +387,6 @@ protected: /// The entity which is attached to this entity (rider), NULL if none cEntity * m_Attachee; - cReferenceManager* m_Referencers; - cReferenceManager* m_References; - // Flags that signal that we haven't updated the clients with the latest. bool m_bDirtyHead; bool m_bDirtyOrientation; @@ -415,11 +426,18 @@ protected: virtual void Destroyed(void) {} // Called after the entity has been destroyed void SetWorld(cWorld * a_World) { m_World = a_World; } - - friend class cReferenceManager; - void AddReference( cEntity*& a_EntityPtr ); - void ReferencedBy( cEntity*& a_EntityPtr ); - void Dereference( cEntity*& a_EntityPtr ); + + /** Called in each tick to handle air-related processing i.e. drowning */ + virtual void HandleAir(); + /** Called once per tick to set IsSwimming and IsSubmerged */ + virtual void SetSwimState(cChunk & a_Chunk); + + /** If an entity is currently swimming in or submerged under water */ + bool m_IsSwimming, m_IsSubmerged; + + /** Air level of a mobile */ + int m_AirLevel; + int m_AirTickTimer; private: // Measured in degrees, [-180, +180) diff --git a/src/Entities/Minecart.cpp b/src/Entities/Minecart.cpp index d0d384481..a650927b1 100644 --- a/src/Entities/Minecart.cpp +++ b/src/Entities/Minecart.cpp @@ -19,6 +19,70 @@ +class cMinecartCollisionCallback : + public cEntityCallback +{ +public: + cMinecartCollisionCallback(Vector3d a_Pos, double a_Height, double a_Width, int a_UniqueID, int a_AttacheeUniqueID) : + m_Pos(a_Pos), + m_Height(a_Height), + m_Width(a_Width), + m_DoesInteserct(false), + m_CollidedEntityPos(0, 0, 0), + m_UniqueID(a_UniqueID), + m_AttacheeUniqueID(a_AttacheeUniqueID) + { + } + + virtual bool Item(cEntity * a_Entity) override + { + ASSERT(a_Entity != NULL); + + if (!a_Entity->IsPlayer() && !a_Entity->IsMob() && !a_Entity->IsMinecart() && !a_Entity->IsBoat()) + { + return false; + } + else if ((a_Entity->GetUniqueID() == m_UniqueID) || (a_Entity->GetUniqueID() == m_AttacheeUniqueID)) + { + return false; + } + + cBoundingBox bbEntity(a_Entity->GetPosition(), a_Entity->GetWidth() / 2, a_Entity->GetHeight()); + cBoundingBox bbMinecart(Vector3d(m_Pos.x, floor(m_Pos.y), m_Pos.z), m_Width / 2, m_Height); + + if (bbEntity.DoesIntersect(bbMinecart)) + { + m_CollidedEntityPos = a_Entity->GetPosition(); + m_DoesInteserct = true; + return true; + } + return false; + } + + bool FoundIntersection(void) const + { + return m_DoesInteserct; + } + + Vector3d GetCollidedEntityPosition(void) const + { + return m_CollidedEntityPos; + } + +protected: + bool m_DoesInteserct; + + Vector3d m_CollidedEntityPos; + + Vector3d m_Pos; + double m_Height, m_Width; + int m_UniqueID; + int m_AttacheeUniqueID; +}; + + + + cMinecart::cMinecart(ePayload a_Payload, double a_X, double a_Y, double a_Z) : super(etMinecart, a_X, a_Y, a_Z, 0.98, 0.7), @@ -30,7 +94,7 @@ cMinecart::cMinecart(ePayload a_Payload, double a_X, double a_Y, double a_Z) : SetMass(20.f); SetMaxHealth(6); SetHealth(6); - SetWidth(1.2); + SetWidth(1); SetHeight(0.9); } @@ -96,10 +160,17 @@ void cMinecart::HandlePhysics(float a_Dt, cChunk & a_Chunk) if (IsBlockRail(InsideType)) AddPosY(1); // Push cart upwards } + bool WasDetectorRail = false; if (IsBlockRail(InsideType)) { - bool WasDetectorRail = false; - SnapToRail(InsideMeta); + if (InsideType == E_BLOCK_RAIL) + { + SnapToRail(InsideMeta); + } + else + { + SnapToRail(InsideMeta & 0x07); + } switch (InsideType) { @@ -116,12 +187,6 @@ void cMinecart::HandlePhysics(float a_Dt, cChunk & a_Chunk) } AddPosition(GetSpeed() * (a_Dt / 1000)); // Commit changes; as we use our own engine when on rails, this needs to be done, whereas it is normally in Entity.cpp - - if (m_bIsOnDetectorRail && !WasDetectorRail) - { - m_World->SetBlock(m_DetectorRailPosition.x, m_DetectorRailPosition.y, m_DetectorRailPosition.z, E_BLOCK_DETECTOR_RAIL, m_World->GetBlockMeta(m_DetectorRailPosition) & 0x07); - m_bIsOnDetectorRail = false; - } } else { @@ -129,6 +194,17 @@ void cMinecart::HandlePhysics(float a_Dt, cChunk & a_Chunk) SetPosY(floor(GetPosY()) + 0.35); // HandlePhysics overrides this if minecart can fall, else, it is to stop ground clipping minecart bottom when off-rail super::HandlePhysics(a_Dt, *Chunk); } + + if (m_bIsOnDetectorRail && !Vector3i((int)floor(GetPosX()), (int)floor(GetPosY()), (int)floor(GetPosZ())).Equals(m_DetectorRailPosition)) + { + m_World->SetBlock(m_DetectorRailPosition.x, m_DetectorRailPosition.y, m_DetectorRailPosition.z, E_BLOCK_DETECTOR_RAIL, m_World->GetBlockMeta(m_DetectorRailPosition) & 0x07); + m_bIsOnDetectorRail = false; + } + else if (WasDetectorRail) + { + m_bIsOnDetectorRail = true; + m_DetectorRailPosition = Vector3i((int)floor(GetPosX()), (int)floor(GetPosY()), (int)floor(GetPosZ())); + } // Broadcast positioning changes to client BroadcastMovementUpdate(); @@ -154,7 +230,9 @@ void cMinecart::HandleRailPhysics(NIBBLETYPE a_RailMeta, float a_Dt) SetSpeedY(0); // Don't move vertically as on ground SetSpeedX(0); // Correct diagonal movement from curved rails - if (TestBlockCollision(a_RailMeta)) return; + // Execute both the entity and block collision checks + bool BlckCol = TestBlockCollision(a_RailMeta), EntCol = TestEntityCollision(a_RailMeta); + if (EntCol || BlckCol) return; if (GetSpeedZ() != 0) // Don't do anything if cart is stationary { @@ -178,7 +256,8 @@ void cMinecart::HandleRailPhysics(NIBBLETYPE a_RailMeta, float a_Dt) SetSpeedY(0); SetSpeedZ(0); - if (TestBlockCollision(a_RailMeta)) return; + bool BlckCol = TestBlockCollision(a_RailMeta), EntCol = TestEntityCollision(a_RailMeta); + if (EntCol || BlckCol) return; if (GetSpeedX() != 0) { @@ -281,94 +360,46 @@ void cMinecart::HandleRailPhysics(NIBBLETYPE a_RailMeta, float a_Dt) { SetYaw(315); // Set correct rotation server side SetPosY(floor(GetPosY()) + 0.55); // Levitate dat cart + SetSpeedY(0); - if (TestBlockCollision(a_RailMeta)) return; + TestBlockCollision(a_RailMeta); + TestEntityCollision(a_RailMeta); + + // SnapToRail handles turning - if (GetSpeedZ() > 0) // Cart moving south - { - int OldX = (int)floor(GetPosX()); - AddSpeedX(-GetSpeedZ() + 0.5); // See below - AddPosX(-GetSpeedZ() * (a_Dt / 1000)); // Diagonally move southwest (which will make cart hit a southwest rail) - // If we are already at southwest rail, set Z speed to zero as we can be moving so fast, MCS doesn't tick fast enough to active the handle for the rail... - // ...and so we derail unexpectedly. - if (GetPosX() <= OldX - 1) SetSpeedZ(0); - } - else if (GetSpeedX() > 0) // Cart moving east - { - int OldZ = (int)floor(GetPosZ()); - AddSpeedZ(-GetSpeedX() + 0.5); - AddPosZ(-GetSpeedX() * (a_Dt / 1000)); // Diagonally move northeast - if (GetPosZ() <= OldZ - 1) SetSpeedX(0); - } break; } case E_META_RAIL_CURVED_ZM_XP: // Curved NORTH EAST { SetYaw(225); SetPosY(floor(GetPosY()) + 0.55); + SetSpeedY(0); - if (TestBlockCollision(a_RailMeta)) return; + TestBlockCollision(a_RailMeta); + TestEntityCollision(a_RailMeta); - if (GetSpeedZ() > 0) - { - int OldX = (int)floor(GetPosX()); - AddSpeedX(GetSpeedZ() - 0.5); - AddPosX(GetSpeedZ() * (a_Dt / 1000)); - if (GetPosX() >= OldX + 1) SetSpeedZ(0); - } - else if (GetSpeedX() < 0) - { - int OldZ = (int)floor(GetPosZ()); - AddSpeedZ(GetSpeedX() + 0.5); - AddPosZ(GetSpeedX() * (a_Dt / 1000)); - if (GetPosZ() <= OldZ - 1) SetSpeedX(0); - } break; } case E_META_RAIL_CURVED_ZP_XM: // Curved SOUTH WEST { SetYaw(135); SetPosY(floor(GetPosY()) + 0.55); + SetSpeedY(0); - if (TestBlockCollision(a_RailMeta)) return; + TestBlockCollision(a_RailMeta); + TestEntityCollision(a_RailMeta); - if (GetSpeedZ() < 0) - { - int OldX = (int)floor(GetPosX()); - AddSpeedX(GetSpeedZ() + 0.5); - AddPosX(GetSpeedZ() * (a_Dt / 1000)); - if (GetPosX() <= OldX - 1) SetSpeedZ(0); - } - else if (GetSpeedX() > 0) - { - int OldZ = (int)floor(GetPosZ()); - AddSpeedZ(GetSpeedX() - 0.5); - AddPosZ(GetSpeedX() * (a_Dt / 1000)); - if (GetPosZ() >= OldZ + 1) SetSpeedX(0); - } break; } case E_META_RAIL_CURVED_ZP_XP: // Curved SOUTH EAST { SetYaw(45); SetPosY(floor(GetPosY()) + 0.55); + SetSpeedY(0); - if (TestBlockCollision(a_RailMeta)) return; + TestBlockCollision(a_RailMeta); + TestEntityCollision(a_RailMeta); - if (GetSpeedZ() < 0) - { - int OldX = (int)floor(GetPosX()); - AddSpeedX(-GetSpeedZ() - 0.5); - AddPosX(-GetSpeedZ() * (a_Dt / 1000)); - if (GetPosX() >= OldX + 1) SetSpeedZ(0); - } - else if (GetSpeedX() < 0) - { - int OldZ = (int)floor(GetPosZ()); - AddSpeedZ(-GetSpeedX() - 0.5); - AddPosZ(-GetSpeedX() * (a_Dt / 1000)); - if (GetPosZ() >= OldZ + 1) SetSpeedX(0); - } break; } default: @@ -385,8 +416,8 @@ void cMinecart::HandleRailPhysics(NIBBLETYPE a_RailMeta, float a_Dt) void cMinecart::HandlePoweredRailPhysics(NIBBLETYPE a_RailMeta) { // Initialise to 'slow down' values - int AccelDecelSpeed = -1; - int AccelDecelNegSpeed = 1; + int AccelDecelSpeed = -2; + int AccelDecelNegSpeed = 2; if ((a_RailMeta & 0x8) == 0x8) { @@ -404,17 +435,18 @@ void cMinecart::HandlePoweredRailPhysics(NIBBLETYPE a_RailMeta) SetSpeedY(0); SetSpeedX(0); - if (TestBlockCollision(a_RailMeta)) return; + bool BlckCol = TestBlockCollision(a_RailMeta), EntCol = TestEntityCollision(a_RailMeta); + if (EntCol || BlckCol) return; if (GetSpeedZ() != 0) { if (GetSpeedZ() > 0) { - AddSpeedZ(AccelDecelNegSpeed); + AddSpeedZ(AccelDecelSpeed); } else { - AddSpeedZ(AccelDecelSpeed); + AddSpeedZ(AccelDecelNegSpeed); } } break; @@ -426,7 +458,8 @@ void cMinecart::HandlePoweredRailPhysics(NIBBLETYPE a_RailMeta) SetSpeedY(0); SetSpeedZ(0); - if (TestBlockCollision(a_RailMeta)) return; + bool BlckCol = TestBlockCollision(a_RailMeta), EntCol = TestEntityCollision(a_RailMeta); + if (EntCol || BlckCol) return; if (GetSpeedX() != 0) { @@ -441,6 +474,87 @@ void cMinecart::HandlePoweredRailPhysics(NIBBLETYPE a_RailMeta) } break; } + case E_META_RAIL_ASCEND_XM: // ASCEND EAST + { + SetYaw(180); + SetSpeedZ(0); + + if (GetSpeedX() >= 0) + { + if (GetSpeedX() <= MAX_SPEED) + { + AddSpeedX(AccelDecelSpeed); + SetSpeedY(-GetSpeedX()); + } + } + else + { + AddSpeedX(AccelDecelNegSpeed); + SetSpeedY(-GetSpeedX()); + } + break; + } + case E_META_RAIL_ASCEND_XP: // ASCEND WEST + { + SetYaw(180); + SetSpeedZ(0); + + if (GetSpeedX() > 0) + { + AddSpeedX(AccelDecelSpeed); + SetSpeedY(GetSpeedX()); + } + else + { + if (GetSpeedX() >= MAX_SPEED_NEGATIVE) + { + AddSpeedX(AccelDecelNegSpeed); + SetSpeedY(GetSpeedX()); + } + } + break; + } + case E_META_RAIL_ASCEND_ZM: // ASCEND NORTH + { + SetYaw(270); + SetSpeedX(0); + + if (GetSpeedZ() >= 0) + { + if (GetSpeedZ() <= MAX_SPEED) + { + AddSpeedZ(AccelDecelSpeed); + SetSpeedY(-GetSpeedZ()); + } + } + else + { + AddSpeedZ(AccelDecelNegSpeed); + SetSpeedY(-GetSpeedZ()); + } + break; + } + case E_META_RAIL_ASCEND_ZP: // ASCEND SOUTH + { + SetYaw(270); + SetSpeedX(0); + + if (GetSpeedZ() > 0) + { + AddSpeedZ(AccelDecelSpeed); + SetSpeedY(GetSpeedZ()); + } + else + { + if (GetSpeedZ() >= MAX_SPEED_NEGATIVE) + { + AddSpeedZ(AccelDecelNegSpeed); + SetSpeedY(GetSpeedZ()); + } + } + break; + } + default: ASSERT(!"Unhandled powered rail metadata!"); break; } } @@ -450,11 +564,17 @@ void cMinecart::HandlePoweredRailPhysics(NIBBLETYPE a_RailMeta) void cMinecart::HandleDetectorRailPhysics(NIBBLETYPE a_RailMeta, float a_Dt) { - m_bIsOnDetectorRail = true; - m_DetectorRailPosition = Vector3i((int)floor(GetPosX()), (int)floor(GetPosY()), (int)floor(GetPosZ())); - m_World->SetBlockMeta(m_DetectorRailPosition, a_RailMeta | 0x08); + // No special handling + HandleRailPhysics(a_RailMeta & 0x07, a_Dt); +} + + + + +void cMinecart::HandleActivatorRailPhysics(NIBBLETYPE a_RailMeta, float a_Dt) +{ HandleRailPhysics(a_RailMeta & 0x07, a_Dt); } @@ -482,6 +602,107 @@ void cMinecart::SnapToRail(NIBBLETYPE a_RailMeta) SetPosX(floor(GetPosX()) + 0.5); break; } + // Curved rail physics: once minecart has reached more than half of the block in the direction that it is travelling in, jerk it in the direction of curvature + case E_META_RAIL_CURVED_ZM_XM: + { + if (GetPosZ() > floor(GetPosZ()) + 0.5) + { + if (GetSpeedZ() > 0) + { + SetSpeedX(-GetSpeedZ() * 0.7); + } + + SetSpeedZ(0); + SetPosZ(floor(GetPosZ()) + 0.5); + } + else if (GetPosX() > floor(GetPosX()) + 0.5) + { + if (GetSpeedX() > 0) + { + SetSpeedZ(-GetSpeedX() * 0.7); + } + + SetSpeedX(0); + SetPosX(floor(GetPosX()) + 0.5); + } + SetSpeedY(0); + break; + } + case E_META_RAIL_CURVED_ZM_XP: + { + if (GetPosZ() > floor(GetPosZ()) + 0.5) + { + if (GetSpeedZ() > 0) + { + SetSpeedX(GetSpeedZ() * 0.7); + } + + SetSpeedZ(0); + SetPosZ(floor(GetPosZ()) + 0.5); + } + else if (GetPosX() < floor(GetPosX()) + 0.5) + { + if (GetSpeedX() < 0) + { + SetSpeedZ(GetSpeedX() * 0.7); + } + + SetSpeedX(0); + SetPosX(floor(GetPosX()) + 0.5); + } + SetSpeedY(0); + break; + } + case E_META_RAIL_CURVED_ZP_XM: + { + if (GetPosZ() < floor(GetPosZ()) + 0.5) + { + if (GetSpeedZ() < 0) + { + SetSpeedX(GetSpeedZ() * 0.7); + } + + SetSpeedZ(0); + SetPosZ(floor(GetPosZ()) + 0.5); + } + else if (GetPosX() > floor(GetPosX()) + 0.5) + { + if (GetSpeedX() > 0) + { + SetSpeedZ(GetSpeedX() * 0.7); + } + + SetSpeedX(0); + SetPosX(floor(GetPosX()) + 0.5); + } + SetSpeedY(0); + break; + } + case E_META_RAIL_CURVED_ZP_XP: + { + if (GetPosZ() < floor(GetPosZ()) + 0.5) + { + if (GetSpeedZ() < 0) + { + SetSpeedX(-GetSpeedZ() * 0.7); + } + + SetSpeedZ(0); + SetPosZ(floor(GetPosZ()) + 0.5); + } + else if (GetPosX() < floor(GetPosX()) + 0.5) + { + if (GetSpeedX() < 0) + { + SetSpeedZ(-GetSpeedX() * 0.7); + } + + SetSpeedX(0); + SetPosX(floor(GetPosX()) + 0.5); + } + SetSpeedY(0); + break; + } default: break; } } @@ -513,7 +734,7 @@ bool cMinecart::TestBlockCollision(NIBBLETYPE a_RailMeta) } } } - else + else if (GetSpeedZ() < 0) { BLOCKTYPE Block = m_World->GetBlock((int)floor(GetPosX()), (int)floor(GetPosY()), (int)floor(GetPosZ()) - 1); if (!IsBlockRail(Block) && g_BlockIsSolid[Block]) @@ -549,7 +770,7 @@ bool cMinecart::TestBlockCollision(NIBBLETYPE a_RailMeta) } } } - else + else if (GetSpeedX() < 0) { BLOCKTYPE Block = m_World->GetBlock((int)floor(GetPosX()) - 1, (int)floor(GetPosY()), (int)floor(GetPosZ())); if (!IsBlockRail(Block) && g_BlockIsSolid[Block]) @@ -597,10 +818,93 @@ bool cMinecart::TestBlockCollision(NIBBLETYPE a_RailMeta) +bool cMinecart::TestEntityCollision(NIBBLETYPE a_RailMeta) +{ + cMinecartCollisionCallback MinecartCollisionCallback(GetPosition(), GetHeight(), GetWidth(), GetUniqueID(), ((m_Attachee == NULL) ? -1 : m_Attachee->GetUniqueID())); + int ChunkX, ChunkZ; + cChunkDef::BlockToChunk((int)floor(GetPosX()), (int)floor(GetPosZ()), ChunkX, ChunkZ); + m_World->ForEachEntityInChunk(ChunkX, ChunkZ, MinecartCollisionCallback); + + if (!MinecartCollisionCallback.FoundIntersection()) + { + return false; + } + + switch (a_RailMeta) + { + case E_META_RAIL_ZM_ZP: + { + if (MinecartCollisionCallback.GetCollidedEntityPosition().z >= GetPosZ()) + { + if ((-GetSpeedZ() * 0.4) < 0.01) + { + AddSpeedZ(-4); + } + else + { + SetSpeedZ(-GetSpeedZ() * 0.4); + } + } + else + { + if ((GetSpeedZ() * 0.4) < 0.01) + { + AddSpeedZ(4); + } + else + { + SetSpeedZ(GetSpeedZ() * 0.4); + } + } + return true; + } + case E_META_RAIL_XM_XP: + { + if (MinecartCollisionCallback.GetCollidedEntityPosition().x >= GetPosX()) + { + if ((-GetSpeedX() * 0.4) < 0.01) + { + AddSpeedX(-4); + } + else + { + SetSpeedX(-GetSpeedX() * 0.4); + } + } + else + { + if ((GetSpeedX() * 0.4) < 0.01) + { + AddSpeedX(4); + } + else + { + SetSpeedX(GetSpeedX() * 0.4); + } + } + return true; + } + case E_META_RAIL_CURVED_ZM_XM: + case E_META_RAIL_CURVED_ZM_XP: + case E_META_RAIL_CURVED_ZP_XM: + case E_META_RAIL_CURVED_ZP_XP: + { + // TODO - simply can't be bothered right now + break; + } + default: break; + } + + return false; +} + + + + void cMinecart::DoTakeDamage(TakeDamageInfo & TDI) { - if (TDI.Attacker->IsPlayer() && ((cPlayer *)TDI.Attacker)->IsGameModeCreative()) + if ((TDI.Attacker != NULL) && TDI.Attacker->IsPlayer() && ((cPlayer *)TDI.Attacker)->IsGameModeCreative()) { Destroy(); TDI.FinalDamage = GetMaxHealth(); // Instant hit for creative diff --git a/src/Entities/Minecart.h b/src/Entities/Minecart.h index 874d0204e..073e78953 100644 --- a/src/Entities/Minecart.h +++ b/src/Entities/Minecart.h @@ -15,20 +15,6 @@ -inline bool IsBlockRail(BLOCKTYPE a_BlockType) - { - return ( - (a_BlockType == E_BLOCK_RAIL) || - (a_BlockType == E_BLOCK_ACTIVATOR_RAIL) || - (a_BlockType == E_BLOCK_DETECTOR_RAIL) || - (a_BlockType == E_BLOCK_POWERED_RAIL) - ) ; - } - - - - - class cMinecart : public cEntity { @@ -79,10 +65,16 @@ protected: */ void HandleDetectorRailPhysics(NIBBLETYPE a_RailMeta, float a_Dt); - /** Snaps a minecart to a rail's axis, resetting its speed */ + /** Handles activator rails - placeholder for future implementation */ + void HandleActivatorRailPhysics(NIBBLETYPE a_RailMeta, float a_Dt); + + /** Snaps a mincecart to a rail's axis, resetting its speed + For curved rails, it changes the cart's direction as well as snapping it to axis */ void SnapToRail(NIBBLETYPE a_RailMeta); - /** Tests is a solid block is in front of a cart, and stops the cart (and returns true) if so; returns false if no obstruction*/ + /** Tests if a solid block is in front of a cart, and stops the cart (and returns true) if so; returns false if no obstruction */ bool TestBlockCollision(NIBBLETYPE a_RailMeta); + /** Tests if this mincecart's bounding box is intersecting another entity's bounding box (collision) and pushes mincecart away */ + bool TestEntityCollision(NIBBLETYPE a_RailMeta); } ; diff --git a/src/Entities/Pickup.cpp b/src/Entities/Pickup.cpp index 001e386a7..bfe162b69 100644 --- a/src/Entities/Pickup.cpp +++ b/src/Entities/Pickup.cpp @@ -6,19 +6,58 @@ #endif #include "Pickup.h" +#include "Player.h" #include "../ClientHandle.h" -#include "../Inventory.h" #include "../World.h" -#include "../Simulator/FluidSimulator.h" #include "../Server.h" -#include "Player.h" #include "../Bindings/PluginManager.h" -#include "../Item.h" #include "../Root.h" #include "../Chunk.h" -#include "../Vector3d.h" -#include "../Vector3f.h" + + + +class cPickupCombiningCallback : + public cEntityCallback +{ +public: + cPickupCombiningCallback(Vector3d a_Position, cPickup * a_Pickup) : + m_Position(a_Position), + m_Pickup(a_Pickup), + m_FoundMatchingPickup(false) + { + } + + virtual bool Item(cEntity * a_Entity) override + { + if (!a_Entity->IsPickup() || (a_Entity->GetUniqueID() == m_Pickup->GetUniqueID()) || a_Entity->IsDestroyed()) + { + return false; + } + + Vector3d EntityPos = a_Entity->GetPosition(); + double Distance = (EntityPos - m_Position).Length(); + + if ((Distance < 1.2) && ((cPickup *)a_Entity)->GetItem().IsEqual(m_Pickup->GetItem())) + { + m_Pickup->GetItem().AddCount(((cPickup *)a_Entity)->GetItem().m_ItemCount); + a_Entity->Destroy(); + m_FoundMatchingPickup = true; + } + return false; + } + + inline bool FoundMatchingPickup() + { + return m_FoundMatchingPickup; + } + +protected: + bool m_FoundMatchingPickup; + + Vector3d m_Position; + cPickup * m_Pickup; +}; @@ -26,10 +65,10 @@ cPickup::cPickup(double a_PosX, double a_PosY, double a_PosZ, const cItem & a_Item, bool IsPlayerCreated, float a_SpeedX /* = 0.f */, float a_SpeedY /* = 0.f */, float a_SpeedZ /* = 0.f */) : cEntity(etPickup, a_PosX, a_PosY, a_PosZ, 0.2, 0.2) - , m_Timer( 0.f ) + , m_Timer(0.f) , m_Item(a_Item) - , m_bCollected( false ) - , m_bIsPlayerCreated( IsPlayerCreated ) + , m_bCollected(false) + , m_bIsPlayerCreated(IsPlayerCreated) { SetGravity(-10.5f); SetMaxHealth(5); @@ -89,6 +128,16 @@ void cPickup::Tick(float a_Dt, cChunk & a_Chunk) return; } } + + if (!IsDestroyed()) // Don't try to combine if someone has tried to combine me + { + cPickupCombiningCallback PickupCombiningCallback(GetPosition(), this); + m_World->ForEachEntity(PickupCombiningCallback); // Not ForEachEntityInChunk, otherwise pickups don't combine across chunk boundaries + if (PickupCombiningCallback.FoundMatchingPickup()) + { + m_World->BroadcastEntityMetadata(*this); + } + } } } } diff --git a/src/Entities/Player.cpp b/src/Entities/Player.cpp index 9a935520f..8c37fdc8d 100644 --- a/src/Entities/Player.cpp +++ b/src/Entities/Player.cpp @@ -7,7 +7,6 @@ #include "../UI/Window.h" #include "../UI/WindowOwner.h" #include "../World.h" -#include "Pickup.h" #include "../Bindings/PluginManager.h" #include "../BlockEntities/BlockEntity.h" #include "../GroupManager.h" @@ -27,8 +26,6 @@ #include "inifile/iniFile.h" #include "json/json.h" -#define float2int(x) ((x)<0 ? ((int)(x))-1 : (int)(x)) - @@ -36,8 +33,6 @@ cPlayer::cPlayer(cClientHandle* a_Client, const AString & a_PlayerName) : super(etPlayer, 0.6, 1.8) - , m_AirLevel( MAX_AIR_LEVEL ) - , m_AirTickTimer(DROWNING_TICKS) , m_bVisible(true) , m_FoodLevel(MAX_FOOD_LEVEL) , m_FoodSaturationLevel(5) @@ -74,6 +69,7 @@ cPlayer::cPlayer(cClientHandle* a_Client, const AString & a_PlayerName) , m_IsChargingBow(false) , m_BowCharge(0) , m_FloaterID(-1) + , m_Team(NULL) { LOGD("Created a player object for \"%s\" @ \"%s\" at %p, ID %d", a_PlayerName.c_str(), a_Client->GetIPString().c_str(), @@ -107,9 +103,23 @@ cPlayer::cPlayer(cClientHandle* a_Client, const AString & a_PlayerName) a_PlayerName.c_str(), GetPosX(), GetPosY(), GetPosZ() ); } + m_LastJumpHeight = (float)(GetPosY()); m_LastGroundHeight = (float)(GetPosY()); m_Stance = GetPosY() + 1.62; + + if (m_GameMode == gmNotSet) + { + cWorld * World = cRoot::Get()->GetWorld(GetLoadedWorldName()); + if (World == NULL) + { + World = cRoot::Get()->GetDefaultWorld(); + } + if (World->IsGameModeCreative()) + { + m_CanFly = true; + } + } cRoot::Get()->GetServer()->PlayerCreated(this); } @@ -198,12 +208,6 @@ void cPlayer::Tick(float a_Dt, cChunk & a_Chunk) super::Tick(a_Dt, a_Chunk); - // Set player swimming state - SetSwimState(a_Chunk); - - // Handle air drowning stuff - HandleAir(); - // Handle charging the bow: if (m_IsChargingBow) { @@ -436,7 +440,7 @@ void cPlayer::SetTouchGround(bool a_bTouchGround) cWorld * World = GetWorld(); if ((GetPosY() >= 0) && (GetPosY() < cChunkDef::Height)) { - BLOCKTYPE BlockType = World->GetBlock(float2int(GetPosX()), float2int(GetPosY()), float2int(GetPosZ())); + BLOCKTYPE BlockType = World->GetBlock((int)floor(GetPosX()), (int)floor(GetPosY()), (int)floor(GetPosZ())); if (BlockType != E_BLOCK_AIR) { m_bTouchGround = true; @@ -461,12 +465,10 @@ void cPlayer::SetTouchGround(bool a_bTouchGround) if (Damage > 0) { - if (!IsGameModeCreative()) - { - TakeDamage(dtFalling, NULL, Damage, Damage, 0); - } + // cPlayer makes sure damage isn't applied in creative, no need to check here + TakeDamage(dtFalling, NULL, Damage, Damage, 0); - // Mojang uses floor() to get X and Z positions, instead of just casting it to an (int) + // Fall particles GetWorld()->BroadcastSoundParticleEffect(2006, (int)floor(GetPosX()), (int)GetPosY() - 1, (int)floor(GetPosZ()), Damage /* Used as particle effect speed modifier */); } @@ -788,10 +790,24 @@ void cPlayer::DoTakeDamage(TakeDamageInfo & a_TDI) { if (IsGameModeCreative()) { - // No damage / health in creative mode + // No damage / health in creative mode if not void damage return; } } + + if ((a_TDI.Attacker != NULL) && (a_TDI.Attacker->IsPlayer())) + { + cPlayer* Attacker = (cPlayer*) a_TDI.Attacker; + + if ((m_Team != NULL) && (m_Team == Attacker->m_Team)) + { + if (!m_Team->AllowsFriendlyFire()) + { + // Friendly fire is disabled + return; + } + } + } super::DoTakeDamage(a_TDI); @@ -838,6 +854,25 @@ void cPlayer::KilledBy(cEntity * a_Killer) GetWorld()->BroadcastChat(Printf("%s[DEATH] %s%s was killed by a %s", cChatColor::Red.c_str(), cChatColor::White.c_str(), GetName().c_str(), KillerClass.c_str())); } + + class cIncrementCounterCB + : public cObjectiveCallback + { + AString m_Name; + public: + cIncrementCounterCB(const AString & a_Name) : m_Name(a_Name) {} + + virtual bool Item(cObjective * a_Objective) override + { + a_Objective->AddScore(m_Name, 1); + return true; + } + } IncrementCounter (GetName()); + + cScoreboard & Scoreboard = m_World->GetScoreBoard(); + + // Update scoreboard objectives + Scoreboard.ForEachObjectiveWith(cObjective::E_TYPE_DEATH_COUNT, IncrementCounter); } @@ -918,6 +953,50 @@ bool cPlayer::IsGameModeAdventure(void) const +void cPlayer::SetTeam(cTeam * a_Team) +{ + if (m_Team == a_Team) + { + return; + } + + if (m_Team) + { + m_Team->RemovePlayer(GetName()); + } + + m_Team = a_Team; + + if (m_Team) + { + m_Team->AddPlayer(GetName()); + } +} + + + + + +cTeam * cPlayer::UpdateTeam(void) +{ + if (m_World == NULL) + { + SetTeam(NULL); + } + else + { + cScoreboard & Scoreboard = m_World->GetScoreBoard(); + + SetTeam(Scoreboard.QueryPlayerTeam(GetName())); + } + + return m_Team; +} + + + + + void cPlayer::OpenWindow(cWindow * a_Window) { if (a_Window != m_CurrentWindow) @@ -1344,59 +1423,68 @@ AString cPlayer::GetColor(void) const -void cPlayer::TossItem( - bool a_bDraggingItem, - char a_Amount /* = 1 */, - short a_CreateType /* = 0 */, - short a_CreateHealth /* = 0 */ -) +void cPlayer::TossEquippedItem(char a_Amount) { cItems Drops; - if (a_CreateType != 0) + cItem DroppedItem(GetInventory().GetEquippedItem()); + if (!DroppedItem.IsEmpty()) { - // Just create item without touching the inventory (used in creative mode) - Drops.push_back(cItem(a_CreateType, a_Amount, a_CreateHealth)); + char NewAmount = a_Amount; + if (NewAmount > GetInventory().GetEquippedItem().m_ItemCount) + { + NewAmount = GetInventory().GetEquippedItem().m_ItemCount; // Drop only what's there + } + + GetInventory().GetHotbarGrid().ChangeSlotCount(GetInventory().GetEquippedSlotNum() /* Returns hotbar subslot, which HotbarGrid takes */, -a_Amount); + + DroppedItem.m_ItemCount = NewAmount; + Drops.push_back(DroppedItem); } - else + + double vX = 0, vY = 0, vZ = 0; + EulerToVector(-GetYaw(), GetPitch(), vZ, vX, vY); + vY = -vY * 2 + 1.f; + m_World->SpawnItemPickups(Drops, GetPosX(), GetEyeHeight(), GetPosZ(), vX * 3, vY * 3, vZ * 3, true); // 'true' because created by player +} + + + + + +void cPlayer::TossHeldItem(char a_Amount) +{ + cItems Drops; + cItem & Item = GetDraggingItem(); + if (!Item.IsEmpty()) { - // Drop an item from the inventory: - if (a_bDraggingItem) + char OriginalItemAmount = Item.m_ItemCount; + Item.m_ItemCount = std::min(OriginalItemAmount, a_Amount); + Drops.push_back(Item); + if (OriginalItemAmount > a_Amount) { - cItem & Item = GetDraggingItem(); - if (!Item.IsEmpty()) - { - char OriginalItemAmount = Item.m_ItemCount; - Item.m_ItemCount = std::min(OriginalItemAmount, a_Amount); - Drops.push_back(Item); - if (OriginalItemAmount > a_Amount) - { - Item.m_ItemCount = OriginalItemAmount - (char)a_Amount; - } - else - { - Item.Empty(); - } - } + Item.m_ItemCount = OriginalItemAmount - a_Amount; } else { - // Else drop equipped item - cItem DroppedItem(GetInventory().GetEquippedItem()); - if (!DroppedItem.IsEmpty()) - { - char NewAmount = a_Amount; - if (NewAmount > GetInventory().GetEquippedItem().m_ItemCount) - { - NewAmount = GetInventory().GetEquippedItem().m_ItemCount; // Drop only what's there - } - - GetInventory().GetHotbarGrid().ChangeSlotCount(GetInventory().GetEquippedSlotNum() /* Returns hotbar subslot, which HotbarGrid takes */, -a_Amount); - - DroppedItem.m_ItemCount = NewAmount; - Drops.push_back(DroppedItem); - } + Item.Empty(); } } + + double vX = 0, vY = 0, vZ = 0; + EulerToVector(-GetYaw(), GetPitch(), vZ, vX, vY); + vY = -vY * 2 + 1.f; + m_World->SpawnItemPickups(Drops, GetPosX(), GetEyeHeight(), GetPosZ(), vX * 3, vY * 3, vZ * 3, true); // 'true' because created by player +} + + + + + +void cPlayer::TossPickup(const cItem & a_Item) +{ + cItems Drops; + Drops.push_back(a_Item); + double vX = 0, vY = 0, vZ = 0; EulerToVector(-GetYaw(), GetPitch(), vZ, vX, vY); vY = -vY * 2 + 1.f; @@ -1545,27 +1633,12 @@ bool cPlayer::LoadFromDisk() m_CurrentXp = (short) root.get("xpCurrent", 0).asInt(); m_IsFlying = root.get("isflying", 0).asBool(); - //SetExperience(root.get("experience", 0).asInt()); - m_GameMode = (eGameMode) root.get("gamemode", eGameMode_NotSet).asInt(); if (m_GameMode == eGameMode_Creative) { m_CanFly = true; } - else if (m_GameMode == eGameMode_NotSet) - { - cWorld * World = cRoot::Get()->GetWorld(GetLoadedWorldName()); - if (World == NULL) - { - World = cRoot::Get()->GetDefaultWorld(); - } - - if (World->GetGameMode() == eGameMode_Creative) - { - m_CanFly = true; - } - } m_Inventory.LoadFromJson(root["inventory"]); @@ -1682,85 +1755,6 @@ void cPlayer::UseEquippedItem(void) -void cPlayer::SetSwimState(cChunk & a_Chunk) -{ - int RelY = (int)floor(m_LastPosY + 0.1); - if ((RelY < 0) || (RelY >= cChunkDef::Height - 1)) - { - m_IsSwimming = false; - m_IsSubmerged = false; - return; - } - - BLOCKTYPE BlockIn; - int RelX = (int)floor(m_LastPosX) - a_Chunk.GetPosX() * cChunkDef::Width; - int RelZ = (int)floor(m_LastPosZ) - a_Chunk.GetPosZ() * cChunkDef::Width; - - // Check if the player is swimming: - // Use Unbounded, because we're being called *after* processing super::Tick(), which could have changed our chunk - if (!a_Chunk.UnboundedRelGetBlockType(RelX, RelY, RelZ, BlockIn)) - { - // This sometimes happens on Linux machines - // Ref.: http://forum.mc-server.org/showthread.php?tid=1244 - LOGD("SetSwimState failure: RelX = %d, RelZ = %d, LastPos = {%.02f, %.02f}, Pos = %.02f, %.02f}", - RelX, RelY, m_LastPosX, m_LastPosZ, GetPosX(), GetPosZ() - ); - m_IsSwimming = false; - m_IsSubmerged = false; - return; - } - m_IsSwimming = IsBlockWater(BlockIn); - - // Check if the player is submerged: - VERIFY(a_Chunk.UnboundedRelGetBlockType(RelX, RelY + 1, RelZ, BlockIn)); - m_IsSubmerged = IsBlockWater(BlockIn); -} - - - - - -void cPlayer::HandleAir(void) -{ - // Ref.: http://www.minecraftwiki.net/wiki/Chunk_format - // see if the player is /submerged/ water (block above is water) - // Get the type of block the player's standing in: - - if (IsSubmerged()) - { - // either reduce air level or damage player - if (m_AirLevel < 1) - { - if (m_AirTickTimer < 1) - { - // damage player - TakeDamage(dtDrowning, NULL, 1, 1, 0); - // reset timer - m_AirTickTimer = DROWNING_TICKS; - } - else - { - m_AirTickTimer -= 1; - } - } - else - { - // reduce air supply - m_AirLevel -= 1; - } - } - else - { - // set the air back to maximum - m_AirLevel = MAX_AIR_LEVEL; - m_AirTickTimer = DROWNING_TICKS; - } -} - - - - - void cPlayer::HandleFood(void) { // Ref.: http://www.minecraftwiki.net/wiki/Hunger diff --git a/src/Entities/Player.h b/src/Entities/Player.h index bf3ca08e8..50f7560d6 100644 --- a/src/Entities/Player.h +++ b/src/Entities/Player.h @@ -13,6 +13,7 @@ class cGroup; class cWindow; class cClientHandle; +class cTeam; @@ -30,8 +31,6 @@ public: MAX_HEALTH = 20, MAX_FOOD_LEVEL = 20, EATING_TICKS = 30, ///< Number of ticks it takes to eat an item - MAX_AIR_LEVEL = 300, - DROWNING_TICKS = 10, //number of ticks per heart of damage } ; // tolua_end @@ -153,6 +152,15 @@ public: AString GetIP(void) const { return m_IP; } // tolua_export + /// Returns the associated team, NULL if none + cTeam * GetTeam(void) { return m_Team; } // tolua_export + + /// Sets the player team, NULL if none + void SetTeam(cTeam * a_Team); + + /// Forces the player to query the scoreboard for his team + cTeam * UpdateTeam(void); + // tolua_end void SetIP(const AString & a_IP); @@ -214,7 +222,14 @@ public: /// Returns the full color code to use for this player, based on their primary group or set in m_Color AString GetColor(void) const; - void TossItem(bool a_bDraggingItem, char a_Amount = 1, short a_CreateType = 0, short a_CreateHealth = 0); + /** tosses the item in the selected hotbar slot */ + void TossEquippedItem(char a_Amount = 1); + + /** tosses the item held in hand (when in UI windows) */ + void TossHeldItem(char a_Amount = 1); + + /** tosses a pickup newly created from a_Item */ + void TossPickup(const cItem & a_Item); /// Heals the player by the specified amount of HPs (positive only); sends health update void Heal(int a_Health); @@ -224,8 +239,6 @@ public: int GetFoodTickTimer (void) const { return m_FoodTickTimer; } double GetFoodExhaustionLevel (void) const { return m_FoodExhaustionLevel; } int GetFoodPoisonedTicksRemaining(void) const { return m_FoodPoisonedTicksRemaining; } - - int GetAirLevel (void) const { return m_AirLevel; } /// Returns true if the player is satiated, i. e. their foodlevel is at the max and they cannot eat anymore bool IsSatiated(void) const { return (m_FoodLevel >= MAX_FOOD_LEVEL); } @@ -336,12 +349,6 @@ public: /// If true the player can fly even when he's not in creative. void SetCanFly(bool a_CanFly); - /// Returns whether the player is swimming or not - virtual bool IsSwimming(void) const{ return m_IsSwimming; } - - /// Return whether the player is under water or not - virtual bool IsSubmerged(void) const{ return m_IsSubmerged; } - /// Returns wheter the player can fly or not. virtual bool CanFly(void) const { return m_CanFly; } // tolua_end @@ -372,12 +379,6 @@ protected: XP_TO_LEVEL30 = 825 } ; - /// Player's air level (for swimming) - int m_AirLevel; - - /// used to time ticks between damage taken via drowning/suffocation - int m_AirTickTimer; - bool m_bVisible; // Food-related variables: @@ -414,7 +415,7 @@ protected: float m_LastBlockActionTime; int m_LastBlockActionCnt; eGameMode m_GameMode; - std::string m_IP; + AString m_IP; /// The item being dragged by the cursor while in a UI window cItem m_DraggingItem; @@ -456,6 +457,8 @@ protected: int m_FloaterID; + cTeam* m_Team; + void ResolvePermissions(void); @@ -463,7 +466,7 @@ protected: virtual void Destroyed(void); - /// Filters out damage for creative mode + /// Filters out damage for creative mode/friendly fire virtual void DoTakeDamage(TakeDamageInfo & TDI) override; /// Called in each tick to handle food-related processing @@ -471,12 +474,6 @@ protected: /// Called in each tick if the player is fishing to make sure the floater dissapears when the player doesn't have a fishing rod as equipped item. void HandleFloater(void); - - /// Called in each tick to handle air-related processing i.e. drowning - void HandleAir(); - - /// Called once per tick to set IsSwimming and IsSubmerged - void SetSwimState(cChunk & a_Chunk); /// Adds food exhaustion based on the difference between Pos and LastPos, sprinting status and swimming (in water block) void ApplyFoodExhaustionFromMovement(); diff --git a/src/Entities/ProjectileEntity.cpp b/src/Entities/ProjectileEntity.cpp index 12ce9a303..bffa790a3 100644 --- a/src/Entities/ProjectileEntity.cpp +++ b/src/Entities/ProjectileEntity.cpp @@ -679,7 +679,8 @@ super(pkExpBottle, a_Creator, a_X, a_Y, a_Z, 0.25, 0.25) void cExpBottleEntity::OnHitSolidBlock(const Vector3d & a_HitPos, char a_HitFace) { - // TODO: Spawn experience orbs + // Spawn an experience orb with a reward between 3 and 11. + m_World->SpawnExperienceOrb(GetPosX(), GetPosY(), GetPosZ(), 3 + m_World->GetTickRandomNumber(8)); Destroy(); } @@ -710,8 +711,6 @@ void cFireworkEntity::OnHitSolidBlock(const Vector3d & a_HitPos, char a_HitFace) SetSpeed(0, 0, 0); SetPosition(GetPosX(), GetPosY() - 0.5, GetPosZ()); - std::cout << a_HitPos.x << " " << a_HitPos.y << " " << a_HitPos.z << std::endl; - m_IsInGround = true; BroadcastMovementUpdate(); diff --git a/src/ForEachChunkProvider.h b/src/ForEachChunkProvider.h new file mode 100644 index 000000000..70cd2196a --- /dev/null +++ b/src/ForEachChunkProvider.h @@ -0,0 +1,14 @@ + +#pragma once + +class cChunkDataCallback; + +class cBlockArea; + +class cForEachChunkProvider +{ +public: + virtual bool ForEachChunkInRect(int a_MinChunkX, int a_MaxChunkX, int a_MinChunkZ, int a_MaxChunkZ, cChunkDataCallback & a_Callback) = 0; + + virtual bool WriteBlockArea(cBlockArea & a_Area, int a_MinBlockX, int a_MinBlockY, int a_MinBlockZ, int a_DataTypes) = 0; +}; diff --git a/src/Generating/CMakeLists.txt b/src/Generating/CMakeLists.txt index e1ba299fc..793f48890 100644 --- a/src/Generating/CMakeLists.txt +++ b/src/Generating/CMakeLists.txt @@ -9,3 +9,5 @@ file(GLOB SOURCE ) add_library(Generating ${SOURCE}) + +target_link_libraries(Generating OSSupport iniFile) diff --git a/src/Generating/ChunkDesc.cpp b/src/Generating/ChunkDesc.cpp index af1a8a6c7..87566aa78 100644 --- a/src/Generating/ChunkDesc.cpp +++ b/src/Generating/ChunkDesc.cpp @@ -562,6 +562,31 @@ cBlockEntity * cChunkDesc::GetBlockEntity(int a_RelX, int a_RelY, int a_RelZ) +void cChunkDesc::UpdateHeightmap(void) +{ + for (int x = 0; x < cChunkDef::Width; x++) + { + for (int z = 0; z < cChunkDef::Width; z++) + { + int Height = 0; + for (int y = cChunkDef::Height - 1; y > 0; y--) + { + BLOCKTYPE BlockType = GetBlockType(x, y, z); + if (BlockType != E_BLOCK_AIR) + { + Height = y; + break; + } + } // for y + SetHeight(x, z, Height); + } // for z + } // for x +} + + + + + void cChunkDesc::CompressBlockMetas(cChunkDef::BlockNibbles & a_DestMetas) { const NIBBLETYPE * AreaMetas = m_BlockArea.GetBlockMetas(); diff --git a/src/Generating/ChunkDesc.h b/src/Generating/ChunkDesc.h index e130c463f..e258383d5 100644 --- a/src/Generating/ChunkDesc.h +++ b/src/Generating/ChunkDesc.h @@ -30,7 +30,7 @@ class cChunkDesc public: // tolua_end - /// Uncompressed block metas, 1 meta per byte + /** Uncompressed block metas, 1 meta per byte */ typedef NIBBLETYPE BlockNibbleBytes[cChunkDef::NumBlocks]; cChunkDesc(int a_ChunkX, int a_ChunkZ); @@ -56,6 +56,8 @@ public: void SetBiome(int a_RelX, int a_RelZ, int a_BiomeID); EMCSBiome GetBiome(int a_RelX, int a_RelZ); + // These operate on the heightmap, so they could get out of sync with the data + // Use UpdateHeightmap() to re-sync void SetHeight(int a_RelX, int a_RelZ, int a_Height); int GetHeight(int a_RelX, int a_RelZ); @@ -71,16 +73,16 @@ public: void SetUseDefaultFinish(bool a_bUseDefaultFinish); bool IsUsingDefaultFinish(void) const; - /// Writes the block area into the chunk, with its origin set at the specified relative coords. Area's data overwrite everything in the chunk. + /** Writes the block area into the chunk, with its origin set at the specified relative coords. Area's data overwrite everything in the chunk. */ void WriteBlockArea(const cBlockArea & a_BlockArea, int a_RelX, int a_RelY, int a_RelZ, cBlockArea::eMergeStrategy a_MergeStrategy = cBlockArea::msOverwrite); - /// Reads an area from the chunk into a cBlockArea, blocktypes and blockmetas + /** Reads an area from the chunk into a cBlockArea, blocktypes and blockmetas */ void ReadBlockArea(cBlockArea & a_Dest, int a_MinRelX, int a_MaxRelX, int a_MinRelY, int a_MaxRelY, int a_MinRelZ, int a_MaxRelZ); - /// Returns the maximum height value in the heightmap + /** Returns the maximum height value in the heightmap */ HEIGHTTYPE GetMaxHeight(void) const; - /// Fills the relative cuboid with specified block; allows cuboid out of range of this chunk + /** Fills the relative cuboid with specified block; allows cuboid out of range of this chunk */ void FillRelCuboid( int a_MinX, int a_MaxX, int a_MinY, int a_MaxY, @@ -88,7 +90,7 @@ public: BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta ); - /// Fills the relative cuboid with specified block; allows cuboid out of range of this chunk + /** Fills the relative cuboid with specified block; allows cuboid out of range of this chunk */ void FillRelCuboid(const cCuboid & a_RelCuboid, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta) { FillRelCuboid( @@ -99,7 +101,7 @@ public: ); } - /// Replaces the specified src blocks in the cuboid by the dst blocks; allows cuboid out of range of this chunk + /** Replaces the specified src blocks in the cuboid by the dst blocks; allows cuboid out of range of this chunk */ void ReplaceRelCuboid( int a_MinX, int a_MaxX, int a_MinY, int a_MaxY, @@ -108,7 +110,7 @@ public: BLOCKTYPE a_DstType, NIBBLETYPE a_DstMeta ); - /// Replaces the specified src blocks in the cuboid by the dst blocks; allows cuboid out of range of this chunk + /** Replaces the specified src blocks in the cuboid by the dst blocks; allows cuboid out of range of this chunk */ void ReplaceRelCuboid( const cCuboid & a_RelCuboid, BLOCKTYPE a_SrcType, NIBBLETYPE a_SrcMeta, @@ -124,7 +126,7 @@ public: ); } - /// Replaces the blocks in the cuboid by the dst blocks if they are considered non-floor (air, water); allows cuboid out of range of this chunk + /** Replaces the blocks in the cuboid by the dst blocks if they are considered non-floor (air, water); allows cuboid out of range of this chunk */ void FloorRelCuboid( int a_MinX, int a_MaxX, int a_MinY, int a_MaxY, @@ -132,7 +134,7 @@ public: BLOCKTYPE a_DstType, NIBBLETYPE a_DstMeta ); - /// Replaces the blocks in the cuboid by the dst blocks if they are considered non-floor (air, water); allows cuboid out of range of this chunk + /** Replaces the blocks in the cuboid by the dst blocks if they are considered non-floor (air, water); allows cuboid out of range of this chunk */ void FloorRelCuboid( const cCuboid & a_RelCuboid, BLOCKTYPE a_DstType, NIBBLETYPE a_DstMeta @@ -146,7 +148,7 @@ public: ); } - /// Fills the relative cuboid with specified block with a random chance; allows cuboid out of range of this chunk + /** Fills the relative cuboid with specified block with a random chance; allows cuboid out of range of this chunk */ void RandomFillRelCuboid( int a_MinX, int a_MaxX, int a_MinY, int a_MaxY, @@ -155,7 +157,7 @@ public: int a_RandomSeed, int a_ChanceOutOf10k ); - /// Fills the relative cuboid with specified block with a random chance; allows cuboid out of range of this chunk + /** Fills the relative cuboid with specified block with a random chance; allows cuboid out of range of this chunk */ void RandomFillRelCuboid( const cCuboid & a_RelCuboid, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta, int a_RandomSeed, int a_ChanceOutOf10k @@ -170,11 +172,15 @@ public: ); } - /// Returns the block entity at the specified coords. - /// If there is no block entity at those coords, tries to create one, based on the block type - /// If the blocktype doesn't support a block entity, returns NULL. + /** Returns the block entity at the specified coords. + If there is no block entity at those coords, tries to create one, based on the block type + If the blocktype doesn't support a block entity, returns NULL. */ cBlockEntity * GetBlockEntity(int a_RelX, int a_RelY, int a_RelZ); + /** Updates the heightmap to match the current contents. + Useful for plugins when writing custom block areas into the chunk */ + void UpdateHeightmap(void); + // tolua_end // Accessors used by cChunkGenerator::Generator descendants: @@ -187,11 +193,11 @@ public: inline cEntityList & GetEntities (void) { return m_Entities; } inline cBlockEntityList & GetBlockEntities (void) { return m_BlockEntities; } - /// Compresses the metas from the BlockArea format (1 meta per byte) into regular format (2 metas per byte) + /** Compresses the metas from the BlockArea format (1 meta per byte) into regular format (2 metas per byte) */ void CompressBlockMetas(cChunkDef::BlockNibbles & a_DestMetas); #ifdef _DEBUG - /// Verifies that the heightmap corresponds to blocktype contents; if not, asserts on that column + /** Verifies that the heightmap corresponds to blocktype contents; if not, asserts on that column */ void VerifyHeightmap(void); #endif // _DEBUG diff --git a/src/Generating/ChunkGenerator.cpp b/src/Generating/ChunkGenerator.cpp index baa5b76b8..ef38f1399 100644 --- a/src/Generating/ChunkGenerator.cpp +++ b/src/Generating/ChunkGenerator.cpp @@ -201,7 +201,7 @@ void cChunkGenerator::Execute(void) while (!m_ShouldTerminate) { cCSLock Lock(m_CS); - while (m_Queue.size() == 0) + while (m_Queue.empty()) { if ((NumChunksGenerated > 16) && (clock() - LastReportTick > CLOCKS_PER_SEC)) { @@ -221,6 +221,13 @@ void cChunkGenerator::Execute(void) LastReportTick = clock(); } + if (m_Queue.empty()) + { + // Sometimes the queue remains empty + // If so, we can't do any front() operations on it! + continue; + } + cChunkCoords coords = m_Queue.front(); // Get next coord from queue m_Queue.erase( m_Queue.begin() ); // Remove coordinate from queue bool SkipEnabled = (m_Queue.size() > QUEUE_SKIP_LIMIT); diff --git a/src/Globals.h b/src/Globals.h index d2080b8eb..7e53da80e 100644 --- a/src/Globals.h +++ b/src/Globals.h @@ -91,6 +91,9 @@ typedef unsigned long long UInt64; typedef unsigned int UInt32; typedef unsigned short UInt16; +typedef unsigned char Byte; + + diff --git a/src/Items/ItemLighter.h b/src/Items/ItemLighter.h index 4281a2d0c..8f3389d95 100644 --- a/src/Items/ItemLighter.h +++ b/src/Items/ItemLighter.h @@ -42,6 +42,10 @@ public: { // Light a fire next to/on top of the block if air: AddFaceDirection(a_BlockX, a_BlockY, a_BlockZ, a_BlockFace); + if ((a_BlockY < 0) || (a_BlockY >= cChunkDef::Height)) + { + break; + } if (a_World->GetBlock(a_BlockX, a_BlockY, a_BlockZ) == E_BLOCK_AIR) { a_World->SetBlock(a_BlockX, a_BlockY, a_BlockZ, E_BLOCK_FIRE, 0); diff --git a/src/LeakFinder.cpp b/src/LeakFinder.cpp index 9d7f185ba..42a5afe56 100644 --- a/src/LeakFinder.cpp +++ b/src/LeakFinder.cpp @@ -862,8 +862,10 @@ static int MyAllocHook(int nAllocType, void *pvData, { // RequestID was found size_t temp = g_CurrentMemUsage; - g_CurrentMemUsage -= nSize ; - g_pCRTTable->Remove(lRequest); + if (g_pCRTTable->Remove(lRequest)) + { + g_CurrentMemUsage -= nSize; + } if (g_CurrentMemUsage > temp) { printf("********************************************\n"); @@ -896,8 +898,11 @@ static int MyAllocHook(int nAllocType, void *pvData, // Try to find the RequestID in the Hash-Table, mark it that it was freed lReallocRequest = pHead->lRequest; size_t temp = g_CurrentMemUsage; - g_CurrentMemUsage -= pHead->nDataSize; bRet = g_pCRTTable->Remove(lReallocRequest); + if (bRet) + { + g_CurrentMemUsage -= pHead->nDataSize; + } if (g_CurrentMemUsage > temp) { printf("********************************************\n"); diff --git a/src/MobProximityCounter.cpp b/src/MobProximityCounter.cpp index 583a71579..6c44ea458 100644 --- a/src/MobProximityCounter.cpp +++ b/src/MobProximityCounter.cpp @@ -59,7 +59,7 @@ cMobProximityCounter::sIterablePair cMobProximityCounter::getMobWithinThosesDist { if (toReturn.m_Begin == m_DistanceToMonster.end()) { - if (a_DistanceMin == -1 || itr->first > a_DistanceMin) + if ((a_DistanceMin == 1) || (itr->first > a_DistanceMin)) { toReturn.m_Begin = itr; // this is the first one with distance > a_DistanceMin; } @@ -67,7 +67,7 @@ cMobProximityCounter::sIterablePair cMobProximityCounter::getMobWithinThosesDist if (toReturn.m_Begin != m_DistanceToMonster.end()) { - if (a_DistanceMax != -1 && itr->first > a_DistanceMax) + if ((a_DistanceMax != 1) && (itr->first > a_DistanceMax)) { toReturn.m_End = itr; // this is just after the last one with distance < a_DistanceMax // Note : if we are not going through this, it's ok, toReturn.m_End will be end(); diff --git a/src/Mobs/AggressiveMonster.cpp b/src/Mobs/AggressiveMonster.cpp index cc7e7da2b..f2f0c404c 100644 --- a/src/Mobs/AggressiveMonster.cpp +++ b/src/Mobs/AggressiveMonster.cpp @@ -4,7 +4,6 @@ #include "AggressiveMonster.h" #include "../World.h" -#include "../Vector3f.h" #include "../Entities/Player.h" #include "../MersenneTwister.h" @@ -13,8 +12,7 @@ cAggressiveMonster::cAggressiveMonster(const AString & a_ConfigName, eType a_MobType, const AString & a_SoundHurt, const AString & a_SoundDeath, double a_Width, double a_Height) : - super(a_ConfigName, a_MobType, a_SoundHurt, a_SoundDeath, a_Width, a_Height), - m_ChaseTime(999999) + super(a_ConfigName, a_MobType, a_SoundHurt, a_SoundDeath, a_Width, a_Height) { m_EMPersonality = AGGRESSIVE; } @@ -27,32 +25,23 @@ cAggressiveMonster::cAggressiveMonster(const AString & a_ConfigName, eType a_Mob void cAggressiveMonster::InStateChasing(float a_Dt) { super::InStateChasing(a_Dt); - m_ChaseTime += a_Dt; + if (m_Target != NULL) { if (m_Target->IsPlayer()) { - cPlayer * Player = (cPlayer *) m_Target; - if (Player->IsGameModeCreative()) + if (((cPlayer *)m_Target)->IsGameModeCreative()) { m_EMState = IDLE; return; } } - Vector3f Pos = Vector3f( GetPosition() ); - Vector3f Their = Vector3f( m_Target->GetPosition() ); - if ((Their - Pos).Length() <= m_AttackRange) + if (((float)m_FinalDestination.x != (float)m_Target->GetPosX()) || ((float)m_FinalDestination.z != (float)m_Target->GetPosZ())) { - Attack(a_Dt); + MoveToPosition(m_Target->GetPosition()); } - MoveToPosition(Their + Vector3f(0, 0.65f, 0)); } - else if (m_ChaseTime > 5.f) - { - m_ChaseTime = 0; - m_EMState = IDLE; - } } @@ -61,8 +50,11 @@ void cAggressiveMonster::InStateChasing(float a_Dt) void cAggressiveMonster::EventSeePlayer(cEntity * a_Entity) { - super::EventSeePlayer(a_Entity); - m_EMState = CHASING; + if (!((cPlayer *)a_Entity)->IsGameModeCreative()) + { + super::EventSeePlayer(a_Entity); + m_EMState = CHASING; + } } @@ -73,25 +65,32 @@ void cAggressiveMonster::Tick(float a_Dt, cChunk & a_Chunk) { super::Tick(a_Dt, a_Chunk); - m_SeePlayerInterval += a_Dt; - - if (m_SeePlayerInterval > 1) + if (m_EMState == CHASING) + { + CheckEventLostPlayer(); + } + else { - int rem = m_World->GetTickRandomNumber(3) + 1; // Check most of the time but miss occasionally + CheckEventSeePlayer(); + } +} - m_SeePlayerInterval = 0.0; - if (rem >= 2) - { - if (m_EMState == CHASING) - { - CheckEventLostPlayer(); - } - else - { - CheckEventSeePlayer(); - } - } + + + + +void cAggressiveMonster::Attack(float a_Dt) +{ + super::Attack(a_Dt); + + if ((m_Target != NULL) && (m_AttackInterval > 3.0)) + { + // 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/AggressiveMonster.h b/src/Mobs/AggressiveMonster.h index 5a0d93f3d..9cee4e7a7 100644 --- a/src/Mobs/AggressiveMonster.h +++ b/src/Mobs/AggressiveMonster.h @@ -13,16 +13,15 @@ class cAggressiveMonster : typedef cMonster super; public: + cAggressiveMonster(const AString & a_ConfigName, eType a_MobType, const AString & a_SoundHurt, const AString & a_SoundDeath, double a_Width, double a_Height); virtual void Tick (float a_Dt, cChunk & a_Chunk) override; virtual void InStateChasing(float a_Dt) override; virtual void EventSeePlayer(cEntity *) override; + virtual void Attack(float a_Dt) override; - -protected: - float m_ChaseTime; } ; diff --git a/src/Mobs/Chicken.cpp b/src/Mobs/Chicken.cpp index 087fd088a..fab92ce49 100644 --- a/src/Mobs/Chicken.cpp +++ b/src/Mobs/Chicken.cpp @@ -9,7 +9,6 @@ - cChicken::cChicken(void) : super("Chicken", mtChicken, "mob.chicken.hurt", "mob.chicken.hurt", 0.3, 0.4), m_EggDropTimer(0) diff --git a/src/Mobs/Chicken.h b/src/Mobs/Chicken.h index 979c4d8a0..a4c1d6b9e 100644 --- a/src/Mobs/Chicken.h +++ b/src/Mobs/Chicken.h @@ -19,8 +19,9 @@ public: virtual void GetDrops(cItems & a_Drops, cEntity * a_Killer = NULL) override; virtual void Tick(float a_Dt, cChunk & a_Chunk) override; -private: + virtual const cItem GetFollowedItem(void) const override { return cItem(E_ITEM_SEEDS); } +private: int m_EggDropTimer; } ; diff --git a/src/Mobs/Cow.cpp b/src/Mobs/Cow.cpp index 9eb74dac2..d8e905217 100644 --- a/src/Mobs/Cow.cpp +++ b/src/Mobs/Cow.cpp @@ -41,5 +41,3 @@ void cCow::OnRightClicked(cPlayer & a_Player) } } - - diff --git a/src/Mobs/Cow.h b/src/Mobs/Cow.h index 0391d4a31..973171ab5 100644 --- a/src/Mobs/Cow.h +++ b/src/Mobs/Cow.h @@ -19,6 +19,9 @@ public: virtual void GetDrops(cItems & a_Drops, cEntity * a_Killer = NULL) override; virtual void OnRightClicked(cPlayer & a_Player) override; + + virtual const cItem GetFollowedItem(void) const override { return cItem(E_ITEM_WHEAT); } + } ; diff --git a/src/Mobs/Creeper.cpp b/src/Mobs/Creeper.cpp index 4e11ae13e..2e1ece865 100644 --- a/src/Mobs/Creeper.cpp +++ b/src/Mobs/Creeper.cpp @@ -3,6 +3,7 @@ #include "Creeper.h" #include "../World.h" +#include "../Entities/ProjectileEntity.h" @@ -11,7 +12,8 @@ cCreeper::cCreeper(void) : super("Creeper", mtCreeper, "mob.creeper.say", "mob.creeper.say", 0.6, 1.8), m_bIsBlowing(false), - m_bIsCharged(false) + m_bIsCharged(false), + m_ExplodingTimer(0) { } @@ -19,11 +21,34 @@ cCreeper::cCreeper(void) : +void cCreeper::Tick(float a_Dt, cChunk & a_Chunk) +{ + super::Tick(a_Dt, a_Chunk); + + if (!ReachedFinalDestination()) + { + m_ExplodingTimer = 0; + m_bIsBlowing = false; + m_World->BroadcastEntityMetadata(*this); + } +} + + + + + void cCreeper::GetDrops(cItems & a_Drops, cEntity * a_Killer) { AddRandomDropItem(a_Drops, 0, 2, E_ITEM_GUNPOWDER); - // TODO Check if killed by a skeleton, then drop random music disk + if ((a_Killer != NULL) && (a_Killer->IsProjectile())) + { + if (((cMonster *)((cProjectileEntity *)a_Killer)->GetCreator())->GetMobType() == mtSkeleton) + { + // 12 music discs. TickRand starts from 0, so range = 11. Disk IDs start at 2256, so add that. There. + AddRandomDropItem(a_Drops, 1, 1, (short)m_World->GetTickRandomNumber(11) + 2256); + } + } } @@ -45,3 +70,27 @@ void cCreeper::DoTakeDamage(TakeDamageInfo & a_TDI) + +void cCreeper::Attack(float a_Dt) +{ + UNUSED(a_Dt); + + m_ExplodingTimer += 1; + + if (!m_bIsBlowing) + { + m_World->BroadcastSoundEffect("random.fuse", (int)GetPosX() * 8, (int)GetPosY() * 8, (int)GetPosZ() * 8, 1.f, (float)(0.75 + ((float)((GetUniqueID() * 23) % 32)) / 64)); + m_bIsBlowing = true; + m_World->BroadcastEntityMetadata(*this); + } + + if (m_ExplodingTimer == 20) + { + m_World->DoExplosionAt((m_bIsCharged ? 5 : 3), GetPosX(), GetPosY(), GetPosZ(), false, esMonster, this); + Destroy(); + } +} + + + + diff --git a/src/Mobs/Creeper.h b/src/Mobs/Creeper.h index c3d4edeae..0f71e5ad2 100644 --- a/src/Mobs/Creeper.h +++ b/src/Mobs/Creeper.h @@ -19,6 +19,8 @@ public: virtual void GetDrops(cItems & a_Drops, cEntity * a_Killer = NULL) override; virtual void DoTakeDamage(TakeDamageInfo & a_TDI) override; + virtual void Attack(float a_Dt) override; + virtual void Tick(float a_Dt, cChunk & a_Chunk) override; bool IsBlowing(void) const {return m_bIsBlowing; } bool IsCharged(void) const {return m_bIsCharged; } @@ -26,6 +28,7 @@ public: private: bool m_bIsBlowing, m_bIsCharged; + int m_ExplodingTimer; } ; diff --git a/src/Mobs/IronGolem.h b/src/Mobs/IronGolem.h index d49ff4cab..41c60438c 100644 --- a/src/Mobs/IronGolem.h +++ b/src/Mobs/IronGolem.h @@ -18,6 +18,10 @@ public: CLASS_PROTODEF(cIronGolem); virtual void GetDrops(cItems & a_Drops, cEntity * a_Killer = NULL) override; + + // Iron golems do not drown + virtual void HandleAir(void) override {} + virtual void SetSwimState(cChunk & a_Chunk) override {} } ; diff --git a/src/Mobs/Monster.cpp b/src/Mobs/Monster.cpp index 98b6c1d28..42c7d2899 100644 --- a/src/Mobs/Monster.cpp +++ b/src/Mobs/Monster.cpp @@ -8,14 +8,9 @@ #include "../World.h" #include "../Entities/Player.h" #include "../Entities/ExpOrb.h" -#include "../Defines.h" #include "../MonsterConfig.h" #include "../MersenneTwister.h" -#include "../Vector3f.h" -#include "../Vector3i.h" -#include "../Vector3d.h" -#include "../Tracer.h" #include "../Chunk.h" #include "../FastRandom.h" @@ -79,17 +74,15 @@ cMonster::cMonster(const AString & a_ConfigName, eType a_MobType, const AString , m_AttackRate(3) , m_IdleInterval(0) , m_bMovingToDestination(false) - , m_DestinationTime( 0 ) - , m_DestroyTimer( 0 ) - , m_Jump(0) + , m_DestroyTimer(0) , m_MobType(a_MobType) , m_SoundHurt(a_SoundHurt) , m_SoundDeath(a_SoundDeath) - , m_SeePlayerInterval (0) - , m_AttackDamage(1.0f) - , m_AttackRange(2.0f) + , m_AttackDamage(1) + , m_AttackRange(2) , m_AttackInterval(0) , m_BurnsInDaylight(false) + , m_LastGroundHeight(POSY_TOINT) { if (!a_ConfigName.empty()) { @@ -110,11 +103,105 @@ void cMonster::SpawnOn(cClientHandle & a_Client) -void cMonster::MoveToPosition( const Vector3f & a_Position ) +void cMonster::TickPathFinding() { + int PosX = (int)floor(GetPosX()); + int PosY = (int)floor(GetPosY()); + int PosZ = (int)floor(GetPosZ()); + + m_FinalDestination.y = (double)FindFirstNonAirBlockPosition(m_FinalDestination.x, m_FinalDestination.z); + + std::vector<Vector3d> m_PotentialCoordinates; + m_TraversedCoordinates.push_back(Vector3i(PosX, PosY, PosZ)); + + static const struct // Define which directions to try to move to + { + int x, z; + } gCrossCoords[] = + { + { 1, 0}, + {-1, 0}, + { 0, 1}, + { 0,-1}, + } ; + + for (size_t i = 0; i < ARRAYCOUNT(gCrossCoords); i++) + { + if ((gCrossCoords[i].x + PosX == PosX) && (gCrossCoords[i].z + PosZ == PosZ)) + { + continue; + } + + if (IsCoordinateInTraversedList(Vector3i(gCrossCoords[i].x + PosX, PosY, gCrossCoords[i].z + PosZ))) + { + continue; + } + + 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); + BLOCKTYPE BlockAtYM = m_World->GetBlock(gCrossCoords[i].x + PosX, PosY - 1, gCrossCoords[i].z + PosZ); + + if (!g_BlockIsSolid[BlockAtY] && !g_BlockIsSolid[BlockAtYP] && !IsBlockLava(BlockAtYM)) + { + m_PotentialCoordinates.push_back(Vector3d((gCrossCoords[i].x + PosX), PosY, gCrossCoords[i].z + PosZ)); + } + else if (g_BlockIsSolid[BlockAtY] && !g_BlockIsSolid[BlockAtYP] && !g_BlockIsSolid[BlockAtYPP] && !IsBlockLava(BlockAtYM)) + { + m_PotentialCoordinates.push_back(Vector3d((gCrossCoords[i].x + PosX), PosY + 1, gCrossCoords[i].z + PosZ)); + } + } + + if (!m_PotentialCoordinates.empty()) + { + Vector3f ShortestCoords = m_PotentialCoordinates.front(); + for (std::vector<Vector3d>::const_iterator itr = m_PotentialCoordinates.begin(); itr != m_PotentialCoordinates.end(); ++itr) + { + Vector3f Distance = m_FinalDestination - ShortestCoords; + Vector3f Distance2 = m_FinalDestination - *itr; + if (Distance.SqrLength() > Distance2.SqrLength()) + { + ShortestCoords = *itr; + } + } + + m_Destination = ShortestCoords; + m_Destination.z += 0.5f; + m_Destination.x += 0.5f; + } + else + { + FinishPathFinding(); + } +} + + + + + +void cMonster::MoveToPosition(const Vector3f & a_Position) +{ + FinishPathFinding(); + + m_FinalDestination = a_Position; m_bMovingToDestination = true; + TickPathFinding(); +} + + - m_Destination = a_Position; + +bool cMonster::IsCoordinateInTraversedList(Vector3i a_Coords) +{ + for (std::vector<Vector3i>::const_iterator itr = m_TraversedCoordinates.begin(); itr != m_TraversedCoordinates.end(); ++itr) + { + if (itr->Equals(a_Coords)) + { + return true; + } + } + + return false; } @@ -123,10 +210,24 @@ void cMonster::MoveToPosition( const Vector3f & a_Position ) bool cMonster::ReachedDestination() { - Vector3f Distance = (m_Destination) - GetPosition(); - if( Distance.SqrLength() < 2.f ) + if ((m_Destination - GetPosition()).Length() < 0.5f) + { return true; + } + + return false; +} + + + +bool cMonster::ReachedFinalDestination() +{ + if ((GetPosition() - m_FinalDestination).Length() <= m_AttackRange) + { + return true; + } + return false; } @@ -149,25 +250,35 @@ void cMonster::Tick(float a_Dt, cChunk & a_Chunk) return; } + if ((m_Target != NULL) && m_Target->IsDestroyed()) + m_Target = NULL; + // Burning in daylight HandleDaylightBurning(a_Chunk); - - HandlePhysics(a_Dt,a_Chunk); - BroadcastMovementUpdate(); a_Dt /= 1000; if (m_bMovingToDestination) { - Vector3f Pos( GetPosition() ); - Vector3f Distance = m_Destination - Pos; - if( !ReachedDestination() ) + if (m_bOnGround) + { + m_Destination.y = FindFirstNonAirBlockPosition(m_Destination.x, m_Destination.z); + + if (DoesPosYRequireJump(m_Destination.y)) + { + m_bOnGround = false; + AddPosY(1.5); // Jump!! + } + } + + Vector3f Distance = m_Destination - GetPosition(); + if(!ReachedDestination() && !ReachedFinalDestination()) // If we haven't reached any sort of destination, move { Distance.y = 0; Distance.Normalize(); Distance *= 3; - SetSpeedX( Distance.x ); - SetSpeedZ( Distance.z ); + SetSpeedX(Distance.x); + SetSpeedZ(Distance.z); if (m_EMState == ESCAPING) { //Runs Faster when escaping :D otherwise they just walk away @@ -177,40 +288,22 @@ void cMonster::Tick(float a_Dt, cChunk & a_Chunk) } else { - m_bMovingToDestination = false; - } - - if( GetSpeed().SqrLength() > 0.f ) - { - if( m_bOnGround ) + if (ReachedFinalDestination()) // If we have reached the ultimate, final destination, stop pathfinding and attack if appropriate + { + FinishPathFinding(); + } + else { - Vector3f NormSpeed = Vector3f(GetSpeed()).NormalizeCopy(); - Vector3f NextBlock = Vector3f( GetPosition() ) + NormSpeed; - int NextHeight; - if (!m_World->TryGetHeight((int)NextBlock.x, (int)NextBlock.z, NextHeight)) - { - // The chunk at NextBlock is not loaded - return; - } - if( NextHeight > (GetPosY() - 1.0) && (NextHeight - GetPosY()) < 2.5 ) - { - m_bOnGround = false; - SetSpeedY(5.f); // Jump!! - } + TickPathFinding(); // We have reached the next point in our path, calculate another point } } } - Vector3d Distance = m_Destination - GetPosition(); - if (Distance.SqrLength() > 0.1f) - { - double Rotation, Pitch; - Distance.Normalize(); - VectorToEuler( Distance.x, Distance.y, Distance.z, Rotation, Pitch ); - SetHeadYaw (Rotation); - SetYaw( Rotation ); - SetPitch( -Pitch ); - } + if (ReachedFinalDestination() && (m_Target != NULL)) + Attack(a_Dt); + + SetPitchAndYawFromDestination(); + HandleFalling(); switch (m_EMState) { @@ -219,21 +312,113 @@ void cMonster::Tick(float a_Dt, cChunk & a_Chunk) // 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 InStateChasing(a_Dt); break; - } - + } case ESCAPING: { InStateEscaping(a_Dt); break; } } // switch (m_EMState) + + BroadcastMovementUpdate(); +} + + + + +void cMonster::SetPitchAndYawFromDestination() +{ + Vector3d FinalDestination = m_FinalDestination; + if (m_Target != NULL) + { + if (m_Target->IsPlayer()) + { + FinalDestination.y = ((cPlayer *)m_Target)->GetStance(); + } + else + { + FinalDestination.y = 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); + } + + { + Vector3d BodyDistance = m_Destination - GetPosition(); + double Rotation, Pitch; + Distance.Normalize(); + VectorToEuler(BodyDistance.x, BodyDistance.y, BodyDistance.z, Rotation, Pitch); + SetYaw(Rotation); + } + } +} + + + + +void cMonster::HandleFalling() +{ + if (m_bOnGround) + { + int Damage = (m_LastGroundHeight - POSY_TOINT) - 3; + + if (Damage > 0) + { + TakeDamage(dtFalling, NULL, Damage, Damage, 0); + + // Fall particles + GetWorld()->BroadcastSoundParticleEffect(2006, POSX_TOINT, POSY_TOINT - 1, POSZ_TOINT, Damage /* Used as particle effect speed modifier */); + } + + m_LastGroundHeight = (int)floor(GetPosY()); + } +} + + + + +int cMonster::FindFirstNonAirBlockPosition(double a_PosX, double a_PosZ) +{ + int PosY = (int)floor(GetPosY()); + + if (PosY < 0) + PosY = 0; + else if (PosY > cChunkDef::Height) + PosY = cChunkDef::Height; + + if (!g_BlockIsSolid[m_World->GetBlock((int)floor(a_PosX), PosY, (int)floor(a_PosZ))]) + { + while (!g_BlockIsSolid[m_World->GetBlock((int)floor(a_PosX), PosY, (int)floor(a_PosZ))] && (PosY > 0)) + { + PosY--; + } + + return PosY + 1; + } + else + { + while (g_BlockIsSolid[m_World->GetBlock((int)floor(a_PosX), PosY, (int)floor(a_PosZ))] && (PosY < cChunkDef::Height)) + { + PosY++; + } + + return PosY; + } } @@ -244,11 +429,13 @@ void cMonster::Tick(float a_Dt, cChunk & a_Chunk) void cMonster::DoTakeDamage(TakeDamageInfo & a_TDI) { super::DoTakeDamage(a_TDI); - if((m_SoundHurt != "") && (m_Health > 0)) m_World->BroadcastSoundEffect(m_SoundHurt, (int)(GetPosX() * 8), (int)(GetPosY() * 8), (int)(GetPosZ() * 8), 1.0f, 0.8f); + + if((m_SoundHurt != "") && (m_Health > 0)) + m_World->BroadcastSoundEffect(m_SoundHurt, (int)(GetPosX() * 8), (int)(GetPosY() * 8), (int)(GetPosZ() * 8), 1.0f, 0.8f); + if (a_TDI.Attacker != NULL) { m_Target = a_TDI.Attacker; - AddReference(m_Target); } } @@ -330,55 +517,12 @@ void cMonster::KilledBy(cEntity * a_Killer) -//----State Logic - -const char *cMonster::GetState() -{ - switch(m_EMState) - { - case IDLE: return "Idle"; - case ATTACKING: return "Attacking"; - case CHASING: return "Chasing"; - default: return "Unknown"; - } -} - - - - - -// for debugging -void cMonster::SetState(const AString & a_State) -{ - if (a_State.compare("Idle") == 0) - { - m_EMState = IDLE; - } - else if (a_State.compare("Attacking") == 0) - { - m_EMState = ATTACKING; - } - else if (a_State.compare("Chasing") == 0) - { - m_EMState = CHASING; - } - else - { - LOGD("cMonster::SetState(): Invalid state"); - ASSERT(!"Invalid state"); - } -} - - - - - //Checks to see if EventSeePlayer should be fired //monster sez: Do I see the player void cMonster::CheckEventSeePlayer(void) { // TODO: Rewrite this to use cWorld's DoWithPlayers() - cPlayer * Closest = FindClosestPlayer(); + cPlayer * Closest = m_World->FindClosestPlayer(GetPosition(), (float)m_SightDistance, false); if (Closest != NULL) { @@ -391,14 +535,10 @@ void cMonster::CheckEventSeePlayer(void) void cMonster::CheckEventLostPlayer(void) -{ - Vector3f pos; - cTracer LineOfSight(GetWorld()); - +{ if (m_Target != NULL) { - pos = m_Target->GetPosition(); - if ((pos - GetPosition()).Length() > m_SightDistance || LineOfSight.Trace(GetPosition(),(pos - GetPosition()), (int)(pos - GetPosition()).Length())) + if ((m_Target->GetPosition() - GetPosition()).Length() > m_SightDistance) { EventLosePlayer(); } @@ -418,7 +558,6 @@ void cMonster::CheckEventLostPlayer(void) void cMonster::EventSeePlayer(cEntity * a_SeenPlayer) { m_Target = a_SeenPlayer; - AddReference(m_Target); } @@ -427,7 +566,6 @@ void cMonster::EventSeePlayer(cEntity * a_SeenPlayer) void cMonster::EventLosePlayer(void) { - Dereference(m_Target); m_Target = NULL; m_EMState = IDLE; } @@ -436,28 +574,35 @@ void cMonster::EventLosePlayer(void) -// What to do if in Idle State void cMonster::InStateIdle(float a_Dt) { + if (m_bMovingToDestination) + { + return; // Still getting there + } + m_IdleInterval += a_Dt; + if (m_IdleInterval > 1) { - // at this interval the results are predictable + // At this interval the results are predictable int rem = m_World->GetTickRandomNumber(6) + 1; - // LOGD("Moving: int: %3.3f rem: %i",idle_interval,rem); - m_IdleInterval -= 1; // So nothing gets dropped when the server hangs for a few seconds - Vector3f Dist; - Dist.x = (float)(m_World->GetTickRandomNumber(10) - 5); - Dist.z = (float)(m_World->GetTickRandomNumber(10) - 5); + m_IdleInterval -= 1; // So nothing gets dropped when the server hangs for a few seconds + + Vector3d Dist; + Dist.x = (double)m_World->GetTickRandomNumber(10) - 5; + Dist.z = (double)m_World->GetTickRandomNumber(10) - 5; + if ((Dist.SqrLength() > 2) && (rem >= 3)) { - m_Destination.x = (float)(GetPosX() + Dist.x); - m_Destination.z = (float)(GetPosZ() + Dist.z); - int PosY; - if (m_World->TryGetHeight((int)m_Destination.x, (int)m_Destination.z, PosY)) + Vector3d Destination(GetPosX() + Dist.x, 0, GetPosZ() + Dist.z); + + int NextHeight = FindFirstNonAirBlockPosition(Destination.x, Destination.z); + + if (IsNextYPosReachable(NextHeight)) { - m_Destination.y = (float)PosY + 1.2f; - MoveToPosition(m_Destination); + Destination.y = NextHeight; + MoveToPosition(Destination); } } } @@ -505,22 +650,6 @@ void cMonster::InStateEscaping(float a_Dt) void cMonster::Attack(float a_Dt) { m_AttackInterval += a_Dt * m_AttackRate; - if ((m_Target != NULL) && (m_AttackInterval > 3.0)) - { - // Setting this higher gives us more wiggle room for attackrate - m_AttackInterval = 0.0; - ((cPawn *)m_Target)->TakeDamage(*this); - } -} - - - - - -// Checks for Players close by and if they are visible return the closest -cPlayer * cMonster::FindClosestPlayer(void) -{ - return m_World->FindClosestPlayer(GetPosition(), m_SightDistance); } @@ -536,42 +665,6 @@ void cMonster::GetMonsterConfig(const AString & a_Name) -void cMonster::SetAttackRate(int ar) -{ - m_AttackRate = (float)ar; -} - - - - - -void cMonster::SetAttackRange(float ar) -{ - m_AttackRange = ar; -} - - - - - -void cMonster::SetAttackDamage(float ad) -{ - m_AttackDamage = ad; -} - - - - - -void cMonster::SetSightDistance(float sd) -{ - m_SightDistance = sd; -} - - - - - AString cMonster::MobTypeToString(cMonster::eType a_MobType) { // Mob types aren't sorted, so we need to search linearly: @@ -635,6 +728,8 @@ cMonster::eType cMonster::StringToMobType(const AString & a_Name) cMonster::eFamily cMonster::FamilyFromType(eType a_Type) { + // Passive-agressive mobs are counted in mob spawning code as passive + switch (a_Type) { case mtBat: return mfAmbient; @@ -699,7 +794,7 @@ cMonster * cMonster::NewMonsterFromType(cMonster::eType a_MobType) case mtMagmaCube: case mtSlime: { - toReturn = new cSlime (Random.NextInt(2) + 1); + toReturn = new cSlime(Random.NextInt(2) + 1); break; } case mtSkeleton: @@ -803,6 +898,13 @@ void cMonster::HandleDaylightBurning(cChunk & a_Chunk) int RelX = (int)floor(GetPosX()) - GetChunkX() * cChunkDef::Width; int RelZ = (int)floor(GetPosZ()) - GetChunkZ() * cChunkDef::Width; + + if (!a_Chunk.IsLightValid()) + { + m_World->QueueLightChunk(GetChunkX(), GetChunkZ()); + return; + } + if ( (a_Chunk.GetSkyLight(RelX, RelY, RelZ) == 15) && // In the daylight (a_Chunk.GetBlock(RelX, RelY, RelZ) != E_BLOCK_SOULSAND) && // Not on soulsand diff --git a/src/Mobs/Monster.h b/src/Mobs/Monster.h index dafb33574..1dd302cdc 100644 --- a/src/Mobs/Monster.h +++ b/src/Mobs/Monster.h @@ -10,7 +10,6 @@ -class Vector3f; class cClientHandle; class cWorld; @@ -74,8 +73,6 @@ public: enum MState{ATTACKING, IDLE, CHASING, ESCAPING} m_EMState; enum MPersonality{PASSIVE,AGGRESSIVE,COWARDLY} m_EMPersonality; - float m_SightDistance; - /** Creates the mob object. * If a_ConfigName is not empty, the configuration is loaded using GetMonsterConfig() * a_MobType is the type of the mob (also used in the protocol ( http://wiki.vg/Entities#Mobs , 2012_12_22)) @@ -100,14 +97,9 @@ public: eType GetMobType(void) const {return m_MobType; } eFamily GetMobFamily(void) const; // tolua_end - - - const char * GetState(); - void SetState(const AString & str); virtual void CheckEventSeePlayer(void); virtual void EventSeePlayer(cEntity * a_Player); - virtual cPlayer * FindClosestPlayer(); // non static is easier. also virtual so other mobs can implement their own searching algo /// Reads the monster configuration for the specified monster name and assigns it to this object. void GetMonsterConfig(const AString & a_Name); @@ -121,11 +113,11 @@ public: virtual void Attack(float a_Dt); - int GetAttackRate(){return (int)m_AttackRate;} - void SetAttackRate(int ar); - void SetAttackRange(float ar); - void SetAttackDamage(float ad); - void SetSightDistance(float sd); + int GetAttackRate() { return (int)m_AttackRate; } + void SetAttackRate(float a_AttackRate) { m_AttackRate = a_AttackRate; } + void SetAttackRange(int a_AttackRange) { m_AttackRange = a_AttackRange; } + void SetAttackDamage(int a_AttackDamage) { m_AttackDamage = a_AttackDamage; } + void SetSightDistance(int a_SightDistance) { m_SightDistance = a_SightDistance; } /// Sets whether the mob burns in daylight. Only evaluated at next burn-decision tick void SetBurnsInDaylight(bool a_BurnsInDaylight) { m_BurnsInDaylight = a_BurnsInDaylight; } @@ -159,34 +151,80 @@ public: protected: - cEntity * m_Target; - float m_AttackRate; - float m_IdleInterval; + /* ======= PATHFINDING ======= */ - Vector3f m_Destination; + /** A pointer to the entity this mobile is aiming to reach */ + cEntity * m_Target; + /** Coordinates of the next position that should be reached */ + Vector3d m_Destination; + /** 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; - bool m_bPassiveAggressive; + /** Finds the first 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 */ + 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 <= (int)floor(GetPosY())) || + DoesPosYRequireJump(a_PosY) + ); + } + /** Returns if a monster can reach a given height by jumping */ + inline bool DoesPosYRequireJump(int a_PosY) + { + return ((a_PosY > (int)floor(GetPosY())) && (a_PosY == (int)floor(GetPosY()) + 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 + 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 ========= */ - float m_DestinationTime; + virtual void HandleFalling(void); + int m_LastGroundHeight; + /* =========================== */ + + float m_IdleInterval; float m_DestroyTimer; - float m_Jump; eType m_MobType; AString m_SoundHurt; AString m_SoundDeath; - float m_SeePlayerInterval; - float m_AttackDamage; - float m_AttackRange; + float m_AttackRate; + int m_AttackDamage; + int m_AttackRange; float m_AttackInterval; + int m_SightDistance; + void HandleDaylightBurning(cChunk & a_Chunk); bool m_BurnsInDaylight; - void AddRandomDropItem(cItems & a_Drops, unsigned int a_Min, unsigned int a_Max, short a_Item, short a_ItemHealth = 0); - - void HandleDaylightBurning(cChunk & a_Chunk); + void AddRandomDropItem(cItems & a_Drops, unsigned int a_Min, unsigned int a_Max, short a_Item, short a_ItemHealth = 0); } ; // tolua_export diff --git a/src/Mobs/Mooshroom.cpp b/src/Mobs/Mooshroom.cpp index 940e2db44..88101cd83 100644 --- a/src/Mobs/Mooshroom.cpp +++ b/src/Mobs/Mooshroom.cpp @@ -6,7 +6,6 @@ - // TODO: Milk Cow @@ -30,4 +29,3 @@ void cMooshroom::GetDrops(cItems & a_Drops, cEntity * a_Killer) - diff --git a/src/Mobs/Mooshroom.h b/src/Mobs/Mooshroom.h index 73f6348b6..c94301098 100644 --- a/src/Mobs/Mooshroom.h +++ b/src/Mobs/Mooshroom.h @@ -18,6 +18,8 @@ public: CLASS_PROTODEF(cMooshroom); virtual void GetDrops(cItems & a_Drops, cEntity * a_Killer = NULL) override; + + virtual const cItem GetFollowedItem(void) const override { return cItem(E_ITEM_WHEAT); } } ; diff --git a/src/Mobs/PassiveAggressiveMonster.cpp b/src/Mobs/PassiveAggressiveMonster.cpp index 28de65905..4b45f9a2a 100644 --- a/src/Mobs/PassiveAggressiveMonster.cpp +++ b/src/Mobs/PassiveAggressiveMonster.cpp @@ -25,8 +25,7 @@ void cPassiveAggressiveMonster::DoTakeDamage(TakeDamageInfo & a_TDI) if ((m_Target != NULL) && (m_Target->IsPlayer())) { - cPlayer * Player = (cPlayer *) m_Target; - if (Player->GetGameMode() != 1) + if (!((cPlayer *)m_Target)->IsGameModeCreative()) { m_EMState = CHASING; } diff --git a/src/Mobs/PassiveMonster.cpp b/src/Mobs/PassiveMonster.cpp index 91ceb5a53..904cd63cc 100644 --- a/src/Mobs/PassiveMonster.cpp +++ b/src/Mobs/PassiveMonster.cpp @@ -2,9 +2,8 @@ #include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules #include "PassiveMonster.h" -#include "../MersenneTwister.h" #include "../World.h" - +#include "../Entities/Player.h" @@ -36,19 +35,22 @@ void cPassiveMonster::Tick(float a_Dt, cChunk & a_Chunk) { super::Tick(a_Dt, a_Chunk); - m_SeePlayerInterval += a_Dt; - - if (m_SeePlayerInterval > 1) // Check every second + if (m_EMState == ESCAPING) { - int rem = m_World->GetTickRandomNumber(3) + 1; // Check most of the time but miss occasionally - - m_SeePlayerInterval = 0.0; - if (rem >= 2) + CheckEventLostPlayer(); + } + cItem FollowedItem = GetFollowedItem(); + if (FollowedItem.IsEmpty()) + { + return; + } + cPlayer * a_Closest_Player = m_World->FindClosestPlayer(GetPosition(), (float)m_SightDistance); + if (a_Closest_Player != NULL) + { + if (a_Closest_Player->GetEquippedItem().IsEqual(FollowedItem)) { - if (m_EMState == ESCAPING) - { - CheckEventLostPlayer(); - } + Vector3d PlayerPos = a_Closest_Player->GetPosition(); + MoveToPosition(PlayerPos); } } } diff --git a/src/Mobs/PassiveMonster.h b/src/Mobs/PassiveMonster.h index 14a6be6b1..0b3c155da 100644 --- a/src/Mobs/PassiveMonster.h +++ b/src/Mobs/PassiveMonster.h @@ -19,6 +19,9 @@ public: /// When hit by someone, run away virtual void DoTakeDamage(TakeDamageInfo & a_TDI) override; + /** Returns the item that the animal of this class follows when a player holds it in hand + Return an empty item not to follow (default). */ + virtual const cItem GetFollowedItem(void) const { return cItem(); } } ; diff --git a/src/Mobs/Pig.cpp b/src/Mobs/Pig.cpp index 0871a38a9..d8f3dda37 100644 --- a/src/Mobs/Pig.cpp +++ b/src/Mobs/Pig.cpp @@ -73,5 +73,3 @@ void cPig::OnRightClicked(cPlayer & a_Player) - - diff --git a/src/Mobs/Pig.h b/src/Mobs/Pig.h index 4fd0d8db8..d434324c1 100644 --- a/src/Mobs/Pig.h +++ b/src/Mobs/Pig.h @@ -19,6 +19,9 @@ public: virtual void GetDrops(cItems & a_Drops, cEntity * a_Killer = NULL) override; virtual void OnRightClicked(cPlayer & a_Player) override; + + virtual const cItem GetFollowedItem(void) const override { return cItem(E_ITEM_CARROT); } + bool IsSaddled(void) const { return m_bIsSaddled; } private: diff --git a/src/Mobs/Sheep.cpp b/src/Mobs/Sheep.cpp index bda4ccff8..4761103e5 100644 --- a/src/Mobs/Sheep.cpp +++ b/src/Mobs/Sheep.cpp @@ -13,7 +13,8 @@ cSheep::cSheep(int a_Color) : super("Sheep", mtSheep, "mob.sheep.say", "mob.sheep.say", 0.6, 1.3), m_IsSheared(false), - m_WoolColor(a_Color) + m_WoolColor(a_Color), + m_TimeToStopEating(-1) { } @@ -60,3 +61,40 @@ void cSheep::OnRightClicked(cPlayer & a_Player) m_World->BroadcastEntityMetadata(*this); } } + + + + + +void cSheep::Tick(float a_Dt, cChunk & a_Chunk) +{ + // The sheep should not move when he's eating so only handle the physics. + if (m_TimeToStopEating > 0) + { + HandlePhysics(a_Dt, a_Chunk); + m_TimeToStopEating--; + if (m_TimeToStopEating == 0) + { + if (m_World->GetBlock((int) GetPosX(), (int) GetPosY() - 1, (int) GetPosZ()) == E_BLOCK_GRASS) + { + // The sheep ate the grass so we change it to dirt. + m_World->SetBlock((int) GetPosX(), (int) GetPosY() - 1, (int) GetPosZ(), E_BLOCK_DIRT, 0); + m_IsSheared = false; + m_World->BroadcastEntityMetadata(*this); + } + } + } + else + { + super::Tick(a_Dt, a_Chunk); + if (m_World->GetTickRandomNumber(600) == 1) + { + if (m_World->GetBlock((int) GetPosX(), (int) GetPosY() - 1, (int) GetPosZ()) == E_BLOCK_GRASS) + { + m_World->BroadcastEntityStatus(*this, 10); + m_TimeToStopEating = 40; + } + } + } +} + diff --git a/src/Mobs/Sheep.h b/src/Mobs/Sheep.h index 8293a2c05..402e8e61c 100644 --- a/src/Mobs/Sheep.h +++ b/src/Mobs/Sheep.h @@ -19,6 +19,10 @@ public: virtual void GetDrops(cItems & a_Drops, cEntity * a_Killer = NULL) override; virtual void OnRightClicked(cPlayer & a_Player) override; + virtual void Tick(float a_Dt, cChunk & a_Chunk) override; + + virtual const cItem GetFollowedItem(void) const override { return cItem(E_ITEM_WHEAT); } + bool IsSheared(void) const { return m_IsSheared; } int GetFurColor(void) const { return m_WoolColor; } @@ -26,6 +30,7 @@ private: bool m_IsSheared; int m_WoolColor; + int m_TimeToStopEating; } ; diff --git a/src/Mobs/Skeleton.cpp b/src/Mobs/Skeleton.cpp index 509c2191e..4c8e78988 100644 --- a/src/Mobs/Skeleton.cpp +++ b/src/Mobs/Skeleton.cpp @@ -30,15 +30,18 @@ void cSkeleton::GetDrops(cItems & a_Drops, cEntity * a_Killer) void cSkeleton::MoveToPosition(const Vector3f & a_Position) { - m_Destination = a_Position; - // If the destination is in the sun and if it is not night AND the skeleton isn't on fire then block the movement. - if (!IsOnFire() && m_World->GetTimeOfDay() < 13187 && m_World->GetBlockSkyLight((int) a_Position.x, (int) a_Position.y, (int) a_Position.z) == 15) + if ( + !IsOnFire() && + (m_World->GetTimeOfDay() < 13187) && + (m_World->GetBlockSkyLight((int) a_Position.x, (int) a_Position.y, (int) a_Position.z) == 15) + ) { m_bMovingToDestination = false; return; } - m_bMovingToDestination = true; + + super::MoveToPosition(a_Position); } diff --git a/src/Mobs/Squid.cpp b/src/Mobs/Squid.cpp index a311108ae..5a27762ff 100644 --- a/src/Mobs/Squid.cpp +++ b/src/Mobs/Squid.cpp @@ -43,7 +43,8 @@ void cSquid::Tick(float a_Dt, cChunk & a_Chunk) } int RelX = (int)floor(Pos.x) - a_Chunk.GetPosX() * cChunkDef::Width; int RelZ = (int)floor(Pos.z) - a_Chunk.GetPosZ() * cChunkDef::Width; - if (!IsBlockWater(a_Chunk.GetBlock(RelX, RelY, RelZ)) && !IsOnFire()) + BLOCKTYPE BlockType; + if (a_Chunk.UnboundedRelGetBlockType(RelX, RelY, RelZ, BlockType) && !IsBlockWater(BlockType) && !IsOnFire()) { // Burn for 10 ticks, then decide again StartBurning(10); diff --git a/src/Mobs/Squid.h b/src/Mobs/Squid.h index ad299b95c..a9dba8b70 100644 --- a/src/Mobs/Squid.h +++ b/src/Mobs/Squid.h @@ -21,6 +21,9 @@ public: virtual void GetDrops(cItems & a_Drops, cEntity * a_Killer = NULL) override; + // Squids do not drown (or float) + virtual void HandleAir(void) override {} + virtual void SetSwimState(cChunk & a_Chunk) override {} } ; diff --git a/src/Mobs/Villager.cpp b/src/Mobs/Villager.cpp index 7f89fb6cc..44ec14295 100644 --- a/src/Mobs/Villager.cpp +++ b/src/Mobs/Villager.cpp @@ -3,6 +3,8 @@ #include "Villager.h" #include "../World.h" +#include "../BlockArea.h" +#include "../Blocks/BlockHandler.h" @@ -10,7 +12,9 @@ cVillager::cVillager(eVillagerType VillagerType) : super("Villager", mtVillager, "", "", 0.6, 1.8), - m_Type(VillagerType) + m_Type(VillagerType), + m_VillagerAction(false), + m_ActionCountDown(-1) { } @@ -21,7 +25,7 @@ cVillager::cVillager(eVillagerType VillagerType) : void cVillager::DoTakeDamage(TakeDamageInfo & a_TDI) { super::DoTakeDamage(a_TDI); - if (a_TDI.Attacker->IsPlayer()) + if ((a_TDI.Attacker != NULL) && a_TDI.Attacker->IsPlayer()) { if (m_World->GetTickRandomNumber(5) == 3) { @@ -33,3 +37,153 @@ void cVillager::DoTakeDamage(TakeDamageInfo & a_TDI) + +void cVillager::Tick(float a_Dt, cChunk & a_Chunk) +{ + super::Tick(a_Dt, a_Chunk); + + if (m_ActionCountDown > -1) + { + m_ActionCountDown--; + if (m_ActionCountDown == 0) + { + switch (m_Type) + { + case vtFarmer: + { + HandleFarmerPlaceCrops(); + } + } + } + return; + } + + if (m_VillagerAction) + { + switch (m_Type) + { + case vtFarmer: + { + HandleFarmerTryHarvestCrops(); + } + } + m_VillagerAction = false; + return; + } + + // Don't always try to do a special action. Each tick has 1% to do a special action. + if (m_World->GetTickRandomNumber(99) != 0) + { + return; + } + + switch (m_Type) + { + case vtFarmer: + { + HandleFarmerPrepareFarmCrops(); + } + } +} + + + + +//////////////////////////////////////////////////////////////////////////////// +// Farmer functions. +void cVillager::HandleFarmerPrepareFarmCrops() +{ + if (!m_World->VillagersShouldHarvestCrops()) + { + return; + } + + cBlockArea Surrounding; + /// Read a 11x7x11 area. + Surrounding.Read( + m_World, + (int) GetPosX() - 5, + (int) GetPosX() + 5, + (int) GetPosY() - 3, + (int) GetPosY() + 3, + (int) GetPosZ() - 5, + (int) GetPosZ() + 5 + ); + + for (int I = 0; I < 5; I++) + { + for (int Y = 0; Y < 6; Y++) + { + // Pick random coordinates and check for crops. + int X = m_World->GetTickRandomNumber(11); + int Z = m_World->GetTickRandomNumber(11); + + // A villager can't farm this. + if (!IsBlockFarmable(Surrounding.GetRelBlockType(X, Y, Z))) + { + continue; + } + if (Surrounding.GetRelBlockMeta(X, Y, Z) != 0x7) + { + continue; + } + + m_VillagerAction = true; + m_CropsPos = Vector3i((int) GetPosX() + X - 5, (int) GetPosY() + Y - 3, (int) GetPosZ() + Z - 5); + MoveToPosition(Vector3f((float) (m_CropsPos.x + 0.5), (float) m_CropsPos.y, (float) (m_CropsPos.z + 0.5))); + return; + } // for Y loop. + } // Repeat the procces 5 times. +} + + + + + +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) + { + // 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); + if (IsBlockFarmable(CropBlock) && m_World->GetBlockMeta(m_CropsPos.x, m_CropsPos.y, m_CropsPos.z) == 0x7) + { + cBlockHandler * Handler = cBlockHandler::GetBlockHandler(CropBlock); + Handler->DropBlock(m_World, this, m_CropsPos.x, m_CropsPos.y, m_CropsPos.z); + m_World->SetBlock(m_CropsPos.x, m_CropsPos.y, m_CropsPos.z, E_BLOCK_AIR, 0); + m_ActionCountDown = 20; + } + } +} + + + + +void cVillager::HandleFarmerPlaceCrops() +{ + // Check if there is still farmland at the spot where the crops were. + if (m_World->GetBlock(m_CropsPos.x, m_CropsPos.y - 1, m_CropsPos.z) == E_BLOCK_FARMLAND) + { + m_World->SetBlock(m_CropsPos.x, m_CropsPos.y, m_CropsPos.z, E_BLOCK_CROPS, 0); + } +} + + + + + +bool cVillager::IsBlockFarmable(BLOCKTYPE a_BlockType) +{ + switch (a_BlockType) + { + case E_BLOCK_CROPS: + case E_BLOCK_POTATOES: + case E_BLOCK_CARROTS: + { + return true; + } + } + return false; +} + diff --git a/src/Mobs/Villager.h b/src/Mobs/Villager.h index 4cd9aaa8e..b99ae876f 100644 --- a/src/Mobs/Villager.h +++ b/src/Mobs/Villager.h @@ -29,12 +29,36 @@ public: CLASS_PROTODEF(cVillager); + // Override functions virtual void DoTakeDamage(TakeDamageInfo & a_TDI) override; - int GetVilType(void) const { return m_Type; } + virtual void Tick (float a_Dt, cChunk & a_Chunk) override; + + // cVillager functions + /** return true if the given blocktype are: crops, potatoes or carrots.*/ + bool IsBlockFarmable(BLOCKTYPE a_BlockType); + + ////////////////////////////////////////////////////////////////// + // Farmer functions + /** It searches in a 11x7x11 area for crops. If it found some it will navigate to them.*/ + void HandleFarmerPrepareFarmCrops(); + + /** Looks if the farmer has reached it's destination, and if it's still crops and the destination is closer then 2 blocks it will harvest them.*/ + void HandleFarmerTryHarvestCrops(); + + /** Replaces the crops he harvested.*/ + void HandleFarmerPlaceCrops(); + + // Get and set functions. + int GetVilType(void) const { return m_Type; } + Vector3i GetCropsPos(void) const { return m_CropsPos; } + bool DoesHaveActionActivated(void) const { return m_VillagerAction; } private: + int m_ActionCountDown; int m_Type; + bool m_VillagerAction; + Vector3i m_CropsPos; } ; diff --git a/src/Mobs/Wolf.cpp b/src/Mobs/Wolf.cpp index 3d4e97c80..c0c7892e3 100644 --- a/src/Mobs/Wolf.cpp +++ b/src/Mobs/Wolf.cpp @@ -37,6 +37,26 @@ void cWolf::DoTakeDamage(TakeDamageInfo & a_TDI) +void cWolf::Attack(float a_Dt) +{ + UNUSED(a_Dt); + + if ((m_Target != NULL) && (m_Target->IsPlayer())) + { + if (((cPlayer *)m_Target)->GetName() != m_OwnerName) + { + super::Attack(a_Dt); + } + } + else + { + super::Attack(a_Dt); + } +} + + + + void cWolf::OnRightClicked(cPlayer & a_Player) { @@ -55,10 +75,12 @@ void cWolf::OnRightClicked(cPlayer & a_Player) SetIsTame(true); SetOwner(a_Player.GetName()); m_World->BroadcastEntityStatus(*this, ENTITY_STATUS_WOLF_TAMED); + m_World->BroadcastParticleEffect("heart", (float) GetPosX(), (float) GetPosY(), (float) GetPosZ(), 0, 0, 0, 0, 5); } else { m_World->BroadcastEntityStatus(*this, ENTITY_STATUS_WOLF_TAMING); + m_World->BroadcastParticleEffect("smoke", (float) GetPosX(), (float) GetPosY(), (float) GetPosZ(), 0, 0, 0, 0, 5); } } } @@ -102,13 +124,14 @@ void cWolf::Tick(float a_Dt, cChunk & a_Chunk) { super::Tick(a_Dt, a_Chunk); } - + + // The wolf is sitting so don't move him at all. if (IsSitting()) { m_bMovingToDestination = false; } - cPlayer * a_Closest_Player = FindClosestPlayer(); + cPlayer * a_Closest_Player = m_World->FindClosestPlayer(GetPosition(), (float)m_SightDistance); if (a_Closest_Player != NULL) { switch (a_Closest_Player->GetEquippedItem().m_ItemType) @@ -125,10 +148,18 @@ void cWolf::Tick(float a_Dt, cChunk & a_Chunk) SetIsBegging(true); m_World->BroadcastEntityMetadata(*this); } - Vector3f a_NewDestination = a_Closest_Player->GetPosition(); - a_NewDestination.y = a_NewDestination.y + 1; // Look at the head of the player, not his feet. - m_Destination = Vector3f(a_NewDestination); - m_bMovingToDestination = false; + // Don't move to the player if the wolf is sitting. + if (IsSitting()) + { + m_bMovingToDestination = false; + } + else + { + m_bMovingToDestination = true; + } + Vector3d PlayerPos = a_Closest_Player->GetPosition(); + PlayerPos.y++; + m_FinalDestination = PlayerPos; break; } default: @@ -163,23 +194,30 @@ void cWolf::TickFollowPlayer() return false; } public: - Vector3f OwnerPos; + Vector3d OwnerPos; } Callback; if (m_World->DoWithPlayer(m_OwnerName, Callback)) { - // The player is present in the world, follow them: + // The player is present in the world, follow him: double Distance = (Callback.OwnerPos - GetPosition()).Length(); - if (Distance < 3) - { - m_bMovingToDestination = false; - } - else if ((Distance > 30) && (!IsSitting())) + if (Distance > 30) { - TeleportToCoords(Callback.OwnerPos.x, Callback.OwnerPos.y, Callback.OwnerPos.z); + if (!IsSitting()) + { + TeleportToCoords(Callback.OwnerPos.x, Callback.OwnerPos.y, Callback.OwnerPos.z); + } } else { - m_Destination = Callback.OwnerPos; + m_FinalDestination = Callback.OwnerPos; + if (IsSitting()) + { + m_bMovingToDestination = false; + } + else + { + m_bMovingToDestination = true; + } } } } diff --git a/src/Mobs/Wolf.h b/src/Mobs/Wolf.h index 040e2cf7a..9e5ad03c7 100644 --- a/src/Mobs/Wolf.h +++ b/src/Mobs/Wolf.h @@ -22,6 +22,7 @@ public: virtual void OnRightClicked(cPlayer & a_Player) override; virtual void Tick(float a_Dt, cChunk & a_Chunk) override; virtual void TickFollowPlayer(); + virtual void Attack(float a_Dt) override; // Get functions bool IsSitting (void) const { return m_IsSitting; } diff --git a/src/Mobs/Zombie.cpp b/src/Mobs/Zombie.cpp index a046fcc92..27e8ed5fb 100644 --- a/src/Mobs/Zombie.cpp +++ b/src/Mobs/Zombie.cpp @@ -34,15 +34,18 @@ void cZombie::GetDrops(cItems & a_Drops, cEntity * a_Killer) void cZombie::MoveToPosition(const Vector3f & a_Position) { - m_Destination = a_Position; - - // If the destination is in the sun and if it is not night AND the skeleton isn't on fire then block the movement. - if ((m_World->GetBlockSkyLight((int) a_Position.x, (int) a_Position.y, (int) a_Position.z) == 15) && (m_World->GetTimeOfDay() < 13187) && !IsOnFire()) + // If the destination is in the sun and if it is not night AND the zombie isn't on fire then block the movement. + if ( + !IsOnFire() && + (m_World->GetTimeOfDay() < 13187) && + (m_World->GetBlockSkyLight((int)a_Position.x, (int)a_Position.y, (int)a_Position.z) == 15) + ) { m_bMovingToDestination = false; return; } - m_bMovingToDestination = true; + + super::MoveToPosition(a_Position); } diff --git a/src/MonsterConfig.cpp b/src/MonsterConfig.cpp index 6165606f0..c06bd6b6f 100644 --- a/src/MonsterConfig.cpp +++ b/src/MonsterConfig.cpp @@ -12,9 +12,9 @@ struct cMonsterConfig::sAttributesStruct { AString m_Name; - double m_SightDistance; - double m_AttackDamage; - double m_AttackRange; + int m_SightDistance; + int m_AttackDamage; + int m_AttackRange; double m_AttackRate; int m_MaxHealth; }; @@ -67,9 +67,9 @@ void cMonsterConfig::Initialize() sAttributesStruct Attributes; AString Name = MonstersIniFile.GetKeyName(i); Attributes.m_Name = Name; - Attributes.m_AttackDamage = MonstersIniFile.GetValueF(Name, "AttackDamage", 0); - Attributes.m_AttackRange = MonstersIniFile.GetValueF(Name, "AttackRange", 0); - Attributes.m_SightDistance = MonstersIniFile.GetValueF(Name, "SightDistance", 0); + Attributes.m_AttackDamage = MonstersIniFile.GetValueI(Name, "AttackDamage", 0); + Attributes.m_AttackRange = MonstersIniFile.GetValueI(Name, "AttackRange", 0); + Attributes.m_SightDistance = MonstersIniFile.GetValueI(Name, "SightDistance", 0); Attributes.m_AttackRate = MonstersIniFile.GetValueF(Name, "AttackRate", 0); Attributes.m_MaxHealth = MonstersIniFile.GetValueI(Name, "MaxHealth", 1); m_pState->AttributesList.push_front(Attributes); @@ -87,10 +87,10 @@ void cMonsterConfig::AssignAttributes(cMonster * a_Monster, const AString & a_Na { if (itr->m_Name.compare(a_Name) == 0) { - a_Monster->SetAttackDamage ((float)itr->m_AttackDamage); - a_Monster->SetAttackRange ((float)itr->m_AttackRange); - a_Monster->SetSightDistance((float)itr->m_SightDistance); - a_Monster->SetAttackRate ((int)itr->m_AttackRate); + a_Monster->SetAttackDamage (itr->m_AttackDamage); + a_Monster->SetAttackRange (itr->m_AttackRange); + a_Monster->SetSightDistance(itr->m_SightDistance); + a_Monster->SetAttackRate ((float)itr->m_AttackRate); a_Monster->SetMaxHealth (itr->m_MaxHealth); return; } diff --git a/src/OSSupport/BlockingTCPLink.cpp b/src/OSSupport/BlockingTCPLink.cpp index 08aec0c65..af50eda5d 100644 --- a/src/OSSupport/BlockingTCPLink.cpp +++ b/src/OSSupport/BlockingTCPLink.cpp @@ -2,7 +2,7 @@ #include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules #include "BlockingTCPLink.h" - +#include "Errors.h" @@ -75,7 +75,7 @@ bool cBlockingTCPLink::Connect(const char * iAddress, unsigned int iPort) server.sin_port = htons( (unsigned short)iPort); if (connect(m_Socket, (struct sockaddr *)&server, sizeof(server))) { - LOGWARN("cTCPLink: Connection to \"%s:%d\" failed (%s)", iAddress, iPort, cSocket::GetErrorString( cSocket::GetLastError() ).c_str() ); + LOGWARN("cTCPLink: Connection to \"%s:%d\" failed (%s)", iAddress, iPort,GetOSErrorString( cSocket::GetLastError() ).c_str() ); CloseSocket(); return false; } diff --git a/src/OSSupport/Errors.cpp b/src/OSSupport/Errors.cpp new file mode 100644 index 000000000..2e05f1df1 --- /dev/null +++ b/src/OSSupport/Errors.cpp @@ -0,0 +1,53 @@ + +#include "Globals.h" + +#include "Errors.h" + +AString GetOSErrorString( int a_ErrNo ) +{ + char buffer[ 1024 ]; + AString Out; + + #ifdef _WIN32 + + FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, a_ErrNo, 0, buffer, ARRAYCOUNT(buffer), NULL); + Printf(Out, "%d: %s", a_ErrNo, buffer); + if (!Out.empty() && (Out[Out.length() - 1] == '\n')) + { + Out.erase(Out.length() - 2); + } + return Out; + + #else // _WIN32 + + // According to http://linux.die.net/man/3/strerror_r there are two versions of strerror_r(): + + #if ( _GNU_SOURCE ) && !defined(ANDROID_NDK) // GNU version of strerror_r() + + char * res = strerror_r( errno, buffer, ARRAYCOUNT(buffer) ); + if( res != NULL ) + { + Printf(Out, "%d: %s", a_ErrNo, res); + return Out; + } + + #else // XSI version of strerror_r(): + + int res = strerror_r( errno, buffer, ARRAYCOUNT(buffer) ); + if( res == 0 ) + { + Printf(Out, "%d: %s", a_ErrNo, buffer); + return Out; + } + + #endif // strerror_r() version + + else + { + Printf(Out, "Error %d while getting error string for error #%d!", errno, a_ErrNo); + return Out; + } + + #endif // else _WIN32 +} + diff --git a/src/OSSupport/Errors.h b/src/OSSupport/Errors.h new file mode 100644 index 000000000..8ce9deb10 --- /dev/null +++ b/src/OSSupport/Errors.h @@ -0,0 +1,5 @@ + +#pragma once + +AString GetOSErrorString(int a_ErrNo); + diff --git a/src/OSSupport/Event.cpp b/src/OSSupport/Event.cpp index cbacbba17..649a0a3cf 100644 --- a/src/OSSupport/Event.cpp +++ b/src/OSSupport/Event.cpp @@ -7,7 +7,7 @@ #include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules #include "Event.h" - +#include "Errors.h" @@ -35,14 +35,16 @@ cEvent::cEvent(void) m_Event = sem_open(EventName.c_str(), O_CREAT, 777, 0 ); if (m_Event == SEM_FAILED) { - LOGERROR("cEvent: Cannot create event, errno = %i. Aborting server.", errno); + AString error = GetOSErrorString(errno); + LOGERROR("cEvent: Cannot create event, err = %s. Aborting server.", error.c_str()); abort(); } // Unlink the semaphore immediately - it will continue to function but will not pollute the namespace // We don't store the name, so can't call this in the destructor if (sem_unlink(EventName.c_str()) != 0) { - LOGWARN("ERROR: Could not unlink cEvent. (%i)", errno); + AString error = GetOSErrorString(errno); + LOGWARN("ERROR: Could not unlink cEvent. (%s)", error.c_str()); } } #endif // *nix @@ -61,7 +63,8 @@ cEvent::~cEvent() { if (sem_close(m_Event) != 0) { - LOGERROR("ERROR: Could not close cEvent. (%i)", errno); + AString error = GetOSErrorString(errno); + LOGERROR("ERROR: Could not close cEvent. (%s)", error.c_str()); } } else @@ -88,7 +91,8 @@ void cEvent::Wait(void) int res = sem_wait(m_Event); if (res != 0 ) { - LOGWARN("cEvent: waiting for the event failed: %i, errno = %i. Continuing, but server may be unstable.", res, errno); + AString error = GetOSErrorString(errno); + LOGWARN("cEvent: waiting for the event failed: %i, err = %s. Continuing, but server may be unstable.", res, error.c_str()); } #endif } @@ -108,7 +112,8 @@ void cEvent::Set(void) int res = sem_post(m_Event); if (res != 0) { - LOGWARN("cEvent: Could not set cEvent: %i, errno = %d", res, errno); + AString error = GetOSErrorString(errno); + LOGWARN("cEvent: Could not set cEvent: %i, err = %s", res, error.c_str()); } #endif } diff --git a/src/OSSupport/File.cpp b/src/OSSupport/File.cpp index 9f7c0d439..0ebd04915 100644 --- a/src/OSSupport/File.cpp +++ b/src/OSSupport/File.cpp @@ -450,3 +450,12 @@ int cFile::Printf(const char * a_Fmt, ...) + +void cFile::Flush(void) +{ + fflush(m_File); +} + + + + diff --git a/src/OSSupport/File.h b/src/OSSupport/File.h index 01663a229..07fce6661 100644 --- a/src/OSSupport/File.h +++ b/src/OSSupport/File.h @@ -18,6 +18,8 @@ Usage: 2, Check if the file was opened using IsOpen() 3, Read / write 4, Destroy the instance + +For reading entire files into memory, just use the static cFile::ReadWholeFile() */ @@ -55,7 +57,7 @@ public: static const char PathSeparator = '/'; #endif - /// The mode in which to open the file + /** The mode in which to open the file */ enum eMode { fmRead, // Read-only. If the file doesn't exist, object will not be valid @@ -63,13 +65,13 @@ public: fmReadWrite // Read/write. If the file already exists, it will be left intact; writing will overwrite the data from the beginning } ; - /// Simple constructor - creates an unopened file object, use Open() to open / create a real file + /** Simple constructor - creates an unopened file object, use Open() to open / create a real file */ cFile(void); - /// Constructs and opens / creates the file specified, use IsOpen() to check for success + /** Constructs and opens / creates the file specified, use IsOpen() to check for success */ cFile(const AString & iFileName, eMode iMode); - /// Auto-closes the file, if open + /** Auto-closes the file, if open */ ~cFile(); bool Open(const AString & iFileName, eMode iMode); @@ -77,60 +79,63 @@ public: bool IsOpen(void) const; bool IsEOF(void) const; - /// Reads up to iNumBytes bytes into iBuffer, returns the number of bytes actually read, or -1 on failure; asserts if not open + /** Reads up to iNumBytes bytes into iBuffer, returns the number of bytes actually read, or -1 on failure; asserts if not open */ int Read (void * iBuffer, int iNumBytes); - /// Writes up to iNumBytes bytes from iBuffer, returns the number of bytes actually written, or -1 on failure; asserts if not open + /** Writes up to iNumBytes bytes from iBuffer, returns the number of bytes actually written, or -1 on failure; asserts if not open */ int Write(const void * iBuffer, int iNumBytes); - /// Seeks to iPosition bytes from file start, returns old position or -1 for failure; asserts if not open + /** Seeks to iPosition bytes from file start, returns old position or -1 for failure; asserts if not open */ int Seek (int iPosition); - /// Returns the current position (bytes from file start) or -1 for failure; asserts if not open + /** Returns the current position (bytes from file start) or -1 for failure; asserts if not open */ int Tell (void) const; - /// Returns the size of file, in bytes, or -1 for failure; asserts if not open + /** Returns the size of file, in bytes, or -1 for failure; asserts if not open */ int GetSize(void) const; - /// Reads the file from current position till EOF into an AString; returns the number of bytes read or -1 for error + /** Reads the file from current position till EOF into an AString; returns the number of bytes read or -1 for error */ int ReadRestOfFile(AString & a_Contents); // tolua_begin - /// Returns true if the file specified exists + /** Returns true if the file specified exists */ static bool Exists(const AString & a_FileName); - /// Deletes a file, returns true if successful + /** Deletes a file, returns true if successful */ static bool Delete(const AString & a_FileName); - /// Renames a file or folder, returns true if successful. May fail if dest already exists (libc-dependant)! + /** Renames a file or folder, returns true if successful. May fail if dest already exists (libc-dependant)! */ static bool Rename(const AString & a_OrigPath, const AString & a_NewPath); - /// Copies a file, returns true if successful. + /** Copies a file, returns true if successful. */ static bool Copy(const AString & a_SrcFileName, const AString & a_DstFileName); - /// Returns true if the specified path is a folder + /** Returns true if the specified path is a folder */ static bool IsFolder(const AString & a_Path); - /// Returns true if the specified path is a regular file + /** Returns true if the specified path is a regular file */ static bool IsFile(const AString & a_Path); - /// Returns the size of the file, or a negative number on error + /** Returns the size of the file, or a negative number on error */ static int GetSize(const AString & a_FileName); - /// Creates a new folder with the specified name. Returns true if successful. Path may be relative or absolute + /** 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. + /** Returns the entire contents of the specified file as a string. Returns empty string on error. */ static AString ReadWholeFile(const AString & a_FileName); // tolua_end - /// Returns the list of all items in the specified folder (files, folders, nix pipes, whatever's there). + /** Returns the list of all items in the specified folder (files, folders, nix pipes, whatever's there). */ static AStringVector GetFolderContents(const AString & a_Folder); // Exported in ManualBindings.cpp int Printf(const char * a_Fmt, ...); + /** Flushes all the bufferef output into the file (only when writing) */ + void Flush(void); + private: #ifdef USE_STDIO_FILE FILE * m_File; diff --git a/src/OSSupport/Socket.cpp b/src/OSSupport/Socket.cpp index d80c9bb3d..6afaceedf 100644 --- a/src/OSSupport/Socket.cpp +++ b/src/OSSupport/Socket.cpp @@ -6,7 +6,8 @@ #ifndef _WIN32 #include <netdb.h> #include <unistd.h> - #include <arpa/inet.h> //inet_ntoa() + #include <arpa/inet.h> // inet_ntoa() + #include <sys/ioctl.h> // ioctl() #else #define socklen_t int #endif @@ -105,58 +106,6 @@ void cSocket::ShutdownReadWrite(void) - -AString cSocket::GetErrorString( int a_ErrNo ) -{ - char buffer[ 1024 ]; - AString Out; - - #ifdef _WIN32 - - FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, a_ErrNo, 0, buffer, ARRAYCOUNT(buffer), NULL); - Printf(Out, "%d: %s", a_ErrNo, buffer); - if (!Out.empty() && (Out[Out.length() - 1] == '\n')) - { - Out.erase(Out.length() - 2); - } - return Out; - - #else // _WIN32 - - // According to http://linux.die.net/man/3/strerror_r there are two versions of strerror_r(): - - #if ( _GNU_SOURCE ) && !defined(ANDROID_NDK) // GNU version of strerror_r() - - char * res = strerror_r( errno, buffer, ARRAYCOUNT(buffer) ); - if( res != NULL ) - { - Printf(Out, "%d: %s", a_ErrNo, res); - return Out; - } - - #else // XSI version of strerror_r(): - - int res = strerror_r( errno, buffer, ARRAYCOUNT(buffer) ); - if( res == 0 ) - { - Printf(Out, "%d: %s", a_ErrNo, buffer); - return Out; - } - - #endif // strerror_r() version - - else - { - Printf(Out, "Error %d while getting error string for error #%d!", errno, a_ErrNo); - return Out; - } - - #endif // else _WIN32 -} - - - - int cSocket::GetLastError() { #ifdef _WIN32 @@ -372,7 +321,7 @@ bool cSocket::ConnectIPv4(const AString & a_HostNameOrAddr, unsigned short a_Por -int cSocket::Receive(char* a_Buffer, unsigned int a_Length, unsigned int a_Flags) +int cSocket::Receive(char * a_Buffer, unsigned int a_Length, unsigned int a_Flags) { return recv(m_Socket, a_Buffer, a_Length, a_Flags); } @@ -406,3 +355,25 @@ unsigned short cSocket::GetPort(void) const + +void cSocket::SetNonBlocking(void) +{ + #ifdef _WIN32 + u_long NonBlocking = 1; + int res = ioctlsocket(m_Socket, FIONBIO, &NonBlocking); + #else + int NonBlocking = 1; + int res = ioctl(m_Socket, FIONBIO, (char *)&NonBlocking); + #endif + if (res != 0) + { + LOGERROR("Cannot set socket to non-blocking. This would make the server deadlock later on, aborting.\nErr: %d, %d, %s", + res, GetLastError(), GetLastErrorString().c_str() + ); + abort(); + } +} + + + + diff --git a/src/OSSupport/Socket.h b/src/OSSupport/Socket.h index 91c9ca5fd..bdc2babf5 100644 --- a/src/OSSupport/Socket.h +++ b/src/OSSupport/Socket.h @@ -14,7 +14,7 @@ #endif - +#include "Errors.h" class cSocket @@ -24,6 +24,12 @@ public: { IPv4 = AF_INET, IPv6 = AF_INET6, + + #ifdef _WIN32 + ErrWouldBlock = WSAEWOULDBLOCK, + #else + ErrWouldBlock = EWOULDBLOCK, + #endif } ; #ifdef _WIN32 @@ -57,11 +63,10 @@ public: /// Initializes the network stack. Returns 0 on success, or another number as an error code. static int WSAStartup(void); - static AString GetErrorString(int a_ErrNo); static int GetLastError(); static AString GetLastErrorString(void) { - return GetErrorString(GetLastError()); + return GetOSErrorString(GetLastError()); } /// Creates a new socket of the specified address family @@ -111,8 +116,11 @@ public: unsigned short GetPort(void) const; // Returns 0 on failure const AString & GetIPString(void) const { return m_IPString; } + + /** Sets the socket into non-blocking mode */ + void SetNonBlocking(void); private: xSocket m_Socket; AString m_IPString; -};
\ No newline at end of file +}; diff --git a/src/OSSupport/SocketThreads.cpp b/src/OSSupport/SocketThreads.cpp index b8069cf00..3e2631733 100644 --- a/src/OSSupport/SocketThreads.cpp +++ b/src/OSSupport/SocketThreads.cpp @@ -7,6 +7,7 @@ #include "Globals.h" #include "SocketThreads.h" +#include "Errors.h" @@ -174,6 +175,7 @@ void cSocketThreads::cSocketThread::AddClient(const cSocket & a_Socket, cCallbac m_Slots[m_NumSlots].m_Client = a_Client; m_Slots[m_NumSlots].m_Socket = a_Socket; + m_Slots[m_NumSlots].m_Socket.SetNonBlocking(); m_Slots[m_NumSlots].m_Outgoing.clear(); m_Slots[m_NumSlots].m_State = sSlot::ssNormal; m_NumSlots++; @@ -212,7 +214,9 @@ bool cSocketThreads::cSocketThread::RemoveClient(const cCallback * a_Client) else { // Query and queue the last batch of outgoing data: - m_Slots[i].m_Client->GetOutgoingData(m_Slots[i].m_Outgoing); + AString Data; + m_Slots[i].m_Client->GetOutgoingData(Data); + m_Slots[i].m_Outgoing.append(Data); if (m_Slots[i].m_Outgoing.empty()) { // No more outgoing data, shut the socket down immediately: @@ -385,38 +389,28 @@ void cSocketThreads::cSocketThread::Execute(void) // The main thread loop: while (!m_ShouldTerminate) { - // Put all sockets into the Read set: + // Read outgoing data from the clients: + QueueOutgoingData(); + + // Put sockets into the sets fd_set fdRead; + fd_set fdWrite; cSocket::xSocket Highest = m_ControlSocket1.GetSocket(); - - PrepareSet(&fdRead, Highest, false); + PrepareSets(&fdRead, &fdWrite, Highest); // Wait for the sockets: timeval Timeout; Timeout.tv_sec = 5; Timeout.tv_usec = 0; - if (select(Highest + 1, &fdRead, NULL, NULL, &Timeout) == -1) + if (select(Highest + 1, &fdRead, &fdWrite, NULL, &Timeout) == -1) { - LOG("select(R) call failed in cSocketThread: \"%s\"", cSocket::GetLastErrorString().c_str()); + LOG("select() call failed in cSocketThread: \"%s\"", cSocket::GetLastErrorString().c_str()); continue; } + // Perform the IO: ReadFromSockets(&fdRead); - - // Test sockets for writing: - fd_set fdWrite; - Highest = m_ControlSocket1.GetSocket(); - PrepareSet(&fdWrite, Highest, true); - Timeout.tv_sec = 0; - Timeout.tv_usec = 0; - if (select(Highest + 1, NULL, &fdWrite, NULL, &Timeout) == -1) - { - LOG("select(W) call failed in cSocketThread: \"%s\"", cSocket::GetLastErrorString().c_str()); - continue; - } - WriteToSockets(&fdWrite); - CleanUpShutSockets(); } // while (!mShouldTerminate) } @@ -425,10 +419,11 @@ void cSocketThreads::cSocketThread::Execute(void) -void cSocketThreads::cSocketThread::PrepareSet(fd_set * a_Set, cSocket::xSocket & a_Highest, bool a_IsForWriting) +void cSocketThreads::cSocketThread::PrepareSets(fd_set * a_Read, fd_set * a_Write, cSocket::xSocket & a_Highest) { - FD_ZERO(a_Set); - FD_SET(m_ControlSocket1.GetSocket(), a_Set); + FD_ZERO(a_Read); + FD_ZERO(a_Write); + FD_SET(m_ControlSocket1.GetSocket(), a_Read); cCSLock Lock(m_Parent->m_CS); for (int i = m_NumSlots - 1; i >= 0; --i) @@ -443,11 +438,16 @@ void cSocketThreads::cSocketThread::PrepareSet(fd_set * a_Set, cSocket::xSocket continue; } cSocket::xSocket s = m_Slots[i].m_Socket.GetSocket(); - FD_SET(s, a_Set); + FD_SET(s, a_Read); if (s > a_Highest) { a_Highest = s; } + if (!m_Slots[i].m_Outgoing.empty()) + { + // There's outgoing data for the socket, put it in the Write set + FD_SET(s, a_Write); + } } // for i - m_Slots[] } @@ -479,34 +479,37 @@ void cSocketThreads::cSocketThread::ReadFromSockets(fd_set * a_Read) int Received = m_Slots[i].m_Socket.Receive(Buffer, ARRAYCOUNT(Buffer), 0); if (Received <= 0) { - // The socket has been closed by the remote party - switch (m_Slots[i].m_State) + if (cSocket::GetLastError() != cSocket::ErrWouldBlock) { - case sSlot::ssNormal: - { - // Notify the callback that the remote has closed the socket; keep the slot - m_Slots[i].m_Client->SocketClosed(); - m_Slots[i].m_State = sSlot::ssRemoteClosed; - break; - } - case sSlot::ssWritingRestOut: - case sSlot::ssShuttingDown: - case sSlot::ssShuttingDown2: - { - // Force-close the socket and remove the slot: - m_Slots[i].m_Socket.CloseSocket(); - m_Slots[i] = m_Slots[--m_NumSlots]; - break; - } - default: + // The socket has been closed by the remote party + switch (m_Slots[i].m_State) { - LOG("%s: Unexpected socket state: %d (%s)", - __FUNCTION__, m_Slots[i].m_Socket.GetSocket(), m_Slots[i].m_Socket.GetIPString().c_str() - ); - ASSERT(!"Unexpected socket state"); - break; - } - } // switch (m_Slots[i].m_State) + case sSlot::ssNormal: + { + // Notify the callback that the remote has closed the socket; keep the slot + m_Slots[i].m_Client->SocketClosed(); + m_Slots[i].m_State = sSlot::ssRemoteClosed; + break; + } + case sSlot::ssWritingRestOut: + case sSlot::ssShuttingDown: + case sSlot::ssShuttingDown2: + { + // Force-close the socket and remove the slot: + m_Slots[i].m_Socket.CloseSocket(); + m_Slots[i] = m_Slots[--m_NumSlots]; + break; + } + default: + { + LOG("%s: Unexpected socket state: %d (%s)", + __FUNCTION__, m_Slots[i].m_Socket.GetSocket(), m_Slots[i].m_Socket.GetIPString().c_str() + ); + ASSERT(!"Unexpected socket state"); + break; + } + } // switch (m_Slots[i].m_State) + } } else { @@ -538,7 +541,9 @@ void cSocketThreads::cSocketThread::WriteToSockets(fd_set * a_Write) // Request another chunk of outgoing data: if (m_Slots[i].m_Client != NULL) { - m_Slots[i].m_Client->GetOutgoingData(m_Slots[i].m_Outgoing); + AString Data; + m_Slots[i].m_Client->GetOutgoingData(Data); + m_Slots[i].m_Outgoing.append(Data); } if (m_Slots[i].m_Outgoing.empty()) { @@ -552,11 +557,10 @@ void cSocketThreads::cSocketThread::WriteToSockets(fd_set * a_Write) } } // if (outgoing data is empty) - int Sent = m_Slots[i].m_Socket.Send(m_Slots[i].m_Outgoing.data(), m_Slots[i].m_Outgoing.size()); - if (Sent < 0) + if (!SendDataThroughSocket(m_Slots[i].m_Socket, m_Slots[i].m_Outgoing)) { int Err = cSocket::GetLastError(); - LOGWARNING("Error %d while writing to client \"%s\", disconnecting. \"%s\"", Err, m_Slots[i].m_Socket.GetIPString().c_str(), cSocket::GetErrorString(Err).c_str()); + LOGWARNING("Error %d while writing to client \"%s\", disconnecting. \"%s\"", Err, m_Slots[i].m_Socket.GetIPString().c_str(), GetOSErrorString(Err).c_str()); m_Slots[i].m_Socket.CloseSocket(); if (m_Slots[i].m_Client != NULL) { @@ -564,7 +568,6 @@ void cSocketThreads::cSocketThread::WriteToSockets(fd_set * a_Write) } return; } - m_Slots[i].m_Outgoing.erase(0, Sent); if (m_Slots[i].m_Outgoing.empty() && (m_Slots[i].m_State == sSlot::ssWritingRestOut)) { @@ -589,8 +592,41 @@ void cSocketThreads::cSocketThread::WriteToSockets(fd_set * a_Write) +bool cSocketThreads::cSocketThread::SendDataThroughSocket(cSocket & a_Socket, AString & a_Data) +{ + // Send data in smaller chunks, so that the OS send buffers aren't overflown easily + while (!a_Data.empty()) + { + size_t NumToSend = std::min(a_Data.size(), (size_t)1024); + int Sent = a_Socket.Send(a_Data.data(), NumToSend); + if (Sent < 0) + { + int Err = cSocket::GetLastError(); + if (Err == cSocket::ErrWouldBlock) + { + // The OS send buffer is full, leave the outgoing data for the next time + return true; + } + // An error has occured + return false; + } + if (Sent == 0) + { + a_Socket.CloseSocket(); + return true; + } + a_Data.erase(0, Sent); + } + return true; +} + + + + + void cSocketThreads::cSocketThread::CleanUpShutSockets(void) { + cCSLock Lock(m_Parent->m_CS); for (int i = m_NumSlots - 1; i >= 0; i--) { switch (m_Slots[i].m_State) @@ -616,3 +652,32 @@ void cSocketThreads::cSocketThread::CleanUpShutSockets(void) +void cSocketThreads::cSocketThread::QueueOutgoingData(void) +{ + cCSLock Lock(m_Parent->m_CS); + for (int i = 0; i < m_NumSlots; i++) + { + if (m_Slots[i].m_Client != NULL) + { + AString Data; + m_Slots[i].m_Client->GetOutgoingData(Data); + m_Slots[i].m_Outgoing.append(Data); + } + if (m_Slots[i].m_Outgoing.empty()) + { + // No outgoing data is ready + if (m_Slots[i].m_State == sSlot::ssWritingRestOut) + { + // The socket doesn't want to be kept alive anymore, and doesn't have any remaining data to send. + // Shut it down and then close it after a timeout, or when the other side agrees + m_Slots[i].m_State = sSlot::ssShuttingDown; + m_Slots[i].m_Socket.ShutdownReadWrite(); + } + continue; + } + } +} + + + + diff --git a/src/OSSupport/SocketThreads.h b/src/OSSupport/SocketThreads.h index 9e1947ab6..fcd2ce11f 100644 --- a/src/OSSupport/SocketThreads.h +++ b/src/OSSupport/SocketThreads.h @@ -66,7 +66,8 @@ public: /** Called when data is received from the remote party */ virtual void DataReceived(const char * a_Data, int a_Size) = 0; - /** Called when data can be sent to remote party; the function is supposed to *append* outgoing data to a_Data */ + /** Called when data can be sent to remote party + The function is supposed to *set* outgoing data to a_Data (overwrite) */ virtual void GetOutgoingData(AString & a_Data) = 0; /** Called when the socket has been closed for any reason */ @@ -156,16 +157,27 @@ private: virtual void Execute(void) override; - /** Puts all sockets into the set, along with m_ControlSocket1. - Only sockets that are able to send and receive data are put in the Set. - Is a_IsForWriting is true, the ssWritingRestOut sockets are added as well. */ - void PrepareSet(fd_set * a_Set, cSocket::xSocket & a_Highest, bool a_IsForWriting); + /** Prepares the Read and Write socket sets for select() + Puts all sockets into the read set, along with m_ControlSocket1. + Only sockets that have outgoing data queued on them are put in the write set.*/ + void PrepareSets(fd_set * a_ReadSet, fd_set * a_WriteSet, cSocket::xSocket & a_Highest); - void ReadFromSockets(fd_set * a_Read); // Reads from sockets indicated in a_Read - void WriteToSockets (fd_set * a_Write); // Writes to sockets indicated in a_Write + /** Reads from sockets indicated in a_Read */ + void ReadFromSockets(fd_set * a_Read); + /** Writes to sockets indicated in a_Write */ + void WriteToSockets (fd_set * a_Write); + + /** Sends data through the specified socket, trying to fill the OS send buffer in chunks. + Returns true if there was no error while sending, false if an error has occured. + Modifies a_Data to contain only the unsent data. */ + bool SendDataThroughSocket(cSocket & a_Socket, AString & a_Data); + /** Removes those slots in ssShuttingDown2 state, sets those with ssShuttingDown state to ssShuttingDown2 */ void CleanUpShutSockets(void); + + /** Calls each client's callback to retrieve outgoing data for that client. */ + void QueueOutgoingData(void); } ; typedef std::list<cSocketThread *> cSocketThreadList; diff --git a/src/OSSupport/Timer.cpp b/src/OSSupport/Timer.cpp index ed16f9e3a..fd838dd0d 100644 --- a/src/OSSupport/Timer.cpp +++ b/src/OSSupport/Timer.cpp @@ -28,7 +28,7 @@ long long cTimer::GetNowTime(void) #else struct timeval now; gettimeofday(&now, NULL); - return (long long)(now.tv_sec * 1000 + now.tv_usec / 1000); + return (long long)now.tv_sec * 1000 + (long long)now.tv_usec / 1000; #endif } diff --git a/src/Protocol/Protocol.h b/src/Protocol/Protocol.h index 1bc5d528e..791082537 100644 --- a/src/Protocol/Protocol.h +++ b/src/Protocol/Protocol.h @@ -12,6 +12,7 @@ #include "../Defines.h" #include "../Endianness.h" +#include "../Scoreboard.h" @@ -92,6 +93,9 @@ public: virtual void SendRespawn (void) = 0; virtual void SendExperience (void) = 0; virtual void SendExperienceOrb (const cExpOrb & a_ExpOrb) = 0; + virtual void SendScoreboardObjective (const AString & a_Name, const AString & a_DisplayName, Byte a_Mode) = 0; + virtual void SendScoreUpdate (const AString & a_Objective, const AString & a_Player, cObjective::Score a_Score, Byte a_Mode) = 0; + virtual void SendDisplayObjective (const AString & a_Objective, cScoreboard::eDisplaySlot a_Display) = 0; virtual void SendSoundEffect (const AString & a_SoundName, int a_SrcX, int a_SrcY, int a_SrcZ, float a_Volume, float a_Pitch) = 0; // a_Src coords are Block * 8 virtual void SendSoundParticleEffect (int a_EffectID, int a_SrcX, int a_SrcY, int a_SrcZ, int a_Data) = 0; virtual void SendSpawnFallingBlock (const cFallingBlock & a_FallingBlock) = 0; diff --git a/src/Protocol/Protocol125.h b/src/Protocol/Protocol125.h index 310f9dd78..cd15ab518 100644 --- a/src/Protocol/Protocol125.h +++ b/src/Protocol/Protocol125.h @@ -68,6 +68,9 @@ public: virtual void SendRespawn (void) override; virtual void SendExperience (void) override; virtual void SendExperienceOrb (const cExpOrb & a_ExpOrb) override; + virtual void SendScoreboardObjective (const AString & a_Name, const AString & a_DisplayName, Byte a_Mode) override {} // This protocol doesn't support such message + virtual void SendScoreUpdate (const AString & a_Objective, const AString & a_Player, cObjective::Score a_Score, Byte a_Mode) override {} // This protocol doesn't support such message + virtual void SendDisplayObjective (const AString & a_Objective, cScoreboard::eDisplaySlot a_Display) override {} // This protocol doesn't support such message virtual void SendSoundEffect (const AString & a_SoundName, int a_SrcX, int a_SrcY, int a_SrcZ, float a_Volume, float a_Pitch) override; // a_Src coords are Block * 8 virtual void SendSoundParticleEffect (int a_EffectID, int a_SrcX, int a_SrcY, int a_SrcZ, int a_Data) override; virtual void SendSpawnFallingBlock (const cFallingBlock & a_FallingBlock) override; @@ -83,8 +86,8 @@ public: virtual void SendUpdateSign (int a_BlockX, int a_BlockY, int a_BlockZ, const AString & a_Line1, const AString & a_Line2, const AString & a_Line3, const AString & a_Line4) override; virtual void SendUseBed (const cEntity & a_Entity, int a_BlockX, int a_BlockY, int a_BlockZ ) override; virtual void SendWeather (eWeather a_Weather) override; - virtual void SendWholeInventory (const cWindow & a_Window) override; - virtual void SendWindowClose (const cWindow & a_Window) override; + virtual void SendWholeInventory (const cWindow & a_Window) override; + virtual void SendWindowClose (const cWindow & a_Window) override; virtual void SendWindowOpen (const cWindow & a_Window) override; virtual void SendWindowProperty (const cWindow & a_Window, short a_Property, short a_Value) override; diff --git a/src/Protocol/Protocol132.cpp b/src/Protocol/Protocol132.cpp index b4ca37d37..f5fd95c7e 100644 --- a/src/Protocol/Protocol132.cpp +++ b/src/Protocol/Protocol132.cpp @@ -28,13 +28,14 @@ #pragma warning(disable:4702) #endif -#include "cryptopp/randpool.h" - #ifdef _MSC_VER #pragma warning(pop) #endif + + + #define HANDLE_PACKET_READ(Proc, Type, Var) \ Type Var; \ { \ @@ -49,17 +50,6 @@ -typedef unsigned char Byte; - - - - - -using namespace CryptoPP; - - - - const int MAX_ENC_LEN = 512; // Maximum size of the encrypted message; should be 128, but who knows... @@ -93,81 +83,6 @@ enum -// Converts a raw 160-bit SHA1 digest into a Java Hex representation -// According to http://wiki.vg/wiki/index.php?title=Protocol_Encryption&oldid=2802 -static void DigestToJava(byte a_Digest[20], AString & a_Out) -{ - bool IsNegative = (a_Digest[0] >= 0x80); - if (IsNegative) - { - // Two's complement: - bool carry = true; // Add one to the whole number - for (int i = 19; i >= 0; i--) - { - a_Digest[i] = ~a_Digest[i]; - if (carry) - { - carry = (a_Digest[i] == 0xff); - a_Digest[i]++; - } - } - } - a_Out.clear(); - a_Out.reserve(40); - for (int i = 0; i < 20; i++) - { - AppendPrintf(a_Out, "%02x", a_Digest[i]); - } - while ((a_Out.length() > 0) && (a_Out[0] == '0')) - { - a_Out.erase(0, 1); - } - if (IsNegative) - { - a_Out.insert(0, "-"); - } -} - - - - - -/* -// Self-test the hash formatting for known values: -// sha1(Notch) : 4ed1f46bbe04bc756bcb17c0c7ce3e4632f06a48 -// sha1(jeb_) : -7c9d5b0044c130109a5d7b5fb5c317c02b4e28c1 -// sha1(simon) : 88e16a1019277b15d58faf0541e11910eb756f6 - -class Test -{ -public: - Test(void) - { - AString DigestNotch, DigestJeb, DigestSimon; - byte Digest[20]; - CryptoPP::SHA1 Checksum; - Checksum.Update((const byte *)"Notch", 5); - Checksum.Final(Digest); - DigestToJava(Digest, DigestNotch); - Checksum.Restart(); - Checksum.Update((const byte *)"jeb_", 4); - Checksum.Final(Digest); - DigestToJava(Digest, DigestJeb); - Checksum.Restart(); - Checksum.Update((const byte *)"simon", 5); - Checksum.Final(Digest); - DigestToJava(Digest, DigestSimon); - printf("Notch: \"%s\"", DigestNotch.c_str()); - printf("jeb_: \"%s\"", DigestJeb.c_str()); - printf("simon: \"%s\"", DigestSimon.c_str()); - } -} test; -*/ - - - - - /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // cProtocol132: @@ -197,11 +112,11 @@ void cProtocol132::DataReceived(const char * a_Data, int a_Size) { if (m_IsEncrypted) { - byte Decrypted[512]; + Byte Decrypted[512]; while (a_Size > 0) { int NumBytes = (a_Size > (int)sizeof(Decrypted)) ? (int)sizeof(Decrypted) : a_Size; - m_Decryptor.ProcessData(Decrypted, (byte *)a_Data, NumBytes); + m_Decryptor.ProcessData(Decrypted, (Byte *)a_Data, NumBytes); super::DataReceived((const char *)Decrypted, NumBytes); a_Size -= NumBytes; a_Data += NumBytes; @@ -582,9 +497,7 @@ int cProtocol132::ParseHandshake(void) return PARSE_OK; // Player is not allowed into the server } - // Send a 0xFD Encryption Key Request http://wiki.vg/Protocol#0xFD - CryptoPP::StringSink sink(m_ServerPublicKey); // GCC won't allow inline instantiation in the following line, damned temporary refs - cRoot::Get()->GetServer()->GetPublicKey().Save(sink); + // Send a 0xfd Encryption Key Request http://wiki.vg/Protocol#0xFD SendEncryptionKeyRequest(); return PARSE_OK; @@ -596,7 +509,7 @@ int cProtocol132::ParseHandshake(void) int cProtocol132::ParseClientStatuses(void) { - HANDLE_PACKET_READ(ReadByte, byte, Status); + HANDLE_PACKET_READ(ReadByte, Byte, Status); if ((Status & 1) == 0) { m_Client->HandleLogin(39, m_Username); @@ -714,11 +627,11 @@ void cProtocol132::Flush(void) int a_Size = m_DataToSend.size(); if (m_IsEncrypted) { - byte Encrypted[8192]; // Larger buffer, we may be sending lots of data (chunks) + Byte Encrypted[8192]; // Larger buffer, we may be sending lots of data (chunks) while (a_Size > 0) { int NumBytes = (a_Size > (int)sizeof(Encrypted)) ? (int)sizeof(Encrypted) : a_Size; - m_Encryptor.ProcessData(Encrypted, (byte *)a_Data, NumBytes); + m_Encryptor.ProcessData(Encrypted, (Byte *)a_Data, NumBytes); super::SendData((const char *)Encrypted, NumBytes); a_Size -= NumBytes; a_Data += NumBytes; @@ -880,8 +793,8 @@ void cProtocol132::SendEncryptionKeyRequest(void) cCSLock Lock(m_CSPacket); WriteByte(0xfd); WriteString(cRoot::Get()->GetServer()->GetServerID()); - WriteShort((short)m_ServerPublicKey.size()); - SendData(m_ServerPublicKey.data(), m_ServerPublicKey.size()); + WriteShort((short)(cRoot::Get()->GetServer()->GetPublicKeyDER().size())); + SendData(cRoot::Get()->GetServer()->GetPublicKeyDER().data(), cRoot::Get()->GetServer()->GetPublicKeyDER().size()); WriteShort(4); WriteInt((int)(intptr_t)this); // Using 'this' as the cryptographic nonce, so that we don't have to generate one each time :) Flush(); @@ -894,13 +807,11 @@ void cProtocol132::SendEncryptionKeyRequest(void) void cProtocol132::HandleEncryptionKeyResponse(const AString & a_EncKey, const AString & a_EncNonce) { // Decrypt EncNonce using privkey - RSAES<PKCS1v15>::Decryptor rsaDecryptor(cRoot::Get()->GetServer()->GetPrivateKey()); - time_t CurTime = time(NULL); - CryptoPP::RandomPool rng; - rng.Put((const byte *)&CurTime, sizeof(CurTime)); + cRSAPrivateKey & rsaDecryptor = cRoot::Get()->GetServer()->GetPrivateKey(); + Int32 DecryptedNonce[MAX_ENC_LEN / sizeof(Int32)]; - DecodingResult res = rsaDecryptor.Decrypt(rng, (const byte *)a_EncNonce.data(), a_EncNonce.size(), (byte *)DecryptedNonce); - if (!res.isValidCoding || (res.messageLength != 4)) + int res = rsaDecryptor.Decrypt((const Byte *)a_EncNonce.data(), a_EncNonce.size(), (Byte *)DecryptedNonce, sizeof(DecryptedNonce)); + if (res != 4) { LOGD("Bad nonce length"); m_Client->Kick("Hacked client"); @@ -914,9 +825,9 @@ void cProtocol132::HandleEncryptionKeyResponse(const AString & a_EncKey, const A } // Decrypt the symmetric encryption key using privkey: - byte DecryptedKey[MAX_ENC_LEN]; - res = rsaDecryptor.Decrypt(rng, (const byte *)a_EncKey.data(), a_EncKey.size(), DecryptedKey); - if (!res.isValidCoding || (res.messageLength != 16)) + Byte DecryptedKey[MAX_ENC_LEN]; + res = rsaDecryptor.Decrypt((const Byte *)a_EncKey.data(), a_EncKey.size(), DecryptedKey, sizeof(DecryptedKey)); + if (res != 16) { LOGD("Bad key length"); m_Client->Kick("Hacked client"); @@ -932,6 +843,12 @@ void cProtocol132::HandleEncryptionKeyResponse(const AString & a_EncKey, const A Flush(); } + #ifdef _DEBUG + AString DecryptedKeyHex; + CreateHexDump(DecryptedKeyHex, DecryptedKey, res, 16); + LOGD("Received encryption key, %d bytes:\n%s", res, DecryptedKeyHex.c_str()); + #endif + StartEncryption(DecryptedKey); return; } @@ -940,21 +857,21 @@ void cProtocol132::HandleEncryptionKeyResponse(const AString & a_EncKey, const A -void cProtocol132::StartEncryption(const byte * a_Key) +void cProtocol132::StartEncryption(const Byte * a_Key) { - m_Encryptor.SetKey(a_Key, 16, MakeParameters(Name::IV(), ConstByteArrayParameter(a_Key, 16))(Name::FeedbackSize(), 1)); - m_Decryptor.SetKey(a_Key, 16, MakeParameters(Name::IV(), ConstByteArrayParameter(a_Key, 16))(Name::FeedbackSize(), 1)); + m_Encryptor.Init(a_Key, a_Key); + m_Decryptor.Init(a_Key, a_Key); m_IsEncrypted = true; // Prepare the m_AuthServerID: - CryptoPP::SHA1 Checksum; + cSHA1Checksum Checksum; AString ServerID = cRoot::Get()->GetServer()->GetServerID(); - Checksum.Update((const byte *)ServerID.c_str(), ServerID.length()); + Checksum.Update((const Byte *)ServerID.c_str(), ServerID.length()); Checksum.Update(a_Key, 16); - Checksum.Update((const byte *)m_ServerPublicKey.c_str(), m_ServerPublicKey.length()); - byte Digest[20]; - Checksum.Final(Digest); - DigestToJava(Digest, m_AuthServerID); + Checksum.Update((const Byte *)cRoot::Get()->GetServer()->GetPublicKeyDER().data(), cRoot::Get()->GetServer()->GetPublicKeyDER().size()); + Byte Digest[20]; + Checksum.Finalize(Digest); + cSHA1Checksum::DigestToJava(Digest, m_AuthServerID); } diff --git a/src/Protocol/Protocol132.h b/src/Protocol/Protocol132.h index 80fc8740a..89f4636f5 100644 --- a/src/Protocol/Protocol132.h +++ b/src/Protocol/Protocol132.h @@ -20,13 +20,12 @@ #pragma warning(disable:4702) #endif -#include "cryptopp/modes.h" -#include "cryptopp/aes.h" - #ifdef _MSC_VER #pragma warning(pop) #endif +#include "../Crypto.h" + @@ -79,16 +78,15 @@ public: protected: bool m_IsEncrypted; - CryptoPP::CFB_Mode<CryptoPP::AES>::Decryption m_Decryptor; - CryptoPP::CFB_Mode<CryptoPP::AES>::Encryption m_Encryptor; + + cAESCFBDecryptor m_Decryptor; + cAESCFBEncryptor m_Encryptor; + AString m_DataToSend; /// The ServerID used for session authentication; set in StartEncryption(), used in GetAuthServerID() AString m_AuthServerID; - /// The server's public key, as used by SendEncryptionKeyRequest() and StartEncryption() - AString m_ServerPublicKey; - virtual void SendData(const char * a_Data, int a_Size) override; // DEBUG: @@ -108,7 +106,7 @@ protected: void HandleEncryptionKeyResponse(const AString & a_EncKey, const AString & a_EncNonce); /// Starts the symmetric encryption with the specified key; also sets m_AuthServerID - void StartEncryption(const byte * a_Key); + void StartEncryption(const Byte * a_Key); } ; diff --git a/src/Protocol/Protocol14x.cpp b/src/Protocol/Protocol14x.cpp index 127ce9d4b..f82e6de45 100644 --- a/src/Protocol/Protocol14x.cpp +++ b/src/Protocol/Protocol14x.cpp @@ -33,8 +33,6 @@ Implements the 1.4.x protocol classes representing these protocols: #pragma warning(disable:4702) #endif -#include "cryptopp/randpool.h" - #ifdef _MSC_VER #pragma warning(pop) #endif diff --git a/src/Protocol/Protocol15x.cpp b/src/Protocol/Protocol15x.cpp index 0f1e59f10..264a596b9 100644 --- a/src/Protocol/Protocol15x.cpp +++ b/src/Protocol/Protocol15x.cpp @@ -35,8 +35,11 @@ Implements the 1.5.x protocol classes: enum { - PACKET_WINDOW_OPEN = 0x64, - PACKET_PARTICLE_EFFECT = 0x3F, + PACKET_WINDOW_OPEN = 0x64, + PACKET_PARTICLE_EFFECT = 0x3F, + PACKET_SCOREBOARD_OBJECTIVE = 0xCE, + PACKET_SCORE_UPDATE = 0xCF, + PACKET_DISPLAY_OBJECTIVE = 0xD0 } ; @@ -97,6 +100,53 @@ void cProtocol150::SendParticleEffect(const AString & a_ParticleName, float a_Sr +void cProtocol150::SendScoreboardObjective(const AString & a_Name, const AString & a_DisplayName, Byte a_Mode) +{ + cCSLock Lock(m_CSPacket); + WriteByte(PACKET_SCOREBOARD_OBJECTIVE); + WriteString(a_Name); + WriteString(a_DisplayName); + WriteByte(a_Mode); + Flush(); +} + + + + + +void cProtocol150::SendScoreUpdate(const AString & a_Objective, const AString & a_Player, cObjective::Score a_Score, Byte a_Mode) +{ + cCSLock Lock(m_CSPacket); + WriteByte(PACKET_SCORE_UPDATE); + WriteString(a_Player); + WriteByte(a_Mode); + + if (a_Mode != 1) + { + WriteString(a_Objective); + WriteInt((int) a_Score); + } + + Flush(); +} + + + + + +void cProtocol150::SendDisplayObjective(const AString & a_Objective, cScoreboard::eDisplaySlot a_Display) +{ + cCSLock Lock(m_CSPacket); + WriteByte(PACKET_DISPLAY_OBJECTIVE); + WriteByte((int) a_Display); + WriteString(a_Objective); + Flush(); +} + + + + + int cProtocol150::ParseWindowClick(void) { HANDLE_PACKET_READ(ReadChar, char, WindowID); diff --git a/src/Protocol/Protocol15x.h b/src/Protocol/Protocol15x.h index 0074b3a83..0d171a67c 100644 --- a/src/Protocol/Protocol15x.h +++ b/src/Protocol/Protocol15x.h @@ -30,6 +30,9 @@ public: virtual void SendWindowOpen (const cWindow & a_Window) 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_ParticleAmmount) override; + virtual void SendScoreboardObjective (const AString & a_Name, const AString & a_DisplayName, Byte a_Mode) override; + virtual void SendScoreUpdate (const AString & a_Objective, const AString & a_Player, cObjective::Score a_Score, Byte a_Mode) override; + virtual void SendDisplayObjective (const AString & a_Objective, cScoreboard::eDisplaySlot a_Display) override; virtual int ParseWindowClick(void); } ; diff --git a/src/Protocol/Protocol17x.cpp b/src/Protocol/Protocol17x.cpp index e5a380f8a..04bade867 100644 --- a/src/Protocol/Protocol17x.cpp +++ b/src/Protocol/Protocol17x.cpp @@ -1,4 +1,3 @@ - // Protocol17x.cpp /* @@ -54,6 +53,22 @@ Implements the 1.7.x protocol classes: +const int MAX_ENC_LEN = 512; // Maximum size of the encrypted message; should be 128, but who knows... + + + + + +// fwd: main.cpp: +extern bool g_ShouldLogCommIn, g_ShouldLogCommOut; + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cProtocol172: + cProtocol172::cProtocol172(cClientHandle * a_Client, const AString & a_ServerAddress, UInt16 a_ServerPort, UInt32 a_State) : super(a_Client), m_ServerAddress(a_ServerAddress), @@ -64,6 +79,13 @@ cProtocol172::cProtocol172(cClientHandle * a_Client, const AString & a_ServerAdd m_OutPacketLenBuffer(20), // 20 bytes is more than enough for one VarInt m_IsEncrypted(false) { + // Create the comm log file, if so requested: + if (g_ShouldLogCommIn || g_ShouldLogCommOut) + { + cFile::CreateFolder("CommLogs"); + AString FileName = Printf("CommLogs/%x__%s.log", (unsigned)time(NULL), a_Client->GetIPString().c_str()); + m_CommLogFile.Open(FileName, cFile::fmWrite); + } } @@ -74,11 +96,11 @@ void cProtocol172::DataReceived(const char * a_Data, int a_Size) { if (m_IsEncrypted) { - byte Decrypted[512]; + Byte Decrypted[512]; while (a_Size > 0) { int NumBytes = (a_Size > sizeof(Decrypted)) ? sizeof(Decrypted) : a_Size; - m_Decryptor.ProcessData(Decrypted, (byte *)a_Data, NumBytes); + m_Decryptor.ProcessData(Decrypted, (Byte *)a_Data, NumBytes); AddReceivedData((const char *)Decrypted, NumBytes); a_Size -= NumBytes; a_Data += NumBytes; @@ -124,7 +146,7 @@ void cProtocol172::SendBlockAction(int a_BlockX, int a_BlockY, int a_BlockZ, cha void cProtocol172::SendBlockBreakAnim(int a_EntityID, int a_BlockX, int a_BlockY, int a_BlockZ, char a_Stage) { cPacketizer Pkt(*this, 0x25); // Block Break Animation packet - Pkt.WriteInt(a_EntityID); + Pkt.WriteVarInt(a_EntityID); Pkt.WriteInt(a_BlockX); Pkt.WriteInt(a_BlockY); Pkt.WriteInt(a_BlockZ); @@ -707,6 +729,46 @@ void cProtocol172::SendExperienceOrb(const cExpOrb & a_ExpOrb) +void cProtocol172::SendScoreboardObjective(const AString & a_Name, const AString & a_DisplayName, Byte a_Mode) +{ + cPacketizer Pkt(*this, 0x3B); + Pkt.WriteString(a_Name); + Pkt.WriteString(a_DisplayName); + Pkt.WriteByte(a_Mode); +} + + + + + +void cProtocol172::SendScoreUpdate(const AString & a_Objective, const AString & a_Player, cObjective::Score a_Score, Byte a_Mode) +{ + cPacketizer Pkt(*this, 0x3C); + Pkt.WriteString(a_Player); + Pkt.WriteByte(a_Mode); + + if (a_Mode != 1) + { + Pkt.WriteString(a_Objective); + Pkt.WriteInt((int) a_Score); + } +} + + + + + +void cProtocol172::SendDisplayObjective(const AString & a_Objective, cScoreboard::eDisplaySlot a_Display) +{ + cPacketizer Pkt(*this, 0x3D); + Pkt.WriteByte((int) a_Display); + Pkt.WriteString(a_Objective); +} + + + + + void cProtocol172::SendSoundEffect(const AString & a_SoundName, int a_SrcX, int a_SrcY, int a_SrcZ, float a_Volume, float a_Pitch) // a_Src coords are Block * 8 { cPacketizer Pkt(*this, 0x29); // Sound Effect packet @@ -923,10 +985,11 @@ void cProtocol172::SendUpdateSign(int a_BlockX, int a_BlockY, int a_BlockZ, cons Pkt.WriteInt(a_BlockX); Pkt.WriteShort((short)a_BlockY); Pkt.WriteInt(a_BlockZ); - Pkt.WriteString(a_Line1); - Pkt.WriteString(a_Line2); - Pkt.WriteString(a_Line3); - Pkt.WriteString(a_Line4); + // Need to send only up to 15 chars, otherwise the client crashes (#598) + Pkt.WriteString(a_Line1.substr(0, 15)); + Pkt.WriteString(a_Line2.substr(0, 15)); + Pkt.WriteString(a_Line3.substr(0, 15)); + Pkt.WriteString(a_Line4.substr(0, 15)); } @@ -1026,6 +1089,31 @@ void cProtocol172::SendWindowProperty(const cWindow & a_Window, short a_Property void cProtocol172::AddReceivedData(const char * a_Data, int a_Size) { + // Write the incoming data into the comm log file: + if (g_ShouldLogCommIn) + { + if (m_ReceivedData.GetReadableSpace() > 0) + { + AString AllData; + int OldReadableSpace = m_ReceivedData.GetReadableSpace(); + m_ReceivedData.ReadAll(AllData); + m_ReceivedData.ResetRead(); + m_ReceivedData.SkipRead(m_ReceivedData.GetReadableSpace() - OldReadableSpace); + ASSERT(m_ReceivedData.GetReadableSpace() == OldReadableSpace); + AString Hex; + CreateHexDump(Hex, AllData.data(), AllData.size(), 16); + m_CommLogFile.Printf("Incoming data, %d (0x%x) unparsed bytes already present in buffer:\n%s\n", + AllData.size(), AllData.size(), Hex.c_str() + ); + } + AString Hex; + CreateHexDump(Hex, a_Data, a_Size, 16); + m_CommLogFile.Printf("Incoming data: %d (0x%x) bytes: \n%s\n", + a_Size, a_Size, Hex.c_str() + ); + m_CommLogFile.Flush(); + } + if (!m_ReceivedData.Write(a_Data, a_Size)) { // Too much data in the incoming queue, report to caller: @@ -1040,12 +1128,14 @@ void cProtocol172::AddReceivedData(const char * a_Data, int a_Size) if (!m_ReceivedData.ReadVarInt(PacketLen)) { // Not enough data - return; + m_ReceivedData.ResetRead(); + break; } if (!m_ReceivedData.CanReadBytes(PacketLen)) { // The full packet hasn't been received yet - return; + m_ReceivedData.ResetRead(); + break; } cByteBuffer bb(PacketLen + 1); VERIFY(m_ReceivedData.ReadToByteBuffer(bb, (int)PacketLen)); @@ -1058,9 +1148,25 @@ void cProtocol172::AddReceivedData(const char * a_Data, int a_Size) if (!bb.ReadVarInt(PacketType)) { // Not enough data - return; + break; } + // Log the packet info into the comm log file: + if (g_ShouldLogCommIn) + { + AString PacketData; + bb.ReadAll(PacketData); + bb.ResetRead(); + bb.ReadVarInt(PacketType); + ASSERT(PacketData.size() > 0); + PacketData.resize(PacketData.size() - 1); + AString PacketDataHex; + CreateHexDump(PacketDataHex, PacketData.data(), PacketData.size(), 16); + m_CommLogFile.Printf("Next incoming packet is type %u (0x%x), length %u (0x%x) at state %d. Payload:\n%s\n", + PacketType, PacketType, PacketLen, PacketLen, m_State, PacketDataHex.c_str() + ); + } + if (!HandlePacket(bb, PacketType)) { // Unknown packet, already been reported, but without the length. Log the length here: @@ -1077,6 +1183,12 @@ void cProtocol172::AddReceivedData(const char * a_Data, int a_Size) LOGD("Packet contents:\n%s", Out.c_str()); #endif // _DEBUG + // Put a message in the comm log: + if (g_ShouldLogCommIn) + { + m_CommLogFile.Printf("^^^^^^ Unhandled packet ^^^^^^\n\n\n"); + } + return; } @@ -1086,10 +1198,37 @@ void cProtocol172::AddReceivedData(const char * a_Data, int a_Size) LOGWARNING("Protocol 1.7: Wrong number of bytes read for packet 0x%x, state %d. Read %u bytes, packet contained %u bytes", PacketType, m_State, bb.GetUsedSpace() - bb.GetReadableSpace(), PacketLen ); + + // Put a message in the comm log: + if (g_ShouldLogCommIn) + { + m_CommLogFile.Printf("^^^^^^ Wrong number of bytes read for this packet (exp %d left, got %d left) ^^^^^^\n\n\n", + 1, bb.GetReadableSpace() + ); + m_CommLogFile.Flush(); + } + ASSERT(!"Read wrong number of bytes!"); m_Client->PacketError(PacketType); } - } // while (true) + } // for(ever) + + // Log any leftover bytes into the logfile: + if (g_ShouldLogCommIn && (m_ReceivedData.GetReadableSpace() > 0)) + { + AString AllData; + int OldReadableSpace = m_ReceivedData.GetReadableSpace(); + m_ReceivedData.ReadAll(AllData); + m_ReceivedData.ResetRead(); + m_ReceivedData.SkipRead(m_ReceivedData.GetReadableSpace() - OldReadableSpace); + ASSERT(m_ReceivedData.GetReadableSpace() == OldReadableSpace); + AString Hex; + CreateHexDump(Hex, AllData.data(), AllData.size(), 16); + m_CommLogFile.Printf("There are %d (0x%x) bytes of non-parse-able data left in the buffer:\n%s", + m_ReceivedData.GetReadableSpace(), m_ReceivedData.GetReadableSpace(), Hex.c_str() + ); + m_CommLogFile.Flush(); + } } @@ -1219,7 +1358,64 @@ void cProtocol172::HandlePacketStatusRequest(cByteBuffer & a_ByteBuffer) void cProtocol172::HandlePacketLoginEncryptionResponse(cByteBuffer & a_ByteBuffer) { - // TODO: Add protocol encryption + short EncKeyLength, EncNonceLength; + a_ByteBuffer.ReadBEShort(EncKeyLength); + AString EncKey; + if (!a_ByteBuffer.ReadString(EncKey, EncKeyLength)) + { + return; + } + a_ByteBuffer.ReadBEShort(EncNonceLength); + AString EncNonce; + if (!a_ByteBuffer.ReadString(EncNonce, EncNonceLength)) + { + return; + } + if ((EncKeyLength > MAX_ENC_LEN) || (EncNonceLength > MAX_ENC_LEN)) + { + LOGD("Too long encryption"); + m_Client->Kick("Hacked client"); + return; + } + + // Decrypt EncNonce using privkey + cRSAPrivateKey & rsaDecryptor = cRoot::Get()->GetServer()->GetPrivateKey(); + Int32 DecryptedNonce[MAX_ENC_LEN / sizeof(Int32)]; + int res = rsaDecryptor.Decrypt((const Byte *)EncNonce.data(), EncNonce.size(), (Byte *)DecryptedNonce, sizeof(DecryptedNonce)); + if (res != 4) + { + LOGD("Bad nonce length: got %d, exp %d", res, 4); + m_Client->Kick("Hacked client"); + return; + } + if (ntohl(DecryptedNonce[0]) != (unsigned)(uintptr_t)this) + { + LOGD("Bad nonce value"); + m_Client->Kick("Hacked client"); + return; + } + + // Decrypt the symmetric encryption key using privkey: + Byte DecryptedKey[MAX_ENC_LEN]; + res = rsaDecryptor.Decrypt((const Byte *)EncKey.data(), EncKey.size(), DecryptedKey, sizeof(DecryptedKey)); + if (res != 16) + { + LOGD("Bad key length"); + m_Client->Kick("Hacked client"); + return; + } + + StartEncryption(DecryptedKey); + + // Send login success: + { + cPacketizer Pkt(*this, 0x02); // Login success packet + Pkt.WriteString(Printf("%d", m_Client->GetUniqueID())); // TODO: proper UUID + Pkt.WriteString(m_Client->GetUsername()); + } + + m_State = 3; // State = Game + m_Client->HandleLogin(4, m_Client->GetUsername()); } @@ -1231,14 +1427,26 @@ void cProtocol172::HandlePacketLoginStart(cByteBuffer & a_ByteBuffer) AString Username; a_ByteBuffer.ReadVarUTF8String(Username); - // TODO: Protocol encryption should be set up here if not localhost / auth - if (!m_Client->HandleHandshake(Username)) { // The client is not welcome here, they have been sent a Kick packet already return; } + // If auth is required, then send the encryption request: + if (cRoot::Get()->GetServer()->ShouldAuthenticate()) + { + cPacketizer Pkt(*this, 0x01); + Pkt.WriteString(cRoot::Get()->GetServer()->GetServerID()); + const AString & PubKeyDer = cRoot::Get()->GetServer()->GetPublicKeyDER(); + Pkt.WriteShort(PubKeyDer.size()); + Pkt.WriteBuf(PubKeyDer.data(), PubKeyDer.size()); + Pkt.WriteShort(4); + Pkt.WriteInt((int)(intptr_t)this); // Using 'this' as the cryptographic nonce, so that we don't have to generate one each time :) + m_Client->SetUsername(Username); + return; + } + // Send login success: { cPacketizer Pkt(*this, 0x02); // Login success packet @@ -1625,11 +1833,11 @@ void cProtocol172::SendData(const char * a_Data, int a_Size) { if (m_IsEncrypted) { - byte Encrypted[8192]; // Larger buffer, we may be sending lots of data (chunks) + Byte Encrypted[8192]; // Larger buffer, we may be sending lots of data (chunks) while (a_Size > 0) { int NumBytes = (a_Size > sizeof(Encrypted)) ? sizeof(Encrypted) : a_Size; - m_Encryptor.ProcessData(Encrypted, (byte *)a_Data, NumBytes); + m_Encryptor.ProcessData(Encrypted, (Byte *)a_Data, NumBytes); m_Client->SendData((const char *)Encrypted, NumBytes); a_Size -= NumBytes; a_Data += NumBytes; @@ -1750,6 +1958,27 @@ void cProtocol172::ParseItemMetadata(cItem & a_Item, const AString & a_Metadata) +void cProtocol172::StartEncryption(const Byte * a_Key) +{ + m_Encryptor.Init(a_Key, a_Key); + m_Decryptor.Init(a_Key, a_Key); + m_IsEncrypted = true; + + // Prepare the m_AuthServerID: + cSHA1Checksum Checksum; + const AString & ServerID = cRoot::Get()->GetServer()->GetServerID(); + Checksum.Update((const Byte *)ServerID.c_str(), ServerID.length()); + Checksum.Update(a_Key, 16); + Checksum.Update((const Byte *)cRoot::Get()->GetServer()->GetPublicKeyDER().data(), cRoot::Get()->GetServer()->GetPublicKeyDER().size()); + Byte Digest[20]; + Checksum.Finalize(Digest); + cSHA1Checksum::DigestToJava(Digest, m_AuthServerID); +} + + + + + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // cProtocol172::cPacketizer: @@ -1768,6 +1997,17 @@ cProtocol172::cPacketizer::~cPacketizer() m_Out.ReadAll(DataToSend); m_Protocol.SendData(DataToSend.data(), DataToSend.size()); m_Out.CommitRead(); + + // Log the comm into logfile: + if (g_ShouldLogCommOut) + { + AString Hex; + ASSERT(DataToSend.size() > 0); + CreateHexDump(Hex, DataToSend.data() + 1, DataToSend.size() - 1, 16); + m_Protocol.m_CommLogFile.Printf("Outgoing packet: type %d (0x%x), length %u (0x%x), state %d. Payload:\n%s\n", + DataToSend[0], DataToSend[0], PacketLen, PacketLen, m_Protocol.m_State, Hex.c_str() + ); + } } diff --git a/src/Protocol/Protocol17x.h b/src/Protocol/Protocol17x.h index 3ae774c18..6a75e41c8 100644 --- a/src/Protocol/Protocol17x.h +++ b/src/Protocol/Protocol17x.h @@ -26,21 +26,20 @@ Declares the 1.7.x protocol classes: #pragma warning(disable:4702) #endif -#include "cryptopp/modes.h" -#include "cryptopp/aes.h" - #ifdef _MSC_VER #pragma warning(pop) #endif +#include "../Crypto.h" + class cProtocol172 : - public cProtocol // TODO + public cProtocol { - typedef cProtocol super; // TODO + typedef cProtocol super; public: @@ -92,6 +91,9 @@ public: virtual void SendSoundEffect (const AString & a_SoundName, int a_SrcX, int a_SrcY, int a_SrcZ, float a_Volume, float a_Pitch) override; // a_Src coords are Block * 8 virtual void SendExperience (void) override; virtual void SendExperienceOrb (const cExpOrb & a_ExpOrb) override; + virtual void SendScoreboardObjective (const AString & a_Name, const AString & a_DisplayName, Byte a_Mode) override; + virtual void SendScoreUpdate (const AString & a_Objective, const AString & a_Player, cObjective::Score a_Score, Byte a_Mode) override; + virtual void SendDisplayObjective (const AString & a_Objective, cScoreboard::eDisplaySlot a_Display) override; virtual void SendSoundParticleEffect (int a_EffectID, int a_SrcX, int a_SrcY, int a_SrcZ, int a_Data) override; virtual void SendSpawnFallingBlock (const cFallingBlock & a_FallingBlock) override; virtual void SendSpawnMob (const cMonster & a_Mob) override; @@ -217,8 +219,12 @@ protected: cByteBuffer m_OutPacketLenBuffer; bool m_IsEncrypted; - CryptoPP::CFB_Mode<CryptoPP::AES>::Decryption m_Decryptor; - CryptoPP::CFB_Mode<CryptoPP::AES>::Encryption m_Encryptor; + + cAESCFBDecryptor m_Decryptor; + cAESCFBEncryptor m_Encryptor; + + /** The logfile where the comm is logged, when g_ShouldLogComm is true */ + cFile m_CommLogFile; /// Adds the received (unencrypted) data to m_ReceivedData, parses complete packets @@ -275,6 +281,8 @@ protected: /// Parses item metadata as read by ReadItem(), into the item enchantments. void ParseItemMetadata(cItem & a_Item, const AString & a_Metadata); + + void StartEncryption(const Byte * a_Key); } ; diff --git a/src/Protocol/ProtocolRecognizer.cpp b/src/Protocol/ProtocolRecognizer.cpp index 5524af136..32409c2aa 100644 --- a/src/Protocol/ProtocolRecognizer.cpp +++ b/src/Protocol/ProtocolRecognizer.cpp @@ -526,6 +526,36 @@ void cProtocolRecognizer::SendExperienceOrb(const cExpOrb & a_ExpOrb) +void cProtocolRecognizer::SendScoreboardObjective(const AString & a_Name, const AString & a_DisplayName, Byte a_Mode) +{ + ASSERT(m_Protocol != NULL); + m_Protocol->SendScoreboardObjective(a_Name, a_DisplayName, a_Mode); +} + + + + + +void cProtocolRecognizer::SendScoreUpdate(const AString & a_Objective, const AString & a_Player, cObjective::Score a_Score, Byte a_Mode) +{ + ASSERT(m_Protocol != NULL); + m_Protocol->SendScoreUpdate(a_Objective, a_Player, a_Score, a_Mode); +} + + + + + +void cProtocolRecognizer::SendDisplayObjective(const AString & a_Objective, cScoreboard::eDisplaySlot a_Display) +{ + ASSERT(m_Protocol != NULL); + m_Protocol->SendDisplayObjective(a_Objective, a_Display); +} + + + + + void cProtocolRecognizer::SendSoundEffect(const AString & a_SoundName, int a_SrcX, int a_SrcY, int a_SrcZ, float a_Volume, float a_Pitch) { ASSERT(m_Protocol != NULL); @@ -807,7 +837,7 @@ bool cProtocolRecognizer::TryRecognizeLengthlessProtocol(void) } switch (ch) { - case PROTO_VERSION_1_3_2: + case PROTO_VERSION_1_3_2: { m_Protocol = new cProtocol132(m_Client); return true; @@ -935,7 +965,7 @@ void cProtocolRecognizer::SendLengthlessServerPing(void) m_Buffer.ResetRead(); if (m_Buffer.CanReadBytes(2)) { - byte val; + Byte val; m_Buffer.ReadByte(val); // Packet type - Serverlist ping m_Buffer.ReadByte(val); // 0x01 magic value ASSERT(val == 0x01); diff --git a/src/Protocol/ProtocolRecognizer.h b/src/Protocol/ProtocolRecognizer.h index 2dccace6e..f58c66d10 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.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, 1.7.2" +#define MCS_CLIENT_VERSIONS "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, 1.7.2, 1.7.4" #define MCS_PROTOCOL_VERSIONS "29, 39, 47, 49, 51, 60, 61, 73, 74, 77, 78, 4" @@ -103,6 +103,9 @@ public: virtual void SendRespawn (void) override; virtual void SendExperience (void) override; virtual void SendExperienceOrb (const cExpOrb & a_ExpOrb) override; + virtual void SendScoreboardObjective (const AString & a_Name, const AString & a_DisplayName, Byte a_Mode) override; + virtual void SendScoreUpdate (const AString & a_Objective, const AString & a_Player, cObjective::Score a_Score, Byte a_Mode) override; + virtual void SendDisplayObjective (const AString & a_Objective, cScoreboard::eDisplaySlot a_Display) override; virtual void SendSoundEffect (const AString & a_SoundName, int a_SrcX, int a_SrcY, int a_SrcZ, float a_Volume, float a_Pitch) override; virtual void SendSoundParticleEffect (int a_EffectID, int a_SrcX, int a_SrcY, int a_SrcZ, int a_Data) override; virtual void SendSpawnFallingBlock (const cFallingBlock & a_FallingBlock) override; diff --git a/src/ReferenceManager.cpp b/src/ReferenceManager.cpp deleted file mode 100644 index 6a9ed0e43..000000000 --- a/src/ReferenceManager.cpp +++ /dev/null @@ -1,43 +0,0 @@ - -#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules - -#include "ReferenceManager.h" -#include "Entities/Entity.h" - - - - - -cReferenceManager::cReferenceManager( ENUM_REFERENCE_MANAGER_TYPE a_Type ) - : m_Type( a_Type ) -{ -} - -cReferenceManager::~cReferenceManager() -{ - if( m_Type == RFMNGR_REFERENCERS ) - { - for( std::list< cEntity** >::iterator itr = m_References.begin(); itr != m_References.end(); ++itr ) - { - *(*itr) = 0; // Set referenced pointer to 0 - } - } - else - { - for( std::list< cEntity** >::iterator itr = m_References.begin(); itr != m_References.end(); ++itr ) - { - cEntity* Ptr = (*(*itr)); - if( Ptr ) Ptr->Dereference( *(*itr) ); - } - } -} - -void cReferenceManager::AddReference( cEntity*& a_EntityPtr ) -{ - m_References.push_back( &a_EntityPtr ); -} - -void cReferenceManager::Dereference( cEntity*& a_EntityPtr ) -{ - m_References.remove( &a_EntityPtr ); -}
\ No newline at end of file diff --git a/src/ReferenceManager.h b/src/ReferenceManager.h deleted file mode 100644 index bcd451f72..000000000 --- a/src/ReferenceManager.h +++ /dev/null @@ -1,34 +0,0 @@ - -#pragma once - - - - - -class cEntity; - - - - - -class cReferenceManager -{ -public: - enum ENUM_REFERENCE_MANAGER_TYPE - { - RFMNGR_REFERENCERS, - RFMNGR_REFERENCES, - }; - cReferenceManager( ENUM_REFERENCE_MANAGER_TYPE a_Type ); - ~cReferenceManager(); - - void AddReference( cEntity*& a_EntityPtr ); - void Dereference( cEntity*& a_EntityPtr ); -private: - ENUM_REFERENCE_MANAGER_TYPE m_Type; - std::list< cEntity** > m_References; -}; - - - - diff --git a/src/Root.cpp b/src/Root.cpp index fa1fdb37a..883bfe76e 100644 --- a/src/Root.cpp +++ b/src/Root.cpp @@ -200,7 +200,7 @@ void cRoot::Start(void) long long finishmseconds = Time.GetNowTime(); finishmseconds -= mseconds; - LOG("Startup complete, took %i ms!", finishmseconds); + LOG("Startup complete, took %lld ms!", finishmseconds); #ifdef _WIN32 EnableMenuItem(hmenu, SC_CLOSE, MF_ENABLED); // Re-enable close button #endif diff --git a/src/Scoreboard.cpp b/src/Scoreboard.cpp new file mode 100644 index 000000000..b2edd613b --- /dev/null +++ b/src/Scoreboard.cpp @@ -0,0 +1,510 @@ + +// Scoreboard.cpp + +// Implementation of a scoreboard that keeps track of specified objectives + +#include "Globals.h" + +#include "Scoreboard.h" +#include "World.h" +#include "ClientHandle.h" + + + + + +AString cObjective::TypeToString(eType a_Type) +{ + switch (a_Type) + { + case E_TYPE_DUMMY: return "dummy"; + case E_TYPE_DEATH_COUNT: return "deathCount"; + case E_TYPE_PLAYER_KILL_COUNT: return "playerKillCount"; + case E_TYPE_TOTAL_KILL_COUNT: return "totalKillCount"; + case E_TYPE_HEALTH: return "health"; + case E_TYPE_ACHIEVEMENT: return "achievement"; + case E_TYPE_STAT: return "stat"; + case E_TYPE_STAT_ITEM_CRAFT: return "stat.craftItem"; + case E_TYPE_STAT_ITEM_USE: return "stat.useItem"; + case E_TYPE_STAT_ITEM_BREAK: return "stat.breakItem"; + case E_TYPE_STAT_BLOCK_MINE: return "stat.mineBlock"; + case E_TYPE_STAT_ENTITY_KILL: return "stat.killEntity"; + case E_TYPE_STAT_ENTITY_KILLED_BY: return "stat.entityKilledBy"; + + default: return ""; + } +} + + + + + +cObjective::eType cObjective::StringToType(const AString & a_Name) +{ + static struct { + eType m_Type; + const char * m_String; + } TypeMap [] = + { + {E_TYPE_DUMMY, "dummy"}, + {E_TYPE_DEATH_COUNT, "deathCount"}, + {E_TYPE_PLAYER_KILL_COUNT, "playerKillCount"}, + {E_TYPE_TOTAL_KILL_COUNT, "totalKillCount"}, + {E_TYPE_HEALTH, "health"}, + {E_TYPE_ACHIEVEMENT, "achievement"}, + {E_TYPE_STAT, "stat"}, + {E_TYPE_STAT_ITEM_CRAFT, "stat.craftItem"}, + {E_TYPE_STAT_ITEM_USE, "stat.useItem"}, + {E_TYPE_STAT_ITEM_BREAK, "stat.breakItem"}, + {E_TYPE_STAT_BLOCK_MINE, "stat.mineBlock"}, + {E_TYPE_STAT_ENTITY_KILL, "stat.killEntity"}, + {E_TYPE_STAT_ENTITY_KILLED_BY, "stat.entityKilledBy"} + }; + for (size_t i = 0; i < ARRAYCOUNT(TypeMap); i++) + { + if (NoCaseCompare(TypeMap[i].m_String, a_Name) == 0) + { + return TypeMap[i].m_Type; + } + } // for i - TypeMap[] + return E_TYPE_DUMMY; +} + + + + + +cObjective::cObjective(const AString & a_Name, const AString & a_DisplayName, cObjective::eType a_Type, cWorld * a_World) + : m_DisplayName(a_DisplayName) + , m_Name(a_Name) + , m_Type(a_Type) + , m_World(a_World) +{ +} + + + + + +void cObjective::Reset(void) +{ + for (cScoreMap::iterator it = m_Scores.begin(); it != m_Scores.end(); ++it) + { + m_World->BroadcastScoreUpdate(m_Name, it->first, 0, 1); + } + + m_Scores.clear(); +} + + + + + +cObjective::Score cObjective::GetScore(const AString & a_Name) const +{ + cScoreMap::const_iterator it = m_Scores.find(a_Name); + + if (it == m_Scores.end()) + { + return 0; + } + else + { + return it->second; + } +} + + + + + +void cObjective::SetScore(const AString & a_Name, cObjective::Score a_Score) +{ + m_Scores[a_Name] = a_Score; + + m_World->BroadcastScoreUpdate(m_Name, a_Name, a_Score, 0); +} + + + + + +void cObjective::ResetScore(const AString & a_Name) +{ + m_Scores.erase(a_Name); + + m_World->BroadcastScoreUpdate(m_Name, a_Name, 0, 1); +} + + + + + +cObjective::Score cObjective::AddScore(const AString & a_Name, cObjective::Score a_Delta) +{ + // TODO 2014-01-19 xdot: Potential optimization - Reuse iterator + Score NewScore = m_Scores[a_Name] + a_Delta; + + SetScore(a_Name, NewScore); + + return NewScore; +} + + + + + +cObjective::Score cObjective::SubScore(const AString & a_Name, cObjective::Score a_Delta) +{ + // TODO 2014-01-19 xdot: Potential optimization - Reuse iterator + Score NewScore = m_Scores[a_Name] - a_Delta; + + SetScore(a_Name, NewScore); + + return NewScore; +} + + + + + +void cObjective::SetDisplayName(const AString & a_Name) +{ + m_DisplayName = a_Name; + + m_World->BroadcastScoreboardObjective(m_Name, m_DisplayName, 2); +} + + + + + +void cObjective::SendTo(cClientHandle & a_Client) +{ + a_Client.SendScoreboardObjective(m_Name, m_DisplayName, 0); + + for (cScoreMap::const_iterator it = m_Scores.begin(); it != m_Scores.end(); ++it) + { + a_Client.SendScoreUpdate(m_Name, it->first, it->second, 0); + } +} + + + + + +cTeam::cTeam(const AString & a_Name, const AString & a_DisplayName, + const AString & a_Prefix, const AString & a_Suffix) + : m_AllowsFriendlyFire(true) + , m_CanSeeFriendlyInvisible(false) + , m_Name(a_Name) + , m_DisplayName(a_DisplayName) + , m_Prefix(a_Prefix) + , m_Suffix(a_Suffix) +{} + + + + + +bool cTeam::AddPlayer(const AString & a_Name) +{ + return m_Players.insert(a_Name).second; +} + + + + + +bool cTeam::RemovePlayer(const AString & a_Name) +{ + return m_Players.erase(a_Name) > 0; +} + + + + + +bool cTeam::HasPlayer(const AString & a_Name) const +{ + cPlayerNameSet::const_iterator it = m_Players.find(a_Name); + + return it != m_Players.end(); +} + + + + + +void cTeam::Reset(void) +{ + // TODO 2014-01-22 xdot: Inform online players + + m_Players.clear(); +} + + + + +unsigned int cTeam::GetNumPlayers(void) const +{ + return m_Players.size(); +} + + + + + +cScoreboard::cScoreboard(cWorld * a_World) : m_World(a_World) +{ + for (int i = 0; i < (int) E_DISPLAY_SLOT_COUNT; ++i) + { + m_Display[i] = NULL; + } +} + + + + + +cObjective* cScoreboard::RegisterObjective(const AString & a_Name, const AString & a_DisplayName, cObjective::eType a_Type) +{ + cObjective Objective(a_Name, a_DisplayName, a_Type, m_World); + + std::pair<cObjectiveMap::iterator, bool> Status = m_Objectives.insert(cNamedObjective(a_Name, Objective)); + + if (Status.second) + { + ASSERT(m_World != NULL); + m_World->BroadcastScoreboardObjective(a_Name, a_DisplayName, 0); + + return &Status.first->second; + } + else + { + return NULL; + } +} + + + + + +bool cScoreboard::RemoveObjective(const AString & a_Name) +{ + cCSLock Lock(m_CSObjectives); + + cObjectiveMap::iterator it = m_Objectives.find(a_Name); + + if (it == m_Objectives.end()) + { + return false; + } + + m_Objectives.erase(it); + + ASSERT(m_World != NULL); + m_World->BroadcastScoreboardObjective(it->second.GetName(), it->second.GetDisplayName(), 1); + + return true; +} + + + + + +cObjective * cScoreboard::GetObjective(const AString & a_Name) +{ + cCSLock Lock(m_CSObjectives); + + cObjectiveMap::iterator it = m_Objectives.find(a_Name); + + if (it == m_Objectives.end()) + { + return NULL; + } + else + { + return &it->second; + } +} + + + + + +cTeam * cScoreboard::RegisterTeam( + const AString & a_Name, const AString & a_DisplayName, + const AString & a_Prefix, const AString & a_Suffix +) +{ + cTeam Team(a_Name, a_DisplayName, a_Prefix, a_Suffix); + + std::pair<cTeamMap::iterator, bool> Status = m_Teams.insert(cNamedTeam(a_Name, Team)); + + return Status.second ? &Status.first->second : NULL; +} + + + + + +bool cScoreboard::RemoveTeam(const AString & a_Name) +{ + cCSLock Lock(m_CSTeams); + + cTeamMap::iterator it = m_Teams.find(a_Name); + + if (it == m_Teams.end()) + { + return false; + } + + m_Teams.erase(it); + + return true; +} + + + + + +cTeam * cScoreboard::GetTeam(const AString & a_Name) +{ + cCSLock Lock(m_CSTeams); + + cTeamMap::iterator it = m_Teams.find(a_Name); + + if (it == m_Teams.end()) + { + return NULL; + } + else + { + return &it->second; + } +} + + + + + +cTeam * cScoreboard::QueryPlayerTeam(const AString & a_Name) +{ + cCSLock Lock(m_CSTeams); + + for (cTeamMap::iterator it = m_Teams.begin(); it != m_Teams.end(); ++it) + { + if (it->second.HasPlayer(a_Name)) + { + return &it->second; + } + } + + return NULL; +} + + + + + +void cScoreboard::SetDisplay(const AString & a_Objective, eDisplaySlot a_Slot) +{ + ASSERT(a_Slot < E_DISPLAY_SLOT_COUNT); + + cObjective * Objective = GetObjective(a_Objective); + + SetDisplay(Objective, a_Slot); +} + + + + + +void cScoreboard::SetDisplay(cObjective * a_Objective, eDisplaySlot a_Slot) +{ + m_Display[a_Slot] = a_Objective; + + ASSERT(m_World != NULL); + m_World->BroadcastDisplayObjective(a_Objective ? a_Objective->GetName() : "", a_Slot); +} + + + + + +cObjective * cScoreboard::GetObjectiveIn(eDisplaySlot a_Slot) +{ + ASSERT(a_Slot < E_DISPLAY_SLOT_COUNT); + + return m_Display[a_Slot]; +} + + + + + +void cScoreboard::ForEachObjectiveWith(cObjective::eType a_Type, cObjectiveCallback& a_Callback) +{ + cCSLock Lock(m_CSObjectives); + + for (cObjectiveMap::iterator it = m_Objectives.begin(); it != m_Objectives.end(); ++it) + { + if (it->second.GetType() == a_Type) + { + // Call callback + if (a_Callback.Item(&it->second)) + { + return; + } + } + } +} + + + + + +void cScoreboard::SendTo(cClientHandle & a_Client) +{ + cCSLock Lock(m_CSObjectives); + + for (cObjectiveMap::iterator it = m_Objectives.begin(); it != m_Objectives.end(); ++it) + { + it->second.SendTo(a_Client); + } + + for (int i = 0; i < (int) E_DISPLAY_SLOT_COUNT; ++i) + { + // Avoid race conditions + cObjective * Objective = m_Display[i]; + + if (Objective) + { + a_Client.SendDisplayObjective(Objective->GetName(), (eDisplaySlot) i); + } + } +} + + + + + +unsigned int cScoreboard::GetNumObjectives(void) const +{ + return m_Objectives.size(); +} + + + + + +unsigned int cScoreboard::GetNumTeams(void) const +{ + return m_Teams.size(); +} + + + + + diff --git a/src/Scoreboard.h b/src/Scoreboard.h new file mode 100644 index 000000000..f64ba2bce --- /dev/null +++ b/src/Scoreboard.h @@ -0,0 +1,276 @@ + +// Scoreboard.h + +// Implementation of a scoreboard that keeps track of specified objectives + + + + + +#pragma once + + + + + +class cObjective; +class cWorld; + +typedef cItemCallback<cObjective> cObjectiveCallback; + + + + + +// tolua_begin +class cObjective +{ +public: + + typedef int Score; + + enum eType + { + E_TYPE_DUMMY, + + E_TYPE_DEATH_COUNT, + E_TYPE_PLAYER_KILL_COUNT, + E_TYPE_TOTAL_KILL_COUNT, + E_TYPE_HEALTH, + + E_TYPE_ACHIEVEMENT, + + E_TYPE_STAT, + E_TYPE_STAT_ITEM_CRAFT, + E_TYPE_STAT_ITEM_USE, + E_TYPE_STAT_ITEM_BREAK, + + E_TYPE_STAT_BLOCK_MINE, + E_TYPE_STAT_ENTITY_KILL, + E_TYPE_STAT_ENTITY_KILLED_BY + }; + + // tolua_end + + static AString TypeToString(eType a_Type); + + static eType StringToType(const AString & a_Name); + +public: + + cObjective(const AString & a_Name, const AString & a_DisplayName, eType a_Type, cWorld * a_World); + + // tolua_begin + + eType GetType(void) const { return m_Type; } + + const AString & GetName(void) const { return m_Name; } + const AString & GetDisplayName(void) const { return m_DisplayName; } + + /// Resets the objective + void Reset(void); + + /// Returns the score of the specified player + Score GetScore(const AString & a_Name) const; + + /// Sets the score of the specified player + void SetScore(const AString & a_Name, Score a_Score); + + /// Resets the score of the specified player + void ResetScore(const AString & a_Name); + + /// Adds a_Delta and returns the new score + Score AddScore(const AString & a_Name, Score a_Delta); + + /// Subtracts a_Delta and returns the new score + Score SubScore(const AString & a_Name, Score a_Delta); + + void SetDisplayName(const AString & a_Name); + + // tolua_end + + /// Send this objective to the specified client + void SendTo(cClientHandle & a_Client); + +private: + + typedef std::pair<AString, Score> cTrackedPlayer; + + typedef std::map<AString, Score> cScoreMap; + + cScoreMap m_Scores; + + AString m_DisplayName; + AString m_Name; + + eType m_Type; + + cWorld * m_World; + + friend class cScoreboardSerializer; + +}; + + + + + +// tolua_begin +class cTeam +{ +public: + + // tolua_end + + cTeam( + const AString & a_Name, const AString & a_DisplayName, + const AString & a_Prefix, const AString & a_Suffix + ); + + /// Adds a new player to the team + bool AddPlayer(const AString & a_Name); + + /// Removes a player from the team + bool RemovePlayer(const AString & a_Name); + + /// Returns whether the specified player is in this team + bool HasPlayer(const AString & a_Name) const; + + /// Removes all registered players + void Reset(void); + + // tolua_begin + + /// Returns the number of registered players + unsigned int GetNumPlayers(void) const; + + bool AllowsFriendlyFire(void) const { return m_AllowsFriendlyFire; } + bool CanSeeFriendlyInvisible(void) const { return m_CanSeeFriendlyInvisible; } + + const AString & GetDisplayName(void) const { return m_DisplayName; } + const AString & GetName(void) const { return m_DisplayName; } + + const AString & GetPrefix(void) const { return m_Prefix; } + const AString & GetSuffix(void) const { return m_Suffix; } + + void SetFriendlyFire(bool a_Flag) { m_AllowsFriendlyFire = a_Flag; } + void SetCanSeeFriendlyInvisible(bool a_Flag) { m_CanSeeFriendlyInvisible = a_Flag; } + + void SetDisplayName(const AString & a_Name); + + void SetPrefix(const AString & a_Prefix) { m_Prefix = a_Prefix; } + void SetSuffix(const AString & a_Suffix) { m_Suffix = a_Suffix; } + + // tolua_end + +private: + + typedef std::set<AString> cPlayerNameSet; + + bool m_AllowsFriendlyFire; + bool m_CanSeeFriendlyInvisible; + + AString m_DisplayName; + AString m_Name; + + AString m_Prefix; + AString m_Suffix; + + cPlayerNameSet m_Players; + + friend class cScoreboardSerializer; + +}; + + + + + +// tolua_begin +class cScoreboard +{ +public: + + enum eDisplaySlot + { + E_DISPLAY_SLOT_LIST = 0, + E_DISPLAY_SLOT_SIDEBAR, + E_DISPLAY_SLOT_NAME, + + E_DISPLAY_SLOT_COUNT + }; + + // tolua_end + + +public: + + cScoreboard(cWorld * a_World); + + // tolua_begin + + /// Registers a new scoreboard objective, returns the cObjective instance, NULL on name collision + cObjective * RegisterObjective(const AString & a_Name, const AString & a_DisplayName, cObjective::eType a_Type); + + /// Removes a registered objective, returns true if operation was successful + bool RemoveObjective(const AString & a_Name); + + /// Retrieves the objective with the specified name, NULL if not found + cObjective * GetObjective(const AString & a_Name); + + /// Registers a new team, returns the cTeam instance, NULL on name collision + cTeam * RegisterTeam(const AString & a_Name, const AString & a_DisplayName, const AString & a_Prefix, const AString & a_Suffix); + + /// Removes a registered team, returns true if operation was successful + bool RemoveTeam(const AString & a_Name); + + /// Retrieves the team with the specified name, NULL if not found + cTeam * GetTeam(const AString & a_Name); + + cTeam * QueryPlayerTeam(const AString & a_Name); // WARNING: O(n logn) + + void SetDisplay(const AString & a_Objective, eDisplaySlot a_Slot); + + void SetDisplay(cObjective * a_Objective, eDisplaySlot a_Slot); + + cObjective * GetObjectiveIn(eDisplaySlot a_Slot); + + /// Execute callback for each objective with the specified type + void ForEachObjectiveWith(cObjective::eType a_Type, cObjectiveCallback& a_Callback); + + unsigned int GetNumObjectives(void) const; + + unsigned int GetNumTeams(void) const; + + // tolua_end + + /// Send this scoreboard to the specified client + void SendTo(cClientHandle & a_Client); + + +private: + + typedef std::pair<AString, cObjective> cNamedObjective; + typedef std::pair<AString, cTeam> cNamedTeam; + + typedef std::map<AString, cObjective> cObjectiveMap; + typedef std::map<AString, cTeam> cTeamMap; + + // TODO 2014-01-19 xdot: Potential optimization - Sort objectives based on type + cCriticalSection m_CSObjectives; + cObjectiveMap m_Objectives; + + cCriticalSection m_CSTeams; + cTeamMap m_Teams; + + cWorld * m_World; + + cObjective* m_Display[E_DISPLAY_SLOT_COUNT]; + + friend class cScoreboardSerializer; + +} ; + + + + diff --git a/src/Server.cpp b/src/Server.cpp index 4d551e164..ba2b46d55 100644 --- a/src/Server.cpp +++ b/src/Server.cpp @@ -194,7 +194,7 @@ bool cServer::InitServer(cIniFile & a_SettingsIni) m_PlayerCount = 0; m_PlayerCountDiff = 0; - m_FaviconData = Base64Encode(cFile::ReadWholeFile("favicon.png")); // Will return empty string if file nonexistant; client doesn't mind + m_FaviconData = Base64Encode(cFile::ReadWholeFile(FILE_IO_PREFIX + AString("favicon.png"))); // Will return empty string if file nonexistant; client doesn't mind if (m_bIsConnected) { @@ -237,7 +237,8 @@ bool cServer::InitServer(cIniFile & a_SettingsIni) m_bIsConnected = true; m_ServerID = "-"; - if (a_SettingsIni.GetValueSetB("Authentication", "Authenticate", true)) + m_ShouldAuthenticate = a_SettingsIni.GetValueSetB("Authentication", "Authenticate", true); + if (m_ShouldAuthenticate) { MTRand mtrand1; unsigned int r1 = (mtrand1.randInt() % 1147483647) + 1000000000; @@ -284,17 +285,9 @@ int cServer::GetNumPlayers(void) void cServer::PrepareKeys(void) { - // TODO: Save and load key for persistence across sessions - // But generating the key takes only a moment, do we even need that? - LOGD("Generating protocol encryption keypair..."); - - time_t CurTime = time(NULL); - CryptoPP::RandomPool rng; - rng.Put((const byte *)&CurTime, sizeof(CurTime)); - m_PrivateKey.GenerateRandomWithKeySize(rng, 1024); - CryptoPP::RSA::PublicKey pk(m_PrivateKey); - m_PublicKey = pk; + VERIFY(m_PrivateKey.Generate(1024)); + m_PublicKeyDER = m_PrivateKey.GetPubKeyDER(); } @@ -492,7 +485,7 @@ void cServer::ExecuteConsoleCommand(const AString & a_Cmd, cCommandOutputCallbac } } #endif - + if (cPluginManager::Get()->ExecuteConsoleCommand(split, a_Output)) { a_Output.Finished(); diff --git a/src/Server.h b/src/Server.h index bb55e81b6..b5280c59d 100644 --- a/src/Server.h +++ b/src/Server.h @@ -23,8 +23,7 @@ #pragma warning(disable:4702) #endif -#include "cryptopp/rsa.h" -#include "cryptopp/randpool.h" +#include "Crypto.h" #ifdef _MSC_VER #pragma warning(pop) @@ -72,13 +71,13 @@ public: // tolua_export bool Command(cClientHandle & a_Client, AString & a_Cmd); - /// Executes the console command, sends output through the specified callback + /** Executes the console command, sends output through the specified callback */ void ExecuteConsoleCommand(const AString & a_Cmd, cCommandOutputCallback & a_Output); - /// Lists all available console commands and their helpstrings + /** Lists all available console commands and their helpstrings */ void PrintHelp(const AStringVector & a_Split, cCommandOutputCallback & a_Output); - /// Binds the built-in console commands with the plugin manager + /** Binds the built-in console commands with the plugin manager */ static void BindBuiltInConsoleCommands(void); void Shutdown(void); @@ -98,26 +97,28 @@ public: // tolua_export void RemoveClient(const cClientHandle * a_Client); // Removes the clienthandle from m_SocketThreads - /// Don't tick a_Client anymore, it will be ticked from its cPlayer instead + /** Don't tick a_Client anymore, it will be ticked from its cPlayer instead */ void ClientMovedToWorld(const cClientHandle * a_Client); - /// Notifies the server that a player was created; the server uses this to adjust the number of players + /** Notifies the server that a player was created; the server uses this to adjust the number of players */ void PlayerCreated(const cPlayer * a_Player); - /// Notifies the server that a player is being destroyed; the server uses this to adjust the number of players + /** Notifies the server that a player is being destroyed; the server uses this to adjust the number of players */ void PlayerDestroying(const cPlayer * a_Player); /** Returns base64 encoded favicon data (obtained from favicon.png) */ const AString & GetFaviconData(void) const { return m_FaviconData; } - CryptoPP::RSA::PrivateKey & GetPrivateKey(void) { return m_PrivateKey; } - CryptoPP::RSA::PublicKey & GetPublicKey (void) { return m_PublicKey; } + cRSAPrivateKey & GetPrivateKey(void) { return m_PrivateKey; } + const AString & GetPublicKeyDER(void) const { return m_PublicKeyDER; } + + bool ShouldAuthenticate(void) const { return m_ShouldAuthenticate; } private: friend class cRoot; // so cRoot can create and destroy cServer - /// When NotifyClientWrite() is called, it is queued for this thread to process (to avoid deadlocks between cSocketThreads, cClientHandle and cChunkMap) + /** When NotifyClientWrite() is called, it is queued for this thread to process (to avoid deadlocks between cSocketThreads, cClientHandle and cChunkMap) */ class cNotifyWriteThread : public cIsThread { @@ -141,7 +142,7 @@ private: void NotifyClientWrite(const cClientHandle * a_Client); } ; - /// The server tick thread takes care of the players who aren't yet spawned in a world + /** The server tick thread takes care of the players who aren't yet spawned in a world */ class cTickThread : public cIsThread { @@ -180,8 +181,11 @@ private: bool m_bRestarting; - CryptoPP::RSA::PrivateKey m_PrivateKey; - CryptoPP::RSA::PublicKey m_PublicKey; + /** The private key used for the assymetric encryption start in the protocols */ + cRSAPrivateKey m_PrivateKey; + + /** Public key for m_PrivateKey, ASN1-DER-encoded */ + AString m_PublicKeyDER; cRCONServer m_RCONServer; @@ -193,18 +197,22 @@ private: cTickThread m_TickThread; cEvent m_RestartEvent; - /// The server ID used for client authentication + /** The server ID used for client authentication */ AString m_ServerID; + /** If true, players will be online-authenticated agains Mojang servers. + This setting is the same as the "online-mode" setting in Vanilla. */ + bool m_ShouldAuthenticate; + cServer(void); - /// Loads, or generates, if missing, RSA keys for protocol encryption + /** Loads, or generates, if missing, RSA keys for protocol encryption */ void PrepareKeys(void); bool Tick(float a_Dt); - /// Ticks the clients in m_Clients, manages the list in respect to removing clients + /** Ticks the clients in m_Clients, manages the list in respect to removing clients */ void TickClients(float a_Dt); // cListenThread::cCallback overrides: diff --git a/src/StringUtils.cpp b/src/StringUtils.cpp index 0dbd41c12..3fe75d611 100644 --- a/src/StringUtils.cpp +++ b/src/StringUtils.cpp @@ -833,7 +833,8 @@ AString Base64Encode(const AString & a_Input) short GetBEShort(const char * a_Mem) { - return (((short)a_Mem[0]) << 8) | a_Mem[1]; + const Byte * Bytes = (const Byte *)a_Mem; + return (Bytes[0] << 8) | Bytes[1]; } @@ -842,7 +843,8 @@ short GetBEShort(const char * a_Mem) int GetBEInt(const char * a_Mem) { - return (((int)a_Mem[0]) << 24) | (((int)a_Mem[1]) << 16) | (((int)a_Mem[2]) << 8) | a_Mem[3]; + const Byte * Bytes = (const Byte *)a_Mem; + return (Bytes[0] << 24) | (Bytes[1] << 16) | (Bytes[2] << 8) | Bytes[3]; } diff --git a/src/UI/Window.cpp b/src/UI/Window.cpp index 3ffeff7a0..1a8456f70 100644 --- a/src/UI/Window.cpp +++ b/src/UI/Window.cpp @@ -13,7 +13,8 @@ #include "../BlockEntities/DropSpenserEntity.h" #include "../BlockEntities/EnderChestEntity.h" #include "../BlockEntities/HopperEntity.h" - +#include "../Root.h" +#include "../Bindings/PluginManager.h" @@ -169,6 +170,7 @@ void cWindow::Clicked( const cItem & a_ClickedItem ) { + cPluginManager * PlgMgr = cRoot::Get()->GetPluginManager(); if (a_WindowID != m_WindowID) { LOGWARNING("%s: Wrong window ID (exp %d, got %d) received from \"%s\"; ignoring click.", __FUNCTION__, m_WindowID, a_WindowID, a_Player.GetName().c_str()); @@ -179,14 +181,35 @@ void cWindow::Clicked( { case caRightClickOutside: { + if (PlgMgr->CallHookPlayerTossingItem(a_Player)) + { + // A plugin doesn't agree with the tossing. The plugin itself is responsible for handling the consequences (possible inventory mismatch) + return; + } + if (a_Player.IsGameModeCreative()) + { + a_Player.TossPickup(a_ClickedItem); + } + // Toss one of the dragged items: - a_Player.TossItem(true); + a_Player.TossHeldItem(); return; } case caLeftClickOutside: { + if (PlgMgr->CallHookPlayerTossingItem(a_Player)) + { + // A plugin doesn't agree with the tossing. The plugin itself is responsible for handling the consequences (possible inventory mismatch) + return; + } + + if (a_Player.IsGameModeCreative()) + { + a_Player.TossPickup(a_ClickedItem); + } + // Toss all dragged items: - a_Player.TossItem(true, a_Player.GetDraggingItem().m_ItemCount); + a_Player.TossHeldItem(a_Player.GetDraggingItem().m_ItemCount); return; } case caLeftClickOutsideHoldNothing: @@ -263,7 +286,7 @@ bool cWindow::ClosedByPlayer(cPlayer & a_Player, bool a_CanRefuse) if (a_Player.IsDraggingItem()) { LOGD("Player holds item! Dropping it..."); - a_Player.TossItem(true, a_Player.GetDraggingItem().m_ItemCount); + a_Player.TossHeldItem(a_Player.GetDraggingItem().m_ItemCount); } cClientHandle * ClientHandle = a_Player.GetClientHandle(); diff --git a/src/World.cpp b/src/World.cpp index 5e9f4a38a..5739098e4 100644 --- a/src/World.cpp +++ b/src/World.cpp @@ -1,4 +1,3 @@ - #include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules #include "BlockID.h" @@ -12,6 +11,7 @@ #include "ChunkMap.h" #include "Generating/ChunkDesc.h" #include "OSSupport/Timer.h" +#include "WorldStorage/ScoreboardSerializer.h" // Entities (except mobs): #include "Entities/ExpOrb.h" @@ -21,6 +21,8 @@ #include "Entities/Player.h" #include "Entities/TNTEntity.h" +#include "BlockEntities/CommandBlockEntity.h" + // Simulators: #include "Simulator/SimulatorManager.h" #include "Simulator/FloodyFluidSimulator.h" @@ -231,7 +233,11 @@ cWorld::cWorld(const AString & a_WorldName) : m_WorldName(a_WorldName), m_IniFileName(m_WorldName + "/world.ini"), m_StorageSchema("Default"), +#ifdef __arm__ + m_StorageCompressionFactor(0), +#else m_StorageCompressionFactor(6), +#endif m_IsSpawnExplicitlySet(false), m_WorldAgeSecs(0), m_TimeOfDaySecs(0), @@ -242,11 +248,16 @@ cWorld::cWorld(const AString & a_WorldName) : m_Weather(eWeather_Sunny), m_WeatherInterval(24000), // Guaranteed 1 day of sunshine at server start :) m_GeneratorCallbacks(*this), - m_TickThread(*this) + m_TickThread(*this), + m_Scoreboard(this) { LOGD("cWorld::cWorld(\"%s\")", a_WorldName.c_str()); cFile::CreateFolder(FILE_IO_PREFIX + m_WorldName); + + // Load the scoreboard + cScoreboardSerializer Serializer(m_WorldName, &m_Scoreboard); + Serializer.Load(); } @@ -266,6 +277,10 @@ cWorld::~cWorld() m_Storage.WaitForFinish(); + // Unload the scoreboard + cScoreboardSerializer Serializer(m_WorldName, &m_Scoreboard); + Serializer.Save(); + delete m_ChunkMap; } @@ -513,7 +528,7 @@ void cWorld::Start(void) } m_StorageSchema = IniFile.GetValueSet ("Storage", "Schema", m_StorageSchema); - m_StorageCompressionFactor = IniFile.GetValueSetI ("Storage", "CompressionFactor", m_StorageCompressionFactor); + m_StorageCompressionFactor = IniFile.GetValueSetI("Storage", "CompressionFactor", m_StorageCompressionFactor); m_MaxCactusHeight = IniFile.GetValueSetI("Plants", "MaxCactusHeight", 3); m_MaxSugarcaneHeight = IniFile.GetValueSetI("Plants", "MaxSugarcaneHeight", 3); m_IsCactusBonemealable = IniFile.GetValueSetB("Plants", "IsCactusBonemealable", false); @@ -530,6 +545,8 @@ void cWorld::Start(void) m_bEnabledPVP = IniFile.GetValueSetB("PVP", "Enabled", true); m_IsDeepSnowEnabled = IniFile.GetValueSetB("Physics", "DeepSnow", false); m_ShouldLavaSpawnFire = IniFile.GetValueSetB("Physics", "ShouldLavaSpawnFire", true); + m_bCommandBlocksEnabled = IniFile.GetValueSetB("Mechanics", "CommandBlocksEnabled", false); + m_VillagersShouldHarvestCrops = IniFile.GetValueSetB("Monsters", "VillagersShouldHarvestCrops", true); m_GameMode = (eGameMode)IniFile.GetValueSetI("GameMode", "GameMode", m_GameMode); @@ -1981,6 +1998,60 @@ void cWorld::BroadcastRemoveEntityEffect(const cEntity & a_Entity, int a_EffectI +void cWorld::BroadcastScoreboardObjective(const AString & a_Name, const AString & a_DisplayName, Byte a_Mode) +{ + cCSLock Lock(m_CSPlayers); + for (cPlayerList::iterator itr = m_Players.begin(); itr != m_Players.end(); ++itr) + { + cClientHandle * ch = (*itr)->GetClientHandle(); + if ((ch == NULL) || !ch->IsLoggedIn() || ch->IsDestroyed()) + { + continue; + } + ch->SendScoreboardObjective(a_Name, a_DisplayName, a_Mode); + } +} + + + + + +void cWorld::BroadcastScoreUpdate(const AString & a_Objective, const AString & a_Player, cObjective::Score a_Score, Byte a_Mode) +{ + cCSLock Lock(m_CSPlayers); + for (cPlayerList::iterator itr = m_Players.begin(); itr != m_Players.end(); ++itr) + { + cClientHandle * ch = (*itr)->GetClientHandle(); + if ((ch == NULL) || !ch->IsLoggedIn() || ch->IsDestroyed()) + { + continue; + } + ch->SendScoreUpdate(a_Objective, a_Player, a_Score, a_Mode); + } +} + + + + + +void cWorld::BroadcastDisplayObjective(const AString & a_Objective, cScoreboard::eDisplaySlot a_Display) +{ + cCSLock Lock(m_CSPlayers); + for (cPlayerList::iterator itr = m_Players.begin(); itr != m_Players.end(); ++itr) + { + cClientHandle * ch = (*itr)->GetClientHandle(); + if ((ch == NULL) || !ch->IsLoggedIn() || ch->IsDestroyed()) + { + continue; + } + ch->SendDisplayObjective(a_Objective, a_Display); + } +} + + + + + void cWorld::BroadcastSoundEffect(const AString & a_SoundName, int a_SrcX, int a_SrcY, int a_SrcZ, float a_Volume, float a_Pitch, const cClientHandle * a_Exclude) { m_ChunkMap->BroadcastSoundEffect(a_SoundName, a_SrcX, a_SrcY, a_SrcZ, a_Volume, a_Pitch, a_Exclude); @@ -2352,7 +2423,7 @@ bool cWorld::FindAndDoWithPlayer(const AString & a_PlayerNameHint, cPlayerListCa // TODO: This interface is dangerous! -cPlayer * cWorld::FindClosestPlayer(const Vector3f & a_Pos, float a_SightLimit) +cPlayer * cWorld::FindClosestPlayer(const Vector3f & a_Pos, float a_SightLimit, bool a_CheckLineOfSight) { cTracer LineOfSight(this); @@ -2367,7 +2438,12 @@ cPlayer * cWorld::FindClosestPlayer(const Vector3f & a_Pos, float a_SightLimit) if (Distance < ClosestDistance) { - if (!LineOfSight.Trace(a_Pos,(Pos - a_Pos),(int)(Pos - a_Pos).Length())) + if (a_CheckLineOfSight && !LineOfSight.Trace(a_Pos,(Pos - a_Pos),(int)(Pos - a_Pos).Length())) + { + ClosestDistance = Distance; + ClosestPlayer = *itr; + } + else { ClosestDistance = Distance; ClosestPlayer = *itr; @@ -2547,6 +2623,28 @@ bool cWorld::UpdateSign(int a_BlockX, int a_BlockY, int a_BlockZ, const AString +bool cWorld::SetCommandBlockCommand(int a_BlockX, int a_BlockY, int a_BlockZ, const AString & a_Command) +{ + class cUpdateCommandBlock : public cCommandBlockCallback + { + AString m_Command; + public: + cUpdateCommandBlock(const AString & a_Command) : m_Command(a_Command) {} + + virtual bool Item(cCommandBlockEntity * a_CommandBlock) override + { + a_CommandBlock->SetCommand(m_Command); + return false; + } + } CmdBlockCB (a_Command); + + return DoWithCommandBlockAt(a_BlockX, a_BlockY, a_BlockZ, CmdBlockCB); +} + + + + + void cWorld::ChunksStay(const cChunkCoordsList & a_Chunks, bool a_Stay) { m_ChunkMap->ChunksStay(a_Chunks, a_Stay); diff --git a/src/World.h b/src/World.h index 930d9d421..4d5659ee6 100644 --- a/src/World.h +++ b/src/World.h @@ -22,6 +22,8 @@ #include "Item.h" #include "Mobs/Monster.h" #include "Entities/ProjectileEntity.h" +#include "ForEachChunkProvider.h" +#include "Scoreboard.h" @@ -60,7 +62,7 @@ typedef cItemCallback<cCommandBlockEntity> cCommandBlockCallback; // tolua_begin -class cWorld +class cWorld : public cForEachChunkProvider { public: @@ -136,6 +138,8 @@ public: bool ShouldLavaSpawnFire(void) const { return m_ShouldLavaSpawnFire; } + bool VillagersShouldHarvestCrops(void) const { return m_VillagersShouldHarvestCrops; } + eDimension GetDimension(void) const { return m_Dimension; } /** Returns the world height at the specified coords; waits for the chunk to get loaded / generated */ @@ -169,6 +173,9 @@ public: 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_ParticleAmmount, cClientHandle * a_Exclude = NULL); void BroadcastPlayerListItem (const cPlayer & a_Player, bool a_IsOnline, const cClientHandle * a_Exclude = NULL); void BroadcastRemoveEntityEffect (const cEntity & a_Entity, int a_EffectID, const cClientHandle * a_Exclude = NULL); + void BroadcastScoreboardObjective(const AString & a_Name, const AString & a_DisplayName, Byte a_Mode); + void BroadcastScoreUpdate (const AString & a_Objective, const AString & a_Player, cObjective::Score a_Score, Byte a_Mode); + void BroadcastDisplayObjective (const AString & a_Objective, cScoreboard::eDisplaySlot a_Display); void BroadcastSoundEffect (const AString & a_SoundName, int a_SrcX, int a_SrcY, int a_SrcZ, float a_Volume, float a_Pitch, const cClientHandle * a_Exclude = NULL); // tolua_export a_Src coords are Block * 8 void BroadcastSoundParticleEffect(int a_EffectID, int a_SrcX, int a_SrcY, int a_SrcZ, int a_Data, const cClientHandle * a_Exclude = NULL); // tolua_export void BroadcastSpawnEntity (cEntity & a_Entity, const cClientHandle * a_Exclude = NULL); @@ -235,7 +242,7 @@ public: bool FindAndDoWithPlayer(const AString & a_PlayerNameHint, cPlayerListCallback & a_Callback); // >> EXPORTED IN MANUALBINDINGS << // TODO: This interface is dangerous - rewrite to DoWithClosestPlayer(pos, sight, action) - cPlayer * FindClosestPlayer(const Vector3f & a_Pos, float a_SightLimit); + cPlayer * FindClosestPlayer(const Vector3f & a_Pos, float a_SightLimit, bool a_CheckLineOfSight = true); void SendPlayerList(cPlayer * a_DestPlayer); // Sends playerlist to the player @@ -292,6 +299,9 @@ public: /** Sets the sign text, asking plugins for permission first. a_Player is the player who this change belongs to, may be NULL. Returns true if sign text changed. Same as SetSignLines() */ bool UpdateSign(int a_X, int a_Y, int a_Z, const AString & a_Line1, const AString & a_Line2, const AString & a_Line3, const AString & a_Line4, cPlayer * a_Player = NULL); // Exported in ManualBindings.cpp + /** Sets the command block command. Returns true if command changed. */ + bool SetCommandBlockCommand(int a_BlockX, int a_BlockY, int a_BlockZ, const AString & a_Command); // tolua_export + /** Marks (a_Stay == true) or unmarks (a_Stay == false) chunks as non-unloadable. To be used only by cChunkStay! */ void ChunksStay(const cChunkCoordsList & a_Chunks, bool a_Stay = true); @@ -307,7 +317,7 @@ public: bool IsChunkLighted(int a_ChunkX, int a_ChunkZ); /** Calls the callback for each chunk in the coords specified (all cords are inclusive). Returns true if all chunks have been processed successfully */ - bool ForEachChunkInRect(int a_MinChunkX, int a_MaxChunkX, int a_MinChunkZ, int a_MaxChunkZ, cChunkDataCallback & a_Callback); + virtual bool ForEachChunkInRect(int a_MinChunkX, int a_MaxChunkX, int a_MinChunkZ, int a_MaxChunkZ, cChunkDataCallback & a_Callback); // tolua_begin @@ -353,7 +363,7 @@ public: Prefer cBlockArea::Write() instead, this is the internal implementation; cBlockArea does error checking, too. a_DataTypes is a bitmask of cBlockArea::baXXX constants ORed together. */ - bool WriteBlockArea(cBlockArea & a_Area, int a_MinBlockX, int a_MinBlockY, int a_MinBlockZ, int a_DataTypes); + virtual bool WriteBlockArea(cBlockArea & a_Area, int a_MinBlockX, int a_MinBlockY, int a_MinBlockZ, int a_DataTypes); // tolua_begin @@ -504,6 +514,13 @@ public: /** Returns the name of the world.ini file used by this world */ const AString & GetIniFileName(void) const {return m_IniFileName; } + + /// Returns the associated scoreboard instance + cScoreboard & GetScoreBoard(void) { return m_Scoreboard; } + + bool AreCommandBlocksEnabled(void) const { return m_bCommandBlocksEnabled; } + + void SetCommandBlocksEnabled(bool a_Flag) { m_bCommandBlocksEnabled = a_Flag; } // tolua_end @@ -728,6 +745,7 @@ private: bool m_bEnabledPVP; bool m_IsDeepSnowEnabled; bool m_ShouldLavaSpawnFire; + bool m_VillagersShouldHarvestCrops; std::vector<BlockTickQueueItem *> m_BlockTickQueue; std::vector<BlockTickQueueItem *> m_BlockTickQueueCopy; // Second is for safely removing the objects from the queue @@ -767,11 +785,15 @@ private: bool m_IsPumpkinBonemealable; bool m_IsSaplingBonemealable; bool m_IsSugarcaneBonemealable; + + bool m_bCommandBlocksEnabled; cCriticalSection m_CSFastSetBlock; sSetBlockList m_FastSetBlockQueue; cChunkGenerator m_Generator; + + cScoreboard m_Scoreboard; /** The callbacks that the ChunkGenerator uses to store new chunks and interface to plugins */ cChunkGeneratorCallbacks m_GeneratorCallbacks; diff --git a/src/WorldStorage/NBTChunkSerializer.cpp b/src/WorldStorage/NBTChunkSerializer.cpp index c84128022..9c454c028 100644 --- a/src/WorldStorage/NBTChunkSerializer.cpp +++ b/src/WorldStorage/NBTChunkSerializer.cpp @@ -454,8 +454,8 @@ void cNBTChunkSerializer::AddMonsterEntity(cMonster * a_Monster) } case cMonster::mtWolf: { - // TODO: - // _X: CopyPasta error: m_Writer.AddInt("Profession", ((const cVillager *)a_Monster)->GetVilType()); + m_Writer.AddString("Owner", ((const cWolf *)a_Monster)->GetOwner()); + m_Writer.AddByte("Sitting", ((const cWolf *)a_Monster)->IsSitting()); break; } case cMonster::mtZombie: @@ -652,20 +652,21 @@ void cNBTChunkSerializer::BlockEntity(cBlockEntity * a_Entity) m_Writer.BeginList("TileEntities", TAG_Compound); } m_IsTagOpen = true; - + // Add tile-entity into NBT: switch (a_Entity->GetBlockType()) { - case E_BLOCK_CHEST: AddChestEntity ((cChestEntity *) a_Entity); break; - case E_BLOCK_DISPENSER: AddDispenserEntity ((cDispenserEntity *) a_Entity); break; - case E_BLOCK_DROPPER: AddDropperEntity ((cDropperEntity *) a_Entity); break; - case E_BLOCK_FURNACE: AddFurnaceEntity ((cFurnaceEntity *) a_Entity); break; - case E_BLOCK_HOPPER: AddHopperEntity ((cHopperEntity *) a_Entity); break; + case E_BLOCK_CHEST: AddChestEntity ((cChestEntity *) a_Entity); break; + case E_BLOCK_DISPENSER: AddDispenserEntity ((cDispenserEntity *) a_Entity); break; + case E_BLOCK_DROPPER: AddDropperEntity ((cDropperEntity *) a_Entity); break; + case E_BLOCK_FURNACE: AddFurnaceEntity ((cFurnaceEntity *) a_Entity); break; + case E_BLOCK_HOPPER: AddHopperEntity ((cHopperEntity *) a_Entity); break; case E_BLOCK_SIGN_POST: - case E_BLOCK_WALLSIGN: AddSignEntity ((cSignEntity *) a_Entity); break; - case E_BLOCK_NOTE_BLOCK: AddNoteEntity ((cNoteEntity *) a_Entity); break; - case E_BLOCK_JUKEBOX: AddJukeboxEntity ((cJukeboxEntity *) a_Entity); break; + case E_BLOCK_WALLSIGN: AddSignEntity ((cSignEntity *) a_Entity); break; + case E_BLOCK_NOTE_BLOCK: AddNoteEntity ((cNoteEntity *) a_Entity); break; + case E_BLOCK_JUKEBOX: AddJukeboxEntity ((cJukeboxEntity *) a_Entity); break; case E_BLOCK_COMMAND_BLOCK: AddCommandBlockEntity((cCommandBlockEntity *) a_Entity); break; + default: { ASSERT(!"Unhandled block entity saved into Anvil"); diff --git a/src/WorldStorage/SchematicFileSerializer.cpp b/src/WorldStorage/SchematicFileSerializer.cpp new file mode 100644 index 000000000..45fd967bd --- /dev/null +++ b/src/WorldStorage/SchematicFileSerializer.cpp @@ -0,0 +1,172 @@ + +#include "Globals.h" + +#include "OSSupport/GZipFile.h" +#include "FastNBT.h" + +#include "SchematicFileSerializer.h" + +bool cSchematicFileSerializer::LoadFromSchematicFile(cBlockArea & a_BlockArea, const AString & a_FileName) +{ + // Un-GZip the contents: + AString Contents; + cGZipFile File; + if (!File.Open(a_FileName, cGZipFile::fmRead)) + { + LOG("Cannot open the schematic file \"%s\".", a_FileName.c_str()); + return false; + } + int NumBytesRead = File.ReadRestOfFile(Contents); + if (NumBytesRead < 0) + { + LOG("Cannot read GZipped data in the schematic file \"%s\", error %d", a_FileName.c_str(), NumBytesRead); + return false; + } + File.Close(); + + // Parse the NBT: + cParsedNBT NBT(Contents.data(), Contents.size()); + if (!NBT.IsValid()) + { + LOG("Cannot parse the NBT in the schematic file \"%s\".", a_FileName.c_str()); + return false; + } + + return LoadFromSchematicNBT(a_BlockArea, NBT); +} + + + + + + +bool cSchematicFileSerializer::SaveToSchematicFile(cBlockArea & a_BlockArea, const AString & a_FileName) +{ + cFastNBTWriter Writer("Schematic"); + Writer.AddShort("Width", a_BlockArea.m_SizeX); + Writer.AddShort("Height", a_BlockArea.m_SizeY); + Writer.AddShort("Length", a_BlockArea.m_SizeZ); + Writer.AddString("Materials", "Alpha"); + if (a_BlockArea.HasBlockTypes()) + { + Writer.AddByteArray("Blocks", (const char *)a_BlockArea.m_BlockTypes, a_BlockArea.GetBlockCount()); + } + else + { + AString Dummy(a_BlockArea.GetBlockCount(), 0); + Writer.AddByteArray("Blocks", Dummy.data(), Dummy.size()); + } + if (a_BlockArea.HasBlockMetas()) + { + Writer.AddByteArray("Data", (const char *)a_BlockArea.m_BlockMetas, a_BlockArea.GetBlockCount()); + } + else + { + AString Dummy(a_BlockArea.GetBlockCount(), 0); + Writer.AddByteArray("Data", Dummy.data(), Dummy.size()); + } + // TODO: Save entities and block entities + Writer.BeginList("Entities", TAG_Compound); + Writer.EndList(); + Writer.BeginList("TileEntities", TAG_Compound); + Writer.EndList(); + Writer.Finish(); + + // Save to file + cGZipFile File; + if (!File.Open(a_FileName, cGZipFile::fmWrite)) + { + LOG("Cannot open file \"%s\" for writing.", a_FileName.c_str()); + return false; + } + if (!File.Write(Writer.GetResult())) + { + LOG("Cannot write data to file \"%s\".", a_FileName.c_str()); + return false; + } + return true; +} + + + + + + +bool cSchematicFileSerializer::LoadFromSchematicNBT(cBlockArea & a_BlockArea, cParsedNBT & a_NBT) +{ + int TMaterials = a_NBT.FindChildByName(a_NBT.GetRoot(), "Materials"); + if ((TMaterials > 0) && (a_NBT.GetType(TMaterials) == TAG_String)) + { + AString Materials = a_NBT.GetString(TMaterials); + if (Materials.compare("Alpha") != 0) + { + LOG("Materials tag is present and \"%s\" instead of \"Alpha\". Possibly a wrong-format schematic file.", Materials.c_str()); + return false; + } + } + int TSizeX = a_NBT.FindChildByName(a_NBT.GetRoot(), "Width"); + int TSizeY = a_NBT.FindChildByName(a_NBT.GetRoot(), "Height"); + int TSizeZ = a_NBT.FindChildByName(a_NBT.GetRoot(), "Length"); + if ( + (TSizeX < 0) || (TSizeY < 0) || (TSizeZ < 0) || + (a_NBT.GetType(TSizeX) != TAG_Short) || + (a_NBT.GetType(TSizeY) != TAG_Short) || + (a_NBT.GetType(TSizeZ) != TAG_Short) + ) + { + LOG("Dimensions are missing from the schematic file (%d, %d, %d), (%d, %d, %d)", + TSizeX, TSizeY, TSizeZ, + a_NBT.GetType(TSizeX), a_NBT.GetType(TSizeY), a_NBT.GetType(TSizeZ) + ); + return false; + } + + int SizeX = a_NBT.GetShort(TSizeX); + int SizeY = a_NBT.GetShort(TSizeY); + int SizeZ = a_NBT.GetShort(TSizeZ); + if ((SizeX < 1) || (SizeY < 1) || (SizeZ < 1)) + { + LOG("Dimensions are invalid in the schematic file: %d, %d, %d", SizeX, SizeY, SizeZ); + return false; + } + + int TBlockTypes = a_NBT.FindChildByName(a_NBT.GetRoot(), "Blocks"); + int TBlockMetas = a_NBT.FindChildByName(a_NBT.GetRoot(), "Data"); + if ((TBlockTypes < 0) || (a_NBT.GetType(TBlockTypes) != TAG_ByteArray)) + { + LOG("BlockTypes are invalid in the schematic file: %d", TBlockTypes); + return false; + } + bool AreMetasPresent = (TBlockMetas > 0) && (a_NBT.GetType(TBlockMetas) == TAG_ByteArray); + + a_BlockArea.Clear(); + a_BlockArea.SetSize(SizeX, SizeY, SizeZ, AreMetasPresent ? (cBlockArea::baTypes | cBlockArea::baMetas) : cBlockArea::baTypes); + + // Copy the block types and metas: + int NumBytes = a_BlockArea.m_SizeX * a_BlockArea.m_SizeY * a_BlockArea.m_SizeZ; + if (a_NBT.GetDataLength(TBlockTypes) < NumBytes) + { + LOG("BlockTypes truncated in the schematic file (exp %d, got %d bytes). Loading partial.", + NumBytes, a_NBT.GetDataLength(TBlockTypes) + ); + NumBytes = a_NBT.GetDataLength(TBlockTypes); + } + memcpy(a_BlockArea.m_BlockTypes, a_NBT.GetData(TBlockTypes), NumBytes); + + if (AreMetasPresent) + { + int NumBytes = a_BlockArea.m_SizeX * a_BlockArea.m_SizeY * a_BlockArea.m_SizeZ; + if (a_NBT.GetDataLength(TBlockMetas) < NumBytes) + { + LOG("BlockMetas truncated in the schematic file (exp %d, got %d bytes). Loading partial.", + NumBytes, a_NBT.GetDataLength(TBlockMetas) + ); + NumBytes = a_NBT.GetDataLength(TBlockMetas); + } + memcpy(a_BlockArea.m_BlockMetas, a_NBT.GetData(TBlockMetas), NumBytes); + } + + return true; +} + + diff --git a/src/WorldStorage/SchematicFileSerializer.h b/src/WorldStorage/SchematicFileSerializer.h new file mode 100644 index 000000000..9be2e5b57 --- /dev/null +++ b/src/WorldStorage/SchematicFileSerializer.h @@ -0,0 +1,29 @@ + +#pragma once + +#include "../BlockArea.h" + + + + + +// fwd: FastNBT.h +class cParsedNBT; + + + + +class cSchematicFileSerializer +{ +public: + + /// Loads an area from a .schematic file. Returns true if successful + static bool LoadFromSchematicFile(cBlockArea & a_BlockArea, const AString & a_FileName); + + /// Saves the area into a .schematic file. Returns true if successful + static bool SaveToSchematicFile(cBlockArea & a_BlockArea, const AString & a_FileName); + +private: + /// Loads the area from a schematic file uncompressed and parsed into a NBT tree. Returns true if successful. + static bool LoadFromSchematicNBT(cBlockArea & a_BlockArea, cParsedNBT & a_NBT); +}; diff --git a/src/WorldStorage/ScoreboardSerializer.cpp b/src/WorldStorage/ScoreboardSerializer.cpp new file mode 100644 index 000000000..c65e13f98 --- /dev/null +++ b/src/WorldStorage/ScoreboardSerializer.cpp @@ -0,0 +1,377 @@ + +// ScoreboardSerializer.cpp + + +#include "Globals.h" +#include "ScoreboardSerializer.h" +#include "../StringCompression.h" +#include "zlib/zlib.h" +#include "FastNBT.h" + +#include "../Scoreboard.h" + + + + + +cScoreboardSerializer::cScoreboardSerializer(const AString & a_WorldName, cScoreboard* a_ScoreBoard) + : m_ScoreBoard(a_ScoreBoard) +{ + AString DataPath; + Printf(DataPath, "%s/data", a_WorldName.c_str()); + + m_Path = DataPath + "/scoreboard.dat"; + + cFile::CreateFolder(FILE_IO_PREFIX + DataPath); +} + + + + + +bool cScoreboardSerializer::Load(void) +{ + AString Data = cFile::ReadWholeFile(FILE_IO_PREFIX + m_Path); + if (Data.empty()) + { + return false; + } + + AString Uncompressed; + int res = UncompressStringGZIP(Data.data(), Data.size(), Uncompressed); + + if (res != Z_OK) + { + return false; + } + + // Parse the NBT data: + cParsedNBT NBT(Uncompressed.data(), Uncompressed.size()); + if (!NBT.IsValid()) + { + // NBT Parsing failed + return false; + } + + return LoadScoreboardFromNBT(NBT); +} + + + + + +bool cScoreboardSerializer::Save(void) +{ + cFastNBTWriter Writer; + + SaveScoreboardToNBT(Writer); + + Writer.Finish(); + + #ifdef _DEBUG + cParsedNBT TestParse(Writer.GetResult().data(), Writer.GetResult().size()); + ASSERT(TestParse.IsValid()); + #endif // _DEBUG + + cFile File; + if (!File.Open(FILE_IO_PREFIX + m_Path, cFile::fmWrite)) + { + return false; + } + + AString Compressed; + int res = CompressStringGZIP(Writer.GetResult().data(), Writer.GetResult().size(), Compressed); + + if (res != Z_OK) + { + return false; + } + + File.Write(Compressed.data(), Compressed.size()); + File.Close(); + + return true; +} + + + + + +void cScoreboardSerializer::SaveScoreboardToNBT(cFastNBTWriter & a_Writer) +{ + a_Writer.BeginCompound("data"); + + a_Writer.BeginList("Objectives", TAG_Compound); + + for (cScoreboard::cObjectiveMap::const_iterator it = m_ScoreBoard->m_Objectives.begin(); it != m_ScoreBoard->m_Objectives.end(); ++it) + { + const cObjective & Objective = it->second; + + a_Writer.BeginCompound(""); + + a_Writer.AddString("CriteriaName", cObjective::TypeToString(Objective.GetType())); + + a_Writer.AddString("DisplayName", Objective.GetDisplayName()); + a_Writer.AddString("Name", it->first); + + a_Writer.EndCompound(); + } + + a_Writer.EndList(); // Objectives + + a_Writer.BeginList("PlayerScores", TAG_Compound); + + for (cScoreboard::cObjectiveMap::const_iterator it = m_ScoreBoard->m_Objectives.begin(); it != m_ScoreBoard->m_Objectives.end(); ++it) + { + const cObjective & Objective = it->second; + + for (cObjective::cScoreMap::const_iterator it2 = Objective.m_Scores.begin(); it2 != Objective.m_Scores.end(); ++it2) + { + a_Writer.BeginCompound(""); + + a_Writer.AddInt("Score", it2->second); + + a_Writer.AddString("Name", it2->first); + a_Writer.AddString("Objective", it->first); + + a_Writer.EndCompound(); + } + } + + a_Writer.EndList(); // PlayerScores + + a_Writer.BeginList("Teams", TAG_Compound); + + for (cScoreboard::cTeamMap::const_iterator it = m_ScoreBoard->m_Teams.begin(); it != m_ScoreBoard->m_Teams.end(); ++it) + { + const cTeam & Team = it->second; + + a_Writer.BeginCompound(""); + + a_Writer.AddByte("AllowFriendlyFire", Team.AllowsFriendlyFire() ? 1 : 0); + a_Writer.AddByte("SeeFriendlyInvisibles", Team.CanSeeFriendlyInvisible() ? 1 : 0); + + a_Writer.AddString("DisplayName", Team.GetDisplayName()); + a_Writer.AddString("Name", it->first); + + a_Writer.AddString("Prefix", Team.GetPrefix()); + a_Writer.AddString("Suffix", Team.GetSuffix()); + + a_Writer.BeginList("Players", TAG_String); + + for (cTeam::cPlayerNameSet::const_iterator it2 = Team.m_Players.begin(); it2 != Team.m_Players.end(); ++it2) + { + a_Writer.AddString("", *it2); + } + + a_Writer.EndList(); + + a_Writer.EndCompound(); + } + + a_Writer.EndList(); // Teams + + a_Writer.BeginCompound("DisplaySlots"); + + cObjective * Objective = m_ScoreBoard->GetObjectiveIn(cScoreboard::E_DISPLAY_SLOT_LIST); + a_Writer.AddString("slot_0", (Objective == NULL) ? "" : Objective->GetName()); + + Objective = m_ScoreBoard->GetObjectiveIn(cScoreboard::E_DISPLAY_SLOT_SIDEBAR); + a_Writer.AddString("slot_1", (Objective == NULL) ? "" : Objective->GetName()); + + Objective = m_ScoreBoard->GetObjectiveIn(cScoreboard::E_DISPLAY_SLOT_NAME); + a_Writer.AddString("slot_2", (Objective == NULL) ? "" : Objective->GetName()); + + a_Writer.EndCompound(); // DisplaySlots + + a_Writer.EndCompound(); // Data +} + + + + + +bool cScoreboardSerializer::LoadScoreboardFromNBT(const cParsedNBT & a_NBT) +{ + int Data = a_NBT.FindChildByName(0, "data"); + if (Data < 0) + { + return false; + } + + int Objectives = a_NBT.FindChildByName(Data, "Objectives"); + if (Objectives < 0) + { + return false; + } + + for (int Child = a_NBT.GetFirstChild(Objectives); Child >= 0; Child = a_NBT.GetNextSibling(Child)) + { + AString CriteriaName, DisplayName, Name; + + int CurrLine = a_NBT.FindChildByName(Child, "CriteriaName"); + if (CurrLine >= 0) + { + CriteriaName = a_NBT.GetString(CurrLine); + } + + CurrLine = a_NBT.FindChildByName(Child, "DisplayName"); + if (CurrLine >= 0) + { + DisplayName = a_NBT.GetString(CurrLine); + } + + CurrLine = a_NBT.FindChildByName(Child, "Name"); + if (CurrLine >= 0) + { + Name = a_NBT.GetString(CurrLine); + } + + cObjective::eType Type = cObjective::StringToType(CriteriaName); + + m_ScoreBoard->RegisterObjective(Name, DisplayName, Type); + } + + int PlayerScores = a_NBT.FindChildByName(Data, "PlayerScores"); + if (PlayerScores < 0) + { + return false; + } + + for (int Child = a_NBT.GetFirstChild(PlayerScores); Child >= 0; Child = a_NBT.GetNextSibling(Child)) + { + AString Name, ObjectiveName; + + cObjective::Score Score; + + int CurrLine = a_NBT.FindChildByName(Child, "Score"); + if (CurrLine >= 0) + { + Score = a_NBT.GetInt(CurrLine); + } + + CurrLine = a_NBT.FindChildByName(Child, "Name"); + if (CurrLine >= 0) + { + Name = a_NBT.GetString(CurrLine); + } + + CurrLine = a_NBT.FindChildByName(Child, "Objective"); + if (CurrLine >= 0) + { + ObjectiveName = a_NBT.GetString(CurrLine); + } + + cObjective * Objective = m_ScoreBoard->GetObjective(ObjectiveName); + + if (Objective) + { + Objective->SetScore(Name, Score); + } + } + + int Teams = a_NBT.FindChildByName(Data, "Teams"); + if (Teams < 0) + { + return false; + } + + for (int Child = a_NBT.GetFirstChild(Teams); Child >= 0; Child = a_NBT.GetNextSibling(Child)) + { + AString Name, DisplayName, Prefix, Suffix; + + bool AllowsFriendlyFire, CanSeeFriendlyInvisible; + + int CurrLine = a_NBT.FindChildByName(Child, "Name"); + if (CurrLine >= 0) + { + Name = a_NBT.GetInt(CurrLine); + } + + CurrLine = a_NBT.FindChildByName(Child, "DisplayName"); + if (CurrLine >= 0) + { + DisplayName = a_NBT.GetInt(CurrLine); + } + + CurrLine = a_NBT.FindChildByName(Child, "Prefix"); + if (CurrLine >= 0) + { + Prefix = a_NBT.GetInt(CurrLine); + } + + CurrLine = a_NBT.FindChildByName(Child, "Suffix"); + if (CurrLine >= 0) + { + Suffix = a_NBT.GetInt(CurrLine); + } + + CurrLine = a_NBT.FindChildByName(Child, "AllowFriendlyFire"); + if (CurrLine >= 0) + { + AllowsFriendlyFire = (a_NBT.GetInt(CurrLine) != 0); + } + + CurrLine = a_NBT.FindChildByName(Child, "SeeFriendlyInvisibles"); + if (CurrLine >= 0) + { + CanSeeFriendlyInvisible = (a_NBT.GetInt(CurrLine) != 0); + } + + cTeam * Team = m_ScoreBoard->RegisterTeam(Name, DisplayName, Prefix, Suffix); + + Team->SetFriendlyFire(AllowsFriendlyFire); + Team->SetCanSeeFriendlyInvisible(CanSeeFriendlyInvisible); + + int Players = a_NBT.FindChildByName(Child, "Players"); + if (Players < 0) + { + continue; + } + + for (int ChildB = a_NBT.GetFirstChild(Players); ChildB >= 0; ChildB = a_NBT.GetNextSibling(ChildB)) + { + Team->AddPlayer(a_NBT.GetString(ChildB)); + } + } + + int DisplaySlots = a_NBT.FindChildByName(Data, "DisplaySlots"); + if (DisplaySlots < 0) + { + return false; + } + + int CurrLine = a_NBT.FindChildByName(DisplaySlots, "slot_0"); + if (CurrLine >= 0) + { + AString Name = a_NBT.GetString(CurrLine); + + m_ScoreBoard->SetDisplay(Name, cScoreboard::E_DISPLAY_SLOT_LIST); + } + + CurrLine = a_NBT.FindChildByName(DisplaySlots, "slot_1"); + if (CurrLine >= 0) + { + AString Name = a_NBT.GetString(CurrLine); + + m_ScoreBoard->SetDisplay(Name, cScoreboard::E_DISPLAY_SLOT_SIDEBAR); + } + + CurrLine = a_NBT.FindChildByName(DisplaySlots, "slot_2"); + if (CurrLine >= 0) + { + AString Name = a_NBT.GetString(CurrLine); + + m_ScoreBoard->SetDisplay(Name, cScoreboard::E_DISPLAY_SLOT_NAME); + } + + return true; +} + + + + + + + + diff --git a/src/WorldStorage/ScoreboardSerializer.h b/src/WorldStorage/ScoreboardSerializer.h new file mode 100644 index 000000000..048fa3ab4 --- /dev/null +++ b/src/WorldStorage/ScoreboardSerializer.h @@ -0,0 +1,52 @@ + +// ScoreboardSerializer.h + +// Declares the cScoreboardSerializer class that is used for saving scoreboards into NBT format used by Anvil + + + + + +#pragma once + + + + + +// fwd: +class cFastNBTWriter; +class cParsedNBT; +class cScoreboard; + + + + +class cScoreboardSerializer +{ +public: + + cScoreboardSerializer(const AString & a_WorldName, cScoreboard* a_ScoreBoard); + + /// Try to load the scoreboard + bool Load(void); + + /// Try to save the scoreboard + bool Save(void); + + +private: + + void SaveScoreboardToNBT(cFastNBTWriter & a_Writer); + + bool LoadScoreboardFromNBT(const cParsedNBT & a_NBT); + + cScoreboard* m_ScoreBoard; + + AString m_Path; + + +} ; + + + + diff --git a/src/WorldStorage/WSSAnvil.cpp b/src/WorldStorage/WSSAnvil.cpp index 8983f25ba..63999add3 100644 --- a/src/WorldStorage/WSSAnvil.cpp +++ b/src/WorldStorage/WSSAnvil.cpp @@ -611,12 +611,18 @@ void cWSSAnvil::LoadBlockEntitiesFromNBT(cBlockEntityList & a_BlockEntities, con bool cWSSAnvil::LoadItemFromNBT(cItem & a_Item, const cParsedNBT & a_NBT, int a_TagIdx) { - int ID = a_NBT.FindChildByName(a_TagIdx, "id"); - if ((ID < 0) || (a_NBT.GetType(ID) != TAG_Short)) + int Type = a_NBT.FindChildByName(a_TagIdx, "id"); + if ((Type < 0) || (a_NBT.GetType(Type) != TAG_Short)) { return false; } - a_Item.m_ItemType = (ENUM_ITEM_ID)(a_NBT.GetShort(ID)); + a_Item.m_ItemType = a_NBT.GetShort(Type); + if (a_Item.m_ItemType < 0) + { + LOGD("Encountered an item with negative type (%d). Replacing with an empty item.", a_NBT.GetShort(Type)); + a_Item.Empty(); + return true; + } int Damage = a_NBT.FindChildByName(a_TagIdx, "Damage"); if ((Damage < 0) || (a_NBT.GetType(Damage) != TAG_Short)) @@ -1870,7 +1876,22 @@ void cWSSAnvil::LoadWolfFromNBT(cEntityList & a_Entities, const cParsedNBT & a_N { return; } - + int OwnerIdx = a_NBT.FindChildByName(a_TagIdx, "Owner"); + if (OwnerIdx > 0) + { + AString OwnerName = a_NBT.GetString(OwnerIdx); + if (OwnerName != "") + { + Monster->SetOwner(OwnerName); + Monster->SetIsTame(true); + } + } + int SittingIdx = a_NBT.FindChildByName(a_TagIdx, "Sitting"); + if (SittingIdx > 0) + { + bool IsSitting = (a_NBT.GetByte(SittingIdx) > 0); + Monster->SetIsSitting(IsSitting); + } a_Entities.push_back(Monster.release()); } @@ -1926,17 +1947,22 @@ bool cWSSAnvil::LoadEntityBaseFromNBT(cEntity & a_Entity, const cParsedNBT & a_N double Speed[3]; if (!LoadDoublesListFromNBT(Speed, 3, a_NBT, a_NBT.FindChildByName(a_TagIdx, "Motion"))) { - return false; + // Provide default speed: + Speed[0] = 0; + Speed[1] = 0; + Speed[2] = 0; } a_Entity.SetSpeed(Speed[0], Speed[1], Speed[2]); double Rotation[3]; if (!LoadDoublesListFromNBT(Rotation, 2, a_NBT, a_NBT.FindChildByName(a_TagIdx, "Rotation"))) { - return false; + // Provide default rotation: + Rotation[0] = 0; + Rotation[1] = 0; } a_Entity.SetYaw(Rotation[0]); - a_Entity.SetRoll (Rotation[1]); + a_Entity.SetRoll(Rotation[1]); return true; } diff --git a/src/WorldStorage/WSSCompact.cpp b/src/WorldStorage/WSSCompact.cpp index ea17a8ec1..4c0684dd8 100644 --- a/src/WorldStorage/WSSCompact.cpp +++ b/src/WorldStorage/WSSCompact.cpp @@ -10,6 +10,7 @@ #include "json/json.h" #include "../StringCompression.h" #include "../BlockEntities/ChestEntity.h" +#include "../BlockEntities/CommandBlockEntity.h" #include "../BlockEntities/DispenserEntity.h" #include "../BlockEntities/FurnaceEntity.h" #include "../BlockEntities/JukeboxEntity.h" @@ -71,14 +72,15 @@ void cJsonChunkSerializer::BlockEntity(cBlockEntity * a_BlockEntity) const char * SaveInto = NULL; switch (a_BlockEntity->GetBlockType()) { - case E_BLOCK_CHEST: SaveInto = "Chests"; break; - case E_BLOCK_DISPENSER: SaveInto = "Dispensers"; break; - case E_BLOCK_DROPPER: SaveInto = "Droppers"; break; - case E_BLOCK_FURNACE: SaveInto = "Furnaces"; break; - case E_BLOCK_SIGN_POST: SaveInto = "Signs"; break; - case E_BLOCK_WALLSIGN: SaveInto = "Signs"; break; - case E_BLOCK_NOTE_BLOCK: SaveInto = "Notes"; break; - case E_BLOCK_JUKEBOX: SaveInto = "Jukeboxes"; break; + case E_BLOCK_CHEST: SaveInto = "Chests"; break; + case E_BLOCK_DISPENSER: SaveInto = "Dispensers"; break; + case E_BLOCK_DROPPER: SaveInto = "Droppers"; break; + case E_BLOCK_FURNACE: SaveInto = "Furnaces"; break; + case E_BLOCK_SIGN_POST: SaveInto = "Signs"; break; + case E_BLOCK_WALLSIGN: SaveInto = "Signs"; break; + case E_BLOCK_NOTE_BLOCK: SaveInto = "Notes"; break; + case E_BLOCK_JUKEBOX: SaveInto = "Jukeboxes"; break; + case E_BLOCK_COMMAND_BLOCK: SaveInto = "CommandBlocks"; break; default: { @@ -263,126 +265,114 @@ bool cWSSCompact::EraseChunkData(const cChunkCoords & a_Chunk) void cWSSCompact::LoadEntitiesFromJson(Json::Value & a_Value, cEntityList & a_Entities, cBlockEntityList & a_BlockEntities, cWorld * a_World) { - // Load chests + // Load chests: Json::Value AllChests = a_Value.get("Chests", Json::nullValue); if (!AllChests.empty()) { for (Json::Value::iterator itr = AllChests.begin(); itr != AllChests.end(); ++itr ) { - Json::Value & Chest = *itr; - cChestEntity * ChestEntity = new cChestEntity(0,0,0, a_World); - if (!ChestEntity->LoadFromJson( Chest ) ) + std::auto_ptr<cChestEntity> ChestEntity(new cChestEntity(0, 0, 0, a_World)); + if (!ChestEntity->LoadFromJson(*itr)) { - LOGERROR("ERROR READING CHEST FROM JSON!" ); - delete ChestEntity; + LOGWARNING("ERROR READING CHEST FROM JSON!" ); } else { - a_BlockEntities.push_back( ChestEntity ); + a_BlockEntities.push_back(ChestEntity.release()); } } // for itr - AllChests[] } - // Load dispensers + // Load dispensers: Json::Value AllDispensers = a_Value.get("Dispensers", Json::nullValue); - if( !AllDispensers.empty() ) + for (Json::Value::iterator itr = AllDispensers.begin(); itr != AllDispensers.end(); ++itr) { - for( Json::Value::iterator itr = AllDispensers.begin(); itr != AllDispensers.end(); ++itr ) + std::auto_ptr<cDispenserEntity> DispenserEntity(new cDispenserEntity(0, 0, 0, a_World)); + if (!DispenserEntity->LoadFromJson(*itr)) { - Json::Value & Dispenser = *itr; - cDispenserEntity * DispenserEntity = new cDispenserEntity(0,0,0, a_World); - if( !DispenserEntity->LoadFromJson( Dispenser ) ) - { - LOGERROR("ERROR READING DISPENSER FROM JSON!" ); - delete DispenserEntity; - } - else - { - a_BlockEntities.push_back( DispenserEntity ); - } - } // for itr - AllDispensers[] - } + LOGWARNING("ERROR READING DISPENSER FROM JSON!" ); + } + else + { + a_BlockEntities.push_back(DispenserEntity.release()); + } + } // for itr - AllDispensers[] - // Load furnaces + // Load furnaces: Json::Value AllFurnaces = a_Value.get("Furnaces", Json::nullValue); - if( !AllFurnaces.empty() ) + for (Json::Value::iterator itr = AllFurnaces.begin(); itr != AllFurnaces.end(); ++itr) { - for( Json::Value::iterator itr = AllFurnaces.begin(); itr != AllFurnaces.end(); ++itr ) + // TODO: The block type and meta aren't correct, there's no way to get them here + std::auto_ptr<cFurnaceEntity> FurnaceEntity(new cFurnaceEntity(0, 0, 0, E_BLOCK_FURNACE, 0, a_World)); + if (!FurnaceEntity->LoadFromJson(*itr)) { - Json::Value & Furnace = *itr; - // TODO: The block type and meta aren't correct, there's no way to get them here - cFurnaceEntity * FurnaceEntity = new cFurnaceEntity(0, 0, 0, E_BLOCK_FURNACE, 0, a_World); - if (!FurnaceEntity->LoadFromJson(Furnace)) - { - LOGERROR("ERROR READING FURNACE FROM JSON!" ); - delete FurnaceEntity; - } - else - { - a_BlockEntities.push_back(FurnaceEntity); - } - } // for itr - AllFurnaces[] - } + LOGWARNING("ERROR READING FURNACE FROM JSON!" ); + } + else + { + a_BlockEntities.push_back(FurnaceEntity.release()); + } + } // for itr - AllFurnaces[] - // Load signs + // Load signs: Json::Value AllSigns = a_Value.get("Signs", Json::nullValue); - if( !AllSigns.empty() ) + for (Json::Value::iterator itr = AllSigns.begin(); itr != AllSigns.end(); ++itr) { - for( Json::Value::iterator itr = AllSigns.begin(); itr != AllSigns.end(); ++itr ) + std::auto_ptr<cSignEntity> SignEntity(new cSignEntity(E_BLOCK_SIGN_POST, 0, 0, 0, a_World)); + if (!SignEntity->LoadFromJson(*itr)) { - Json::Value & Sign = *itr; - cSignEntity * SignEntity = new cSignEntity( E_BLOCK_SIGN_POST, 0,0,0, a_World); - if ( !SignEntity->LoadFromJson( Sign ) ) - { - LOGERROR("ERROR READING SIGN FROM JSON!" ); - delete SignEntity; - } - else - { - a_BlockEntities.push_back( SignEntity ); - } - } // for itr - AllSigns[] - } + LOGWARNING("ERROR READING SIGN FROM JSON!"); + } + else + { + a_BlockEntities.push_back(SignEntity.release()); + } + } // for itr - AllSigns[] - // Load note blocks + // Load note blocks: Json::Value AllNotes = a_Value.get("Notes", Json::nullValue); - if( !AllNotes.empty() ) + for( Json::Value::iterator itr = AllNotes.begin(); itr != AllNotes.end(); ++itr ) { - for( Json::Value::iterator itr = AllNotes.begin(); itr != AllNotes.end(); ++itr ) + std::auto_ptr<cNoteEntity> NoteEntity(new cNoteEntity(0, 0, 0, a_World)); + if (!NoteEntity->LoadFromJson(*itr)) { - Json::Value & Note = *itr; - cNoteEntity * NoteEntity = new cNoteEntity(0, 0, 0, a_World); - if ( !NoteEntity->LoadFromJson( Note ) ) - { - LOGERROR("ERROR READING NOTE BLOCK FROM JSON!" ); - delete NoteEntity; - } - else - { - a_BlockEntities.push_back( NoteEntity ); - } - } // for itr - AllNotes[] - } + LOGWARNING("ERROR READING NOTE BLOCK FROM JSON!" ); + } + else + { + a_BlockEntities.push_back(NoteEntity.release()); + } + } // for itr - AllNotes[] - // Load jukeboxes + // Load jukeboxes: Json::Value AllJukeboxes = a_Value.get("Jukeboxes", Json::nullValue); - if( !AllJukeboxes.empty() ) + for( Json::Value::iterator itr = AllJukeboxes.begin(); itr != AllJukeboxes.end(); ++itr ) { - for( Json::Value::iterator itr = AllJukeboxes.begin(); itr != AllJukeboxes.end(); ++itr ) + std::auto_ptr<cJukeboxEntity> JukeboxEntity(new cJukeboxEntity(0, 0, 0, a_World)); + if (!JukeboxEntity->LoadFromJson(*itr)) { - Json::Value & Jukebox = *itr; - cJukeboxEntity * JukeboxEntity = new cJukeboxEntity(0, 0, 0, a_World); - if ( !JukeboxEntity->LoadFromJson( Jukebox ) ) - { - LOGERROR("ERROR READING JUKEBOX FROM JSON!" ); - delete JukeboxEntity; - } - else - { - a_BlockEntities.push_back( JukeboxEntity ); - } - } // for itr - AllJukeboxes[] - } + LOGWARNING("ERROR READING JUKEBOX FROM JSON!" ); + } + else + { + a_BlockEntities.push_back(JukeboxEntity.release()); + } + } // for itr - AllJukeboxes[] + + // Load command blocks: + Json::Value AllCommandBlocks = a_Value.get("CommandBlocks", Json::nullValue); + for( Json::Value::iterator itr = AllCommandBlocks.begin(); itr != AllCommandBlocks.end(); ++itr ) + { + std::auto_ptr<cCommandBlockEntity> CommandBlockEntity(new cCommandBlockEntity(0, 0, 0, a_World)); + if (!CommandBlockEntity->LoadFromJson(*itr)) + { + LOGWARNING("ERROR READING COMMAND BLOCK FROM JSON!" ); + } + else + { + a_BlockEntities.push_back(CommandBlockEntity.release()); + } + } // for itr - AllCommandBlocks[] } diff --git a/src/main.cpp b/src/main.cpp index 340149e0b..c8cd2d4fe 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -19,6 +19,16 @@ bool g_SERVER_TERMINATED = false; // Set to true when the server terminates, so +/** If set to true, the protocols will log each player's incoming (C->S) communication to a per-connection logfile */ +bool g_ShouldLogCommIn; + +/** If set to true, the protocols will log each player's outgoing (S->C) communication to a per-connection logfile */ +bool g_ShouldLogCommOut; + + + + + /// If defined, a thorough leak finder will be used (debug MSVC only); leaks will be output to the Output window #define ENABLE_LEAK_FINDER @@ -59,11 +69,13 @@ 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"); + exit(EXIT_FAILURE); break; } + case SIGINT: case SIGTERM: { - std::signal(SIGTERM, SIG_IGN); // Server is shutting down, wait for it... + std::signal(a_Signal, SIG_IGN); // Server is shutting down, wait for it... break; } default: break; @@ -216,12 +228,45 @@ int main( int argc, char **argv ) #ifndef _DEBUG std::signal(SIGSEGV, NonCtrlHandler); std::signal(SIGTERM, NonCtrlHandler); - std::signal(SIGINT, NonCtrlHandler); + std::signal(SIGINT, NonCtrlHandler); + std::signal(SIGABRT, NonCtrlHandler); + #ifdef SIGABRT_COMPAT + std::signal(SIGABRT_COMPAT, NonCtrlHandler); + #endif // SIGABRT_COMPAT #endif // DEBUG: test the dumpfile creation: // *((int *)0) = 0; + // Check if comm logging is to be enabled: + for (int i = 0; i < argc; i++) + { + if ( + (NoCaseCompare(argv[i], "/commlog") == 0) || + (NoCaseCompare(argv[i], "/logcomm") == 0) + ) + { + g_ShouldLogCommIn = true; + g_ShouldLogCommOut = true; + } + if ( + (NoCaseCompare(argv[i], "/commlogin") == 0) || + (NoCaseCompare(argv[i], "/comminlog") == 0) || + (NoCaseCompare(argv[i], "/logcommin") == 0) + ) + { + g_ShouldLogCommIn = true; + } + if ( + (NoCaseCompare(argv[i], "/commlogout") == 0) || + (NoCaseCompare(argv[i], "/commoutlog") == 0) || + (NoCaseCompare(argv[i], "/logcommout") == 0) + ) + { + g_ShouldLogCommOut = true; + } + } + #if !defined(ANDROID_NDK) try #endif |