diff options
Diffstat (limited to 'src')
92 files changed, 5044 insertions, 1838 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/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 00e62fcf6..2fca7142c 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,7 +282,7 @@ 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()); @@ -742,6 +742,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; } @@ -914,6 +918,40 @@ bool cLuaState::CheckParamString(int a_StartParam, int a_EndParam) +bool cLuaState::CheckParamFunction(int a_StartParam, int a_EndParam) +{ + ASSERT(IsValid()); + + if (a_EndParam < 0) + { + a_EndParam = a_StartParam; + } + + for (int i = a_StartParam; i <= a_EndParam; i++) + { + if (lua_isfunction(m_LuaState, i)) + { + continue; + } + // Not the correct parameter + lua_Debug entry; + VERIFY(lua_getstack(m_LuaState, 0, &entry)); + VERIFY(lua_getinfo (m_LuaState, "n", &entry)); + AString ErrMsg = Printf("Error in function '%s' parameter #%d. Function expected, got %s", + (entry.name != NULL) ? entry.name : "?", i, GetTypeText(i).c_str() + ); + LogStackTrace(); + return false; + } // for i - Param + + // All params checked ok + return true; +} + + + + + bool cLuaState::CheckParamEnd(int a_Param) { tolua_Error tolua_err; @@ -991,21 +1029,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 414e5e4b2..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,43 +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 parameter on the stack is nil (indicating an end-of-parameters) + /** 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) */ 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" @@ -844,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; @@ -866,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 3ebe7b294..e7c66c6fb 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" @@ -986,11 +988,10 @@ static int tolua_cWorld_QueueTask(lua_State * tolua_S) class cLuaScheduledWorldTask : - public cWorld::cScheduledTask + public cWorld::cTask { public: - cLuaScheduledWorldTask(cPluginLua & a_Plugin, int a_FnRef, int a_Ticks) : - cScheduledTask(a_Ticks), + cLuaScheduledWorldTask(cPluginLua & a_Plugin, int a_FnRef) : m_Plugin(a_Plugin), m_FnRef(a_FnRef) { @@ -1025,14 +1026,19 @@ static int tolua_cWorld_ScheduleTask(lua_State * tolua_S) } // Retrieve the args: - cWorld * self = (cWorld *)tolua_tousertype(tolua_S, 1, 0); - if (self == NULL) + cLuaState L(tolua_S); + if ( + !L.CheckParamUserType(1, "cWorld") || + !L.CheckParamNumber (2) || + !L.CheckParamFunction(3) + ) { - return lua_do_error(tolua_S, "Error in function call '#funcname#': Not called on an object instance"); + return 0; } - if (!lua_isfunction(tolua_S, 2)) + cWorld * World = (cWorld *)tolua_tousertype(tolua_S, 1, NULL); + if (World == NULL) { - return lua_do_error(tolua_S, "Error in function call '#funcname#': Expected a function for parameter #1"); + return lua_do_error(tolua_S, "Error in function call '#funcname#': Not called on an object instance"); } // Create a reference to the function: @@ -1042,9 +1048,9 @@ static int tolua_cWorld_ScheduleTask(lua_State * tolua_S) return lua_do_error(tolua_S, "Error in function call '#funcname#': Could not get function reference of parameter #1"); } - int Ticks = (int) tolua_tonumber (tolua_S, 3, 0); + int DelayTicks = (int)tolua_tonumber(tolua_S, 2, 0); - self->ScheduleTask(new cLuaScheduledWorldTask(*Plugin, FnRef, Ticks)); + World->ScheduleTask(DelayTicks, new cLuaScheduledWorldTask(*Plugin, FnRef)); return 0; } @@ -1536,6 +1542,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); @@ -1730,112 +1815,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; } @@ -2235,6 +2236,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); @@ -2250,6 +2308,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); @@ -2301,6 +2364,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 4c4664815..1d8c4c6ed 100644 --- a/src/Bindings/PluginLua.cpp +++ b/src/Bindings/PluginLua.cpp @@ -1469,6 +1469,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 c01f5ca89..c13f31424 100644 --- a/src/Bindings/PluginLua.h +++ b/src/Bindings/PluginLua.h @@ -105,7 +105,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 @@ -115,26 +115,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. @@ -143,37 +143,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); @@ -181,13 +191,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; @@ -198,7 +208,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 24bb914d1..e582fde86 100644 --- a/src/Bindings/PluginManager.cpp +++ b/src/Bindings/PluginManager.cpp @@ -1736,6 +1736,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 9936f5a35..d8b838d80 100644 --- a/src/Bindings/PluginManager.h +++ b/src/Bindings/PluginManager.h @@ -122,7 +122,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: @@ -132,7 +132,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; @@ -143,7 +147,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 @@ -206,46 +210,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. @@ -253,9 +257,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; @@ -281,22 +289,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 ca617b04d..0bc6ca359 100644 --- a/src/BlockEntities/CommandBlockEntity.cpp +++ b/src/BlockEntities/CommandBlockEntity.cpp @@ -7,6 +7,7 @@ #include "json/json.h" #include "CommandBlockEntity.h" #include "../Entities/Player.h" +#include "../WorldStorage/FastNBT.h" #include "../CommandOutput.h" #include "../Root.h" @@ -40,6 +41,15 @@ void cCommandBlockEntity::UsedBy(cPlayer * a_Player) void cCommandBlockEntity::SetCommand(const AString & a_Cmd) { m_Command = a_Cmd; + + /* + Vanilla requires that the server send a Block Entity Update after a command has been set + Therefore, command blocks don't support on-the-fly (when window is open) updating of a command and therefore... + ...the following code can't be put in UsedBy just before the window opens + + Just documenting my experience in getting this to work :P + */ + m_World->BroadcastBlockEntity(GetPosX(), GetPosY(), GetPosZ()); } @@ -48,6 +58,7 @@ void cCommandBlockEntity::SetCommand(const AString & a_Cmd) void cCommandBlockEntity::SetLastOutput(const AString & a_LastOut) { + m_World->BroadcastBlockEntity(GetPosX(), GetPosY(), GetPosZ()); m_LastOutput = a_LastOut; } @@ -131,8 +142,7 @@ bool cCommandBlockEntity::Tick(float a_Dt, cChunk & a_Chunk) void cCommandBlockEntity::SendTo(cClientHandle & a_Client) { - // Nothing needs to be sent - UNUSED(a_Client); + a_Client.SendUpdateBlockEntity(*this); } @@ -141,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; } @@ -154,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; } @@ -165,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); } @@ -184,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/Blocks/BlockCommandBlock.h b/src/Blocks/BlockCommandBlock.h new file mode 100644 index 000000000..cf0103765 --- /dev/null +++ b/src/Blocks/BlockCommandBlock.h @@ -0,0 +1,32 @@ + +#pragma once + +#include "BlockEntity.h" + + + + + +class cBlockCommandBlockHandler : + public cBlockEntityHandler +{ +public: + cBlockCommandBlockHandler(BLOCKTYPE a_BlockType) + : cBlockEntityHandler(a_BlockType) + { + } + + virtual void ConvertToPickups(cItems & a_Pickups, NIBBLETYPE a_BlockMeta) override + { + a_Pickups.push_back(cItem(E_BLOCK_AIR, 8, 0)); + } + + virtual const char * GetStepSound(void) override + { + return "step.stone"; + } +} ; + + + + diff --git a/src/Blocks/BlockHandler.cpp b/src/Blocks/BlockHandler.cpp index ff1022e12..b9c0887ce 100644 --- a/src/Blocks/BlockHandler.cpp +++ b/src/Blocks/BlockHandler.cpp @@ -14,6 +14,7 @@ #include "BlockChest.h" #include "BlockCloth.h" #include "BlockCobWeb.h" +#include "BlockCommandBlock.h" #include "BlockComparator.h" #include "BlockCrops.h" #include "BlockDeadBush.h" @@ -116,6 +117,7 @@ cBlockHandler * cBlockHandler::CreateBlockHandler(BLOCKTYPE a_BlockType) case E_BLOCK_CAULDRON: return new cBlockCauldronHandler (a_BlockType); case E_BLOCK_CHEST: return new cBlockChestHandler (a_BlockType); case E_BLOCK_COAL_ORE: return new cBlockOreHandler (a_BlockType); + case E_BLOCK_COMMAND_BLOCK: return new cBlockCommandBlockHandler (a_BlockType); case E_BLOCK_ACTIVE_COMPARATOR: return new cBlockComparatorHandler (a_BlockType); case E_BLOCK_COBBLESTONE: return new cBlockStoneHandler (a_BlockType); case E_BLOCK_COBBLESTONE_STAIRS: return new cBlockStairsHandler (a_BlockType); 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 faf583fbb..ad3f15adc 100644 --- a/src/ClientHandle.cpp +++ b/src/ClientHandle.cpp @@ -120,9 +120,6 @@ cClientHandle::~cClientHandle() LOGD("Deleting client \"%s\" at %p", GetUsername().c_str(), this); - // Remove from cSocketThreads, we're not to be called anymore: - cRoot::Get()->GetServer()->ClientDestroying(this); - { cCSLock Lock(m_CSChunkLists); m_LoadedChunks.clear(); @@ -151,17 +148,7 @@ 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); - } - - // Queue the socket to close as soon as it sends all outgoing data: - cRoot::Get()->GetServer()->QueueClientClose(this); + // Close the socket as soon as it sends all outgoing data: cRoot::Get()->GetServer()->RemoveClient(this); delete m_Protocol; @@ -267,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); } @@ -546,12 +539,18 @@ void cClientHandle::HandlePlayerPos(double a_PosX, double a_PosY, double a_PosZ, void cClientHandle::HandlePluginMessage(const AString & a_Channel, const AString & a_Message) { - if (a_Channel == "MC|AdvCdm") // Command block + if (a_Channel == "MC|AdvCdm") // Command block, set text, Client -> Server { const char* Data = a_Message.c_str(); - HandleCommandBlockMessage(Data, a_Message.size()); - + return; + } + else if (a_Channel == "MC|Brand") // Client <-> Server branding exchange + { + // We are custom, + // We are awesome, + // We are MCServer. + SendPluginMessage("MC|Brand", "MCServer"); return; } @@ -566,6 +565,7 @@ void cClientHandle::HandleCommandBlockMessage(const char* a_Data, unsigned int a { if (a_Length < 14) { + SendChat(Printf("%s[INFO]%s Failure setting command block command; bad request", cChatColor::Red.c_str(), cChatColor::White.c_str())); LOGD("Malformed MC|AdvCdm packet."); return; } @@ -595,28 +595,24 @@ void cClientHandle::HandleCommandBlockMessage(const char* a_Data, unsigned int a default: { + SendChat(Printf("%s[INFO]%s Failure setting command block command; unhandled mode", cChatColor::Red.c_str(), cChatColor::White.c_str())); LOGD("Unhandled MC|AdvCdm packet mode."); return; } } - 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())); + } + else + { + SendChat(Printf("%s[INFO]%s Command blocks are not enabled on this server", cChatColor::Yellow.c_str(), cChatColor::White.c_str())); + } } @@ -652,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; } @@ -707,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; } @@ -2105,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); @@ -2203,6 +2227,14 @@ void cClientHandle::SendUnloadChunk(int a_ChunkX, int a_ChunkZ) +void cClientHandle::SendUpdateBlockEntity(cBlockEntity & a_BlockEntity) +{ + m_Protocol->SendUpdateBlockEntity(a_BlockEntity); +} + + + + void cClientHandle::SendUpdateSign( int a_BlockX, int a_BlockY, int a_BlockZ, diff --git a/src/ClientHandle.h b/src/ClientHandle.h index 373ca9e2e..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); @@ -136,6 +140,7 @@ public: void SendThunderbolt (int a_BlockX, int a_BlockY, int a_BlockZ); void SendTimeUpdate (Int64 a_WorldAge, Int64 a_TimeOfDay); void SendUnloadChunk (int a_ChunkX, int a_ChunkZ); + void SendUpdateBlockEntity (cBlockEntity & a_BlockEntity); 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); void SendUseBed (const cEntity & a_Entity, int a_BlockX, int a_BlockY, int a_BlockZ ); void SendWeather (eWeather a_Weather); diff --git a/src/Crypto.cpp b/src/Crypto.cpp new file mode 100644 index 000000000..2045d0385 --- /dev/null +++ b/src/Crypto.cpp @@ -0,0 +1,435 @@ + +// 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_PlainLength < 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; + } + size_t DecryptedLength; + int res = rsa_pkcs1_encrypt( + &m_Rsa, ctr_drbg_random, &m_Ctr_drbg, RSA_PUBLIC, + a_PlainLength, a_PlainData, a_EncryptedData + ); + if (res != 0) + { + return -1; + } + return (int)DecryptedLength; +} + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// 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..a97f34fbf --- /dev/null +++ b/src/Crypto.h @@ -0,0 +1,169 @@ + +// 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" + + + + + +/** 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); +} ; + + + + + +/** 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 298180fb1..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; diff --git a/src/Enchantments.cpp b/src/Enchantments.cpp index 95ca201f0..1d8188e96 100644 --- a/src/Enchantments.cpp +++ b/src/Enchantments.cpp @@ -213,87 +213,8 @@ bool cEnchantments::operator !=(const cEnchantments & a_Other) const -void cEnchantments::WriteToNBTCompound(cFastNBTWriter & a_Writer, const AString & a_ListTagName) const -{ - // Write the enchantments into the specified NBT writer - // begin with the LIST tag of the specified name ("ench" or "StoredEnchantments") - - a_Writer.BeginList(a_ListTagName, TAG_Compound); - for (cMap::const_iterator itr = m_Enchantments.begin(), end = m_Enchantments.end(); itr != end; ++itr) - { - a_Writer.BeginCompound(""); - a_Writer.AddShort("id", itr->first); - a_Writer.AddShort("lvl", itr->second); - a_Writer.EndCompound(); - } // for itr - m_Enchantments[] - a_Writer.EndList(); -} - -void cEnchantments::ParseFromNBT(const cParsedNBT & a_NBT, int a_EnchListTagIdx) -{ - // Read the enchantments from the specified NBT list tag (ench or StoredEnchantments) - - // Verify that the tag is a list: - if (a_NBT.GetType(a_EnchListTagIdx) != TAG_List) - { - LOGWARNING("%s: Invalid EnchListTag type: exp %d, got %d. Enchantments not parsed", - __FUNCTION__, TAG_List, a_NBT.GetType(a_EnchListTagIdx) - ); - ASSERT(!"Bad EnchListTag type"); - return; - } - - // Verify that the list is of Compounds: - if (a_NBT.GetChildrenType(a_EnchListTagIdx) != TAG_Compound) - { - LOGWARNING("%s: Invalid NBT list children type: exp %d, got %d. Enchantments not parsed", - __FUNCTION__, TAG_Compound, a_NBT.GetChildrenType(a_EnchListTagIdx) - ); - ASSERT(!"Bad EnchListTag children type"); - return; - } - - Clear(); - - // Iterate over all the compound children, parse an enchantment from each: - for (int tag = a_NBT.GetFirstChild(a_EnchListTagIdx); tag >= 0; tag = a_NBT.GetNextSibling(tag)) - { - // tag is the compound inside the "ench" list tag - ASSERT(a_NBT.GetType(tag) == TAG_Compound); - - // Search for the id and lvl tags' values: - int id = -1, lvl = -1; - for (int ch = a_NBT.GetFirstChild(tag); ch >= 0; ch = a_NBT.GetNextSibling(ch)) - { - if (a_NBT.GetType(ch) != TAG_Short) - { - continue; - } - if (a_NBT.GetName(ch) == "id") - { - id = a_NBT.GetShort(ch); - } - else if (a_NBT.GetName(ch) == "lvl") - { - lvl = a_NBT.GetShort(ch); - } - } // for ch - children of the compound tag - - if ((id == -1) || (lvl <= 0)) - { - // Failed to parse either the id or the lvl, skip this compound - continue; - } - - // Store the enchantment: - m_Enchantments[id] = lvl; - } // for tag - children of the ench list tag -} - - - diff --git a/src/Enchantments.h b/src/Enchantments.h index 7581b87b5..e984df92e 100644 --- a/src/Enchantments.h +++ b/src/Enchantments.h @@ -8,6 +8,7 @@ #pragma once +#include "WorldStorage/EnchantmentSerializer.h" @@ -20,7 +21,6 @@ class cParsedNBT; -// tolua_begin /** Class that stores item enchantments or stored-enchantments The enchantments may be serialized to a stringspec and read back from such stringspec. @@ -29,10 +29,12 @@ mapping each enchantment's id onto its level. ID may be either a number or the e Level value of 0 means no such enchantment, and it will not be stored in the m_Enchantments. Serialization will never put zero-level enchantments into the stringspec and will always use numeric IDs. */ +// tolua_begin class cEnchantments { public: /// Individual enchantment IDs, corresponding to their NBT IDs ( http://www.minecraftwiki.net/wiki/Data_Values#Enchantment_IDs ) + enum { enchProtection = 0, @@ -97,10 +99,10 @@ public: bool operator !=(const cEnchantments & a_Other) const; /// Writes the enchantments into the specified NBT writer; begins with the LIST tag of the specified name ("ench" or "StoredEnchantments") - void WriteToNBTCompound(cFastNBTWriter & a_Writer, const AString & a_ListTagName) const; + friend void EnchantmentSerializer::WriteToNBTCompound(cEnchantments const& a_Enchantments, cFastNBTWriter & a_Writer, const AString & a_ListTagName); /// Reads the enchantments from the specified NBT list tag (ench or StoredEnchantments) - void ParseFromNBT(const cParsedNBT & a_NBT, int a_EnchListTagIdx); + friend void EnchantmentSerializer::ParseFromNBT(cEnchantments& a_Enchantments, const cParsedNBT & a_NBT, int a_EnchListTagIdx); protected: /// Maps enchantment ID -> enchantment level 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/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 c1f2456eb..82e31ae65 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); } @@ -196,12 +206,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) { @@ -434,7 +438,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; @@ -459,12 +463,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 */); } @@ -786,10 +788,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); @@ -836,6 +852,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); } @@ -916,6 +951,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) @@ -1342,59 +1421,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; @@ -1543,27 +1631,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"]); @@ -1680,85 +1753,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/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/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/HTTPServer/HTTPConnection.cpp b/src/HTTPServer/HTTPConnection.cpp index a4af706f2..78b7ce4d9 100644 --- a/src/HTTPServer/HTTPConnection.cpp +++ b/src/HTTPServer/HTTPConnection.cpp @@ -26,7 +26,7 @@ cHTTPConnection::cHTTPConnection(cHTTPServer & a_HTTPServer) : cHTTPConnection::~cHTTPConnection() { - // LOGD("HTTP: Del connection at %p", this); + delete m_CurrentRequest; } 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/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/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..d774d3170 100644 --- a/src/Mobs/PassiveMonster.cpp +++ b/src/Mobs/PassiveMonster.cpp @@ -2,7 +2,6 @@ #include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules #include "PassiveMonster.h" -#include "../MersenneTwister.h" #include "../World.h" @@ -36,20 +35,9 @@ 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) - { - if (m_EMState == ESCAPING) - { - CheckEventLostPlayer(); - } - } + CheckEventLostPlayer(); } } diff --git a/src/Mobs/Sheep.cpp b/src/Mobs/Sheep.cpp index bda4ccff8..702108ae4 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,39 @@ 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..4eee3db1c 100644 --- a/src/Mobs/Sheep.h +++ b/src/Mobs/Sheep.h @@ -19,6 +19,8 @@ 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; + bool IsSheared(void) const { return m_IsSheared; } int GetFurColor(void) const { return m_WoolColor; } @@ -26,6 +28,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..c8ff4f101 100644 --- a/src/Mobs/Villager.cpp +++ b/src/Mobs/Villager.cpp @@ -21,7 +21,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) { 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 8ea5d8320..4226a7535 100644 --- a/src/OSSupport/Socket.cpp +++ b/src/OSSupport/Socket.cpp @@ -87,52 +87,19 @@ void cSocket::CloseSocket() -AString cSocket::GetErrorString( int a_ErrNo ) +void cSocket::ShutdownReadWrite(void) { - 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 + int res = shutdown(m_Socket, SD_BOTH); + #else + int res = shutdown(m_Socket, SHUT_RDWR); + #endif + if (res != 0) { - Printf(Out, "Error %d while getting error string for error #%d!", errno, a_ErrNo); - return Out; + LOGWARN("%s: Error shutting down socket %d (%s): %d (%s)", + __FUNCTION__, m_Socket, m_IPString.c_str(), this->GetLastError(), GetLastErrorString().c_str() + ); } - - #endif // else _WIN32 } diff --git a/src/OSSupport/Socket.h b/src/OSSupport/Socket.h index b86560de8..4ca3d61f4 100644 --- a/src/OSSupport/Socket.h +++ b/src/OSSupport/Socket.h @@ -14,7 +14,7 @@ #endif - +#include "Errors.h" class cSocket @@ -39,7 +39,11 @@ public: bool IsValid(void) const { return IsValidSocket(m_Socket); } void CloseSocket(void); - + + /** Notifies the socket that we don't expect any more reads nor writes on it. + Most TCPIP implementations use this to send the FIN flag in a packet */ + void ShutdownReadWrite(void); + operator xSocket(void) const; xSocket GetSocket(void) const; @@ -53,11 +57,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,4 +114,4 @@ public: 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 3e505616c..74932daf8 100644 --- a/src/OSSupport/SocketThreads.cpp +++ b/src/OSSupport/SocketThreads.cpp @@ -7,6 +7,7 @@ #include "Globals.h" #include "SocketThreads.h" +#include "Errors.h" @@ -71,29 +72,6 @@ bool cSocketThreads::AddClient(const cSocket & a_Socket, cCallback * a_Client) -/* -void cSocketThreads::RemoveClient(const cSocket * a_Socket) -{ - // Remove the socket (and associated client) from processing - - cCSLock Lock(m_CS); - for (cSocketThreadList::iterator itr = m_Threads.begin(); itr != m_Threads.end(); ++itr) - { - if ((*itr)->RemoveSocket(a_Socket)) - { - return; - } - } // for itr - m_Threads[] - - // Cannot assert here, this may actually happen legally, since cClientHandle has to clean up the socket and it may have already closed in the meantime - // ASSERT(!"Removing an unknown socket"); -} -*/ - - - - - void cSocketThreads::RemoveClient(const cCallback * a_Client) { // Remove the associated socket and the client from processing @@ -155,47 +133,6 @@ void cSocketThreads::Write(const cCallback * a_Client, const AString & a_Data) -/// Stops reading from the socket - when this call returns, no more calls to the callbacks are made -void cSocketThreads::StopReading(const cCallback * a_Client) -{ - cCSLock Lock(m_CS); - for (cSocketThreadList::iterator itr = m_Threads.begin(); itr != m_Threads.end(); ++itr) - { - if ((*itr)->StopReading(a_Client)) - { - return; - } - } // for itr - m_Threads[] - - // Cannot assert, this normally happens if the socket is closed before the client deinitializes - // ASSERT(!"Stopping reading on an unknown client"); -} - - - - - -/// Queues the socket for closing, as soon as its outgoing data is sent -void cSocketThreads::QueueClose(const cCallback * a_Client) -{ - LOGD("QueueClose(client %p)", a_Client); - - cCSLock Lock(m_CS); - for (cSocketThreadList::iterator itr = m_Threads.begin(); itr != m_Threads.end(); ++itr) - { - if ((*itr)->QueueClose(a_Client)) - { - return; - } - } // for itr - m_Threads[] - - ASSERT(!"Queueing close of an unknown client"); -} - - - - - //////////////////////////////////////////////////////////////////////////////// // cSocketThreads::cSocketThread: @@ -233,13 +170,13 @@ cSocketThreads::cSocketThread::~cSocketThread() void cSocketThreads::cSocketThread::AddClient(const cSocket & a_Socket, cCallback * a_Client) { + ASSERT(m_Parent->m_CS.IsLockedByCurrentThread()); ASSERT(m_NumSlots < MAX_SLOTS); // Use HasEmptySlot() to check before adding m_Slots[m_NumSlots].m_Client = a_Client; m_Slots[m_NumSlots].m_Socket = a_Socket; m_Slots[m_NumSlots].m_Outgoing.clear(); - m_Slots[m_NumSlots].m_ShouldClose = false; - m_Slots[m_NumSlots].m_ShouldCallClient = true; + m_Slots[m_NumSlots].m_State = sSlot::ssNormal; m_NumSlots++; // Notify the thread of the change: @@ -253,7 +190,7 @@ void cSocketThreads::cSocketThread::AddClient(const cSocket & a_Socket, cCallbac bool cSocketThreads::cSocketThread::RemoveClient(const cCallback * a_Client) { - // Returns true if removed, false if not found + ASSERT(m_Parent->m_CS.IsLockedByCurrentThread()); if (m_NumSlots == 0) { @@ -267,36 +204,29 @@ bool cSocketThreads::cSocketThread::RemoveClient(const cCallback * a_Client) continue; } - // Found, remove it: - m_Slots[i] = m_Slots[--m_NumSlots]; - - // Notify the thread of the change: - ASSERT(m_ControlSocket2.IsValid()); - m_ControlSocket2.Send("r", 1); - return true; - } // for i - m_Slots[] - - // Not found - return false; -} - - - - - -bool cSocketThreads::cSocketThread::RemoveSocket(const cSocket * a_Socket) -{ - // Returns true if removed, false if not found - - for (int i = m_NumSlots - 1; i >= 0 ; --i) - { - if (m_Slots[i].m_Socket != *a_Socket) + // Found the slot: + if (m_Slots[i].m_State == sSlot::ssRemoteClosed) { - continue; + // The remote has already closed the socket, remove the slot altogether: + m_Slots[i] = m_Slots[--m_NumSlots]; + } + else + { + // Query and queue the last batch of outgoing data: + m_Slots[i].m_Client->GetOutgoingData(m_Slots[i].m_Outgoing); + if (m_Slots[i].m_Outgoing.empty()) + { + // No more outgoing data, shut the socket down immediately: + m_Slots[i].m_Socket.ShutdownReadWrite(); + m_Slots[i].m_State = sSlot::ssShuttingDown; + } + else + { + // More data to send, shut down reading and wait for the rest to get sent: + m_Slots[i].m_State = sSlot::ssWritingRestOut; + } + m_Slots[i].m_Client = NULL; } - - // Found, remove it: - m_Slots[i] = m_Slots[--m_NumSlots]; // Notify the thread of the change: ASSERT(m_ControlSocket2.IsValid()); @@ -314,6 +244,8 @@ bool cSocketThreads::cSocketThread::RemoveSocket(const cSocket * a_Socket) bool cSocketThreads::cSocketThread::HasClient(const cCallback * a_Client) const { + ASSERT(m_Parent->m_CS.IsLockedByCurrentThread()); + for (int i = m_NumSlots - 1; i >= 0; --i) { if (m_Slots[i].m_Client == a_Client) @@ -346,6 +278,8 @@ bool cSocketThreads::cSocketThread::HasSocket(const cSocket * a_Socket) const bool cSocketThreads::cSocketThread::NotifyWrite(const cCallback * a_Client) { + ASSERT(m_Parent->m_CS.IsLockedByCurrentThread()); + if (HasClient(a_Client)) { // Notify the thread that there's another packet in the queue: @@ -362,7 +296,7 @@ bool cSocketThreads::cSocketThread::NotifyWrite(const cCallback * a_Client) bool cSocketThreads::cSocketThread::Write(const cCallback * a_Client, const AString & a_Data) { - // Returns true if socket handled by this thread + ASSERT(m_Parent->m_CS.IsLockedByCurrentThread()); for (int i = m_NumSlots - 1; i >= 0; --i) { if (m_Slots[i].m_Client == a_Client) @@ -383,47 +317,6 @@ bool cSocketThreads::cSocketThread::Write(const cCallback * a_Client, const AStr -bool cSocketThreads::cSocketThread::StopReading (const cCallback * a_Client) -{ - // Returns true if client handled by this thread - for (int i = m_NumSlots - 1; i >= 0; --i) - { - if (m_Slots[i].m_Client == a_Client) - { - m_Slots[i].m_ShouldCallClient = false; - return true; - } - } // for i - m_Slots[] - return false; -} - - - - - -bool cSocketThreads::cSocketThread::QueueClose(const cCallback * a_Client) -{ - // Returns true if socket handled by this thread - for (int i = m_NumSlots - 1; i >= 0; --i) - { - if (m_Slots[i].m_Client == a_Client) - { - m_Slots[i].m_ShouldClose = true; - - // Notify the thread that there's a close queued (in case its conditions are already met): - ASSERT(m_ControlSocket2.IsValid()); - m_ControlSocket2.Send("c", 1); - - return true; - } - } // for i - m_Slots[] - return false; -} - - - - - bool cSocketThreads::cSocketThread::Start(void) { // Create the control socket listener @@ -497,10 +390,13 @@ void cSocketThreads::cSocketThread::Execute(void) fd_set fdRead; cSocket::xSocket Highest = m_ControlSocket1.GetSocket(); - PrepareSet(&fdRead, Highest); + PrepareSet(&fdRead, Highest, false); // Wait for the sockets: - if (select(Highest + 1, &fdRead, NULL, NULL, NULL) == -1) + timeval Timeout; + Timeout.tv_sec = 5; + Timeout.tv_usec = 0; + if (select(Highest + 1, &fdRead, NULL, NULL, &Timeout) == -1) { LOG("select(R) call failed in cSocketThread: \"%s\"", cSocket::GetLastErrorString().c_str()); continue; @@ -511,8 +407,7 @@ void cSocketThreads::cSocketThread::Execute(void) // Test sockets for writing: fd_set fdWrite; Highest = m_ControlSocket1.GetSocket(); - PrepareSet(&fdWrite, Highest); - timeval Timeout; + PrepareSet(&fdWrite, Highest, true); Timeout.tv_sec = 0; Timeout.tv_usec = 0; if (select(Highest + 1, NULL, &fdWrite, NULL, &Timeout) == -1) @@ -522,6 +417,8 @@ void cSocketThreads::cSocketThread::Execute(void) } WriteToSockets(&fdWrite); + + CleanUpShutSockets(); } // while (!mShouldTerminate) } @@ -529,7 +426,7 @@ void cSocketThreads::cSocketThread::Execute(void) -void cSocketThreads::cSocketThread::PrepareSet(fd_set * a_Set, cSocket::xSocket & a_Highest) +void cSocketThreads::cSocketThread::PrepareSet(fd_set * a_Set, cSocket::xSocket & a_Highest, bool a_IsForWriting) { FD_ZERO(a_Set); FD_SET(m_ControlSocket1.GetSocket(), a_Set); @@ -541,6 +438,11 @@ void cSocketThreads::cSocketThread::PrepareSet(fd_set * a_Set, cSocket::xSocket { continue; } + if (m_Slots[i].m_State == sSlot::ssRemoteClosed) + { + // This socket won't provide nor consume any data anymore, don't put it in the Set + continue; + } cSocket::xSocket s = m_Slots[i].m_Socket.GetSocket(); FD_SET(s, a_Set); if (s > a_Highest) @@ -576,29 +478,42 @@ void cSocketThreads::cSocketThread::ReadFromSockets(fd_set * a_Read) } char Buffer[1024]; int Received = m_Slots[i].m_Socket.Receive(Buffer, ARRAYCOUNT(Buffer), 0); - if (Received == 0) - { - // The socket has been closed by the remote party, close our socket and let it be removed after we process all reading - m_Slots[i].m_Socket.CloseSocket(); - if (m_Slots[i].m_ShouldCallClient) - { - m_Slots[i].m_Client->SocketClosed(); - } - } - else if (Received > 0) + if (Received <= 0) { - if (m_Slots[i].m_ShouldCallClient) + // The socket has been closed by the remote party + switch (m_Slots[i].m_State) { - m_Slots[i].m_Client->DataReceived(Buffer, Received); - } + 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 { - // The socket has encountered an error, close it and let it be removed after we process all reading - m_Slots[i].m_Socket.CloseSocket(); - if (m_Slots[i].m_ShouldCallClient) + if (m_Slots[i].m_Client != NULL) { - m_Slots[i].m_Client->SocketClosed(); + m_Slots[i].m_Client->DataReceived(Buffer, Received); } } } // for i - m_Slots[] @@ -622,22 +537,17 @@ void cSocketThreads::cSocketThread::WriteToSockets(fd_set * a_Write) if (m_Slots[i].m_Outgoing.empty()) { // Request another chunk of outgoing data: - if (m_Slots[i].m_ShouldCallClient) + if (m_Slots[i].m_Client != NULL) { m_Slots[i].m_Client->GetOutgoingData(m_Slots[i].m_Outgoing); } if (m_Slots[i].m_Outgoing.empty()) { - // Nothing ready - if (m_Slots[i].m_ShouldClose) + // No outgoing data is ready + if (m_Slots[i].m_State == sSlot::ssWritingRestOut) { - // Socket was queued for closing and there's no more data to send, close it now: - - // DEBUG - LOGD("Socket was queued for closing, closing now. Slot %d, client %p, socket %d", i, m_Slots[i].m_Client, m_Slots[i].m_Socket.GetSocket()); - - m_Slots[i].m_Socket.CloseSocket(); - // The slot must be freed actively by the client, using RemoveClient() + m_Slots[i].m_State = sSlot::ssShuttingDown; + m_Slots[i].m_Socket.ShutdownReadWrite(); } continue; } @@ -647,9 +557,9 @@ void cSocketThreads::cSocketThread::WriteToSockets(fd_set * a_Write) if (Sent < 0) { 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_ShouldCallClient) + if (m_Slots[i].m_Client != NULL) { m_Slots[i].m_Client->SocketClosed(); } @@ -657,6 +567,12 @@ void cSocketThreads::cSocketThread::WriteToSockets(fd_set * a_Write) } m_Slots[i].m_Outgoing.erase(0, Sent); + if (m_Slots[i].m_Outgoing.empty() && (m_Slots[i].m_State == sSlot::ssWritingRestOut)) + { + m_Slots[i].m_State = sSlot::ssShuttingDown; + m_Slots[i].m_Socket.ShutdownReadWrite(); + } + // _X: If there's data left, it means the client is not reading fast enough, the server would unnecessarily spin in the main loop with zero actions taken; so signalling is disabled // This means that if there's data left, it will be sent only when there's incoming data or someone queues another packet (for any socket handled by this thread) /* @@ -673,3 +589,31 @@ void cSocketThreads::cSocketThread::WriteToSockets(fd_set * a_Write) + +void cSocketThreads::cSocketThread::CleanUpShutSockets(void) +{ + for (int i = m_NumSlots - 1; i >= 0; i--) + { + switch (m_Slots[i].m_State) + { + case sSlot::ssShuttingDown2: + { + // The socket has reached the shutdown timeout, close it and clear its slot: + m_Slots[i].m_Socket.CloseSocket(); + m_Slots[i] = m_Slots[--m_NumSlots]; + break; + } + case sSlot::ssShuttingDown: + { + // The socket has been shut down for a single thread loop, let it loop once more before closing: + m_Slots[i].m_State = sSlot::ssShuttingDown2; + break; + } + default: break; + } + } // for i - m_Slots[] +} + + + + diff --git a/src/OSSupport/SocketThreads.h b/src/OSSupport/SocketThreads.h index 858729c49..9e1947ab6 100644 --- a/src/OSSupport/SocketThreads.h +++ b/src/OSSupport/SocketThreads.h @@ -7,19 +7,20 @@ /* Additional details: -When a client is terminating a connection: -- they call the StopReading() method to disable callbacks for the incoming data -- they call the Write() method to queue any outstanding outgoing data -- they call the QueueClose() method to queue the socket to close after outgoing data has been sent. -When a socket slot is marked as having no callback, it is kept alive until its outgoing data queue is empty and its m_ShouldClose flag is set. -This means that the socket can be written to several times before finally closing it via QueueClose() +When a client wants to terminate the connection, they call the RemoveClient() function. This calls the +callback one last time to read all the available outgoing data, putting it in the slot's m_OutgoingData +buffer. Then it marks the slot as having no callback. The socket is kept alive until its outgoing data +queue is empty, then shutdown is called on it and finally the socket is closed after a timeout. +If at any time within this the remote end closes the socket, then the socket is closed directly. +As soon as the socket is closed, the slot is finally removed from the SocketThread. +The graph in $/docs/SocketThreads States.gv shows the state-machine transitions of the slot. */ -/// How many clients should one thread handle? (must be less than FD_SETSIZE for your platform) +/** How many clients should one thread handle? (must be less than FD_SETSIZE for your platform) */ #define MAX_SLOTS 63 @@ -27,8 +28,6 @@ This means that the socket can be written to several times before finally closin #pragma once -#ifndef CSOCKETTHREADS_H_INCLUDED -#define CSOCKETTHREADS_H_INCLUDED #include "Socket.h" #include "IsThread.h" @@ -64,13 +63,13 @@ public: // Force a virtual destructor in all subclasses: virtual ~cCallback() {} - /// Called when data is received from the remote party + /** 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 *append* outgoing data to a_Data */ virtual void GetOutgoingData(AString & a_Data) = 0; - /// Called when the socket has been closed for any reason + /** Called when the socket has been closed for any reason */ virtual void SocketClosed(void) = 0; } ; @@ -78,24 +77,21 @@ public: cSocketThreads(void); ~cSocketThreads(); - /// Add a (socket, client) pair for processing, data from a_Socket is to be sent to a_Client; returns true if successful + /** Add a (socket, client) pair for processing, data from a_Socket is to be sent to a_Client; returns true if successful */ bool AddClient(const cSocket & a_Socket, cCallback * a_Client); - /// Remove the associated socket and the client from processing. The socket is left to send its data and is removed only after all its m_OutgoingData is sent + /** Remove the associated socket and the client from processing. + The socket is left to send its last outgoing data and is removed only after all its m_Outgoing is sent + and after the socket is properly shutdown (unless the remote disconnects before that) + */ void RemoveClient(const cCallback * a_Client); - /// Notify the thread responsible for a_Client that the client has something to write + /** Notify the thread responsible for a_Client that the client has something to write */ void NotifyWrite(const cCallback * a_Client); - /// Puts a_Data into outgoing data queue for a_Client + /** Puts a_Data into outgoing data queue for a_Client */ void Write(const cCallback * a_Client, const AString & a_Data); - /// Stops reading from the client - when this call returns, no more calls to the callbacks are made - void StopReading(const cCallback * a_Client); - - /// Queues the client for closing, as soon as its outgoing data is sent - void QueueClose(const cCallback * a_Client); - private: class cSocketThread : @@ -114,13 +110,10 @@ private: void AddClient (const cSocket & a_Socket, cCallback * a_Client); // Takes ownership of the socket bool RemoveClient(const cCallback * a_Client); // Returns true if removed, false if not found - bool RemoveSocket(const cSocket * a_Socket); // Returns true if removed, false if not found bool HasClient (const cCallback * a_Client) const; bool HasSocket (const cSocket * a_Socket) const; bool NotifyWrite (const cCallback * a_Client); // Returns true if client handled by this thread bool Write (const cCallback * a_Client, const AString & a_Data); // Returns true if client handled by this thread - bool StopReading (const cCallback * a_Client); // Returns true if client handled by this thread - bool QueueClose (const cCallback * a_Client); // Returns true if client handled by this thread bool Start(void); // Hide the cIsThread's Start method, we need to provide our own startup to create the control socket @@ -134,24 +127,45 @@ private: cSocket m_ControlSocket1; cSocket m_ControlSocket2; - // Socket-client-packetqueues triplets. + // Socket-client-dataqueues-state quadruplets. // Manipulation with these assumes that the parent's m_CS is locked struct sSlot { - cSocket m_Socket; // The socket is primarily owned by this + /** The socket is primarily owned by this object */ + cSocket m_Socket; + + /** The callback to call for events. May be NULL */ cCallback * m_Client; - AString m_Outgoing; // If sending writes only partial data, the rest is stored here for another send - bool m_ShouldClose; // If true, the socket is to be closed after sending all outgoing data - bool m_ShouldCallClient; // If true, the client callbacks are called. Set to false in StopReading() + + /** If sending writes only partial data, the rest is stored here for another send. + Also used when the slot is being removed to store the last batch of outgoing data. */ + AString m_Outgoing; + + enum eState + { + ssNormal, ///< Normal read / write operations + ssWritingRestOut, ///< The client callback was removed, continue to send outgoing data + ssShuttingDown, ///< The last outgoing data has been sent, the socket has called shutdown() + ssShuttingDown2, ///< The shutdown has been done at least 1 thread loop ago (timeout detection) + ssRemoteClosed, ///< The remote end has closed the connection (and we still have a client callback) + } m_State; } ; + sSlot m_Slots[MAX_SLOTS]; int m_NumSlots; // Number of slots actually used virtual void Execute(void) override; - void PrepareSet (fd_set * a_Set, cSocket::xSocket & a_Highest); // Puts all sockets into the set, along with m_ControlSocket1 + /** 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); + 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 + + /** Removes those slots in ssShuttingDown2 state, sets those with ssShuttingDown state to ssShuttingDown2 */ + void CleanUpShutSockets(void); } ; typedef std::list<cSocketThread *> cSocketThreadList; @@ -164,9 +178,3 @@ private: - -#endif // CSOCKETTHREADS_H_INCLUDED - - - - diff --git a/src/Protocol/Protocol.h b/src/Protocol/Protocol.h index 3293da32c..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; @@ -103,6 +107,7 @@ public: virtual void SendThunderbolt (int a_BlockX, int a_BlockY, int a_BlockZ) = 0; virtual void SendTimeUpdate (Int64 a_WorldAge, Int64 a_TimeOfDay) = 0; virtual void SendUnloadChunk (int a_ChunkX, int a_ChunkZ) = 0; + virtual void SendUpdateBlockEntity (cBlockEntity & a_BlockEntity) = 0; 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) = 0; virtual void SendUseBed (const cEntity & a_Entity, int a_BlockX, int a_BlockY, int a_BlockZ ) = 0; virtual void SendWeather (eWeather a_Weather) = 0; diff --git a/src/Protocol/Protocol125.h b/src/Protocol/Protocol125.h index d0e5c9428..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; @@ -79,11 +82,12 @@ public: virtual void SendThunderbolt (int a_BlockX, int a_BlockY, int a_BlockZ) override; virtual void SendTimeUpdate (Int64 a_WorldAge, Int64 a_TimeOfDay) override; virtual void SendUnloadChunk (int a_ChunkX, int a_ChunkZ) override; + virtual void SendUpdateBlockEntity (cBlockEntity & a_BlockEntity) override {}; 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 29fbb4bba..f5fd95c7e 100644 --- a/src/Protocol/Protocol132.cpp +++ b/src/Protocol/Protocol132.cpp @@ -16,6 +16,7 @@ #include "../UI/Window.h" #include "../Entities/Pickup.h" #include "../WorldStorage/FastNBT.h" +#include "../WorldStorage/EnchantmentSerializer.h" #include "../StringCompression.h" #ifdef _MSC_VER @@ -27,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; \ { \ @@ -48,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... @@ -92,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: @@ -196,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; @@ -581,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; @@ -595,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); @@ -713,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; @@ -763,7 +677,7 @@ void cProtocol132::WriteItem(const cItem & a_Item) // Send the enchantments: cFastNBTWriter Writer; const char * TagName = (a_Item.m_ItemType == E_ITEM_BOOK) ? "StoredEnchantments" : "ench"; - a_Item.m_Enchantments.WriteToNBTCompound(Writer, TagName); + EnchantmentSerializer::WriteToNBTCompound(a_Item.m_Enchantments, Writer, TagName); Writer.Finish(); AString Compressed; CompressStringGZIP(Writer.GetResult().data(), Writer.GetResult().size(), Compressed); @@ -849,7 +763,7 @@ int cProtocol132::ParseItemMetadata(cItem & a_Item, const AString & a_Metadata) ) ) { - a_Item.m_Enchantments.ParseFromNBT(NBT, tag); + EnchantmentSerializer::ParseFromNBT(a_Item.m_Enchantments, NBT, tag); } } @@ -879,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(); @@ -893,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"); @@ -913,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"); @@ -931,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; } @@ -939,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 5b3a79555..9bb2cfbf0 100644 --- a/src/Protocol/Protocol17x.cpp +++ b/src/Protocol/Protocol17x.cpp @@ -1,4 +1,3 @@ - // Protocol17x.cpp /* @@ -16,6 +15,7 @@ Implements the 1.7.x protocol classes: #include "../Server.h" #include "../World.h" #include "../WorldStorage/FastNBT.h" +#include "../WorldStorage/EnchantmentSerializer.h" #include "../StringCompression.h" #include "../Entities/ExpOrb.h" #include "../Entities/Minecart.h" @@ -24,6 +24,7 @@ Implements the 1.7.x protocol classes: #include "../Entities/Player.h" #include "../Mobs/IncludeAllMonsters.h" #include "../UI/Window.h" +#include "../BlockEntities/CommandBlockEntity.h" @@ -52,6 +53,16 @@ Implements the 1.7.x protocol classes: +// fwd: main.cpp: +extern bool g_ShouldLogComm; + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cProtocol172: + cProtocol172::cProtocol172(cClientHandle * a_Client, const AString & a_ServerAddress, UInt16 a_ServerPort, UInt32 a_State) : super(a_Client), m_ServerAddress(a_ServerAddress), @@ -62,6 +73,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_ShouldLogComm) + { + cFile::CreateFolder("CommLogs"); + AString FileName = Printf("CommLogs/%x__%s.log", (unsigned)time(NULL), a_Client->GetIPString().c_str()); + m_CommLogFile.Open(FileName, cFile::fmWrite); + } } @@ -72,11 +90,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; @@ -122,7 +140,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); @@ -705,6 +723,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 @@ -892,6 +950,28 @@ void cProtocol172::SendUnloadChunk(int a_ChunkX, int a_ChunkZ) +void cProtocol172::SendUpdateBlockEntity(cBlockEntity & a_BlockEntity) +{ + cPacketizer Pkt(*this, 0x35); // Update tile entity packet + Pkt.WriteInt(a_BlockEntity.GetPosX()); + Pkt.WriteShort(a_BlockEntity.GetPosY()); + Pkt.WriteInt(a_BlockEntity.GetPosZ()); + + Byte Action = 0; + switch (a_BlockEntity.GetBlockType()) + { + case E_BLOCK_MOB_SPAWNER: Action = 1; break; // Update mob spawner spinny mob thing + case E_BLOCK_COMMAND_BLOCK: Action = 2; break; // Update command block text + default: ASSERT(!"Unhandled or unimplemented BlockEntity update request!"); break; + } + Pkt.WriteByte(Action); + + Pkt.WriteBlockEntity(a_BlockEntity); +} + + + + void cProtocol172::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) { @@ -1002,6 +1082,29 @@ 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_ShouldLogComm) + { + 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); + AString Hex; + CreateHexDump(Hex, AllData.data(), AllData.size(), 16); + m_CommLogFile.Printf("Incoming data, %d (0x%x) bytes unparsed 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() + ); + } + if (!m_ReceivedData.Write(a_Data, a_Size)) { // Too much data in the incoming queue, report to caller: @@ -1037,6 +1140,22 @@ void cProtocol172::AddReceivedData(const char * a_Data, int a_Size) return; } + // Log the packet info into the comm log file: + if (g_ShouldLogComm) + { + 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: @@ -1053,6 +1172,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_ShouldLogComm) + { + m_CommLogFile.Printf("^^^^^^ Unhandled packet ^^^^^^\n\n\n"); + } + return; } @@ -1062,6 +1187,16 @@ 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_ShouldLogComm) + { + 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); } @@ -1601,11 +1736,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; @@ -1696,7 +1831,7 @@ void cProtocol172::ParseItemMetadata(cItem & a_Item, const AString & a_Metadata) ) ) { - a_Item.m_Enchantments.ParseFromNBT(NBT, tag); + EnchantmentSerializer::ParseFromNBT(a_Item.m_Enchantments, NBT, tag); } else if ((NBT.GetType(tag) == TAG_Compound) && (NBT.GetName(tag) == "display")) // Custom name and lore tag { @@ -1744,6 +1879,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_ShouldLogComm) + { + 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() + ); + } } @@ -1781,7 +1927,7 @@ void cProtocol172::cPacketizer::WriteItem(const cItem & a_Item) if (!a_Item.m_Enchantments.IsEmpty()) { const char * TagName = (a_Item.m_ItemType == E_ITEM_BOOK) ? "StoredEnchantments" : "ench"; - a_Item.m_Enchantments.WriteToNBTCompound(Writer, TagName); + EnchantmentSerializer::WriteToNBTCompound(a_Item.m_Enchantments,Writer, TagName); } if (!a_Item.IsBothNameAndLoreEmpty()) { @@ -1819,6 +1965,51 @@ void cProtocol172::cPacketizer::WriteItem(const cItem & a_Item) +void cProtocol172::cPacketizer::WriteBlockEntity(const cBlockEntity & a_BlockEntity) +{ + cFastNBTWriter Writer; + + switch (a_BlockEntity.GetBlockType()) + { + case E_BLOCK_COMMAND_BLOCK: + { + cCommandBlockEntity & CommandBlockEntity = (cCommandBlockEntity &)a_BlockEntity; + + Writer.AddByte("TrackOutput", 1); // Neither I nor the MC wiki has any idea about this + Writer.AddInt("SuccessCount", CommandBlockEntity.GetResult()); + Writer.AddInt("x", CommandBlockEntity.GetPosX()); + Writer.AddInt("y", CommandBlockEntity.GetPosY()); + Writer.AddInt("z", CommandBlockEntity.GetPosZ()); + Writer.AddString("Command", CommandBlockEntity.GetCommand().c_str()); + // You can set custom names for windows in Vanilla + // For a command block, this would be the 'name' prepended to anything it outputs into global chat + // MCS doesn't have this, so just leave it @ '@'. (geddit?) + Writer.AddString("CustomName", "@"); + Writer.AddString("id", "Control"); // "Tile Entity ID" - MC wiki; vanilla server always seems to send this though + + if (!CommandBlockEntity.GetLastOutput().empty()) + { + AString Output; + Printf(Output, "{\"text\":\"%s\"}", CommandBlockEntity.GetLastOutput().c_str()); + + Writer.AddString("LastOutput", Output.c_str()); + } + break; + } + default: break; + } + + Writer.Finish(); + + AString Compressed; + CompressStringGZIP(Writer.GetResult().data(), Writer.GetResult().size(), Compressed); + WriteShort(Compressed.size()); + WriteBuf(Compressed.data(), Compressed.size()); +} + + + + void cProtocol172::cPacketizer::WriteByteAngle(double a_Angle) { diff --git a/src/Protocol/Protocol17x.h b/src/Protocol/Protocol17x.h index 07dba834b..72544b575 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; @@ -102,6 +104,7 @@ public: virtual void SendThunderbolt (int a_BlockX, int a_BlockY, int a_BlockZ) override; virtual void SendTimeUpdate (Int64 a_WorldAge, Int64 a_TimeOfDay) override; virtual void SendUnloadChunk (int a_ChunkX, int a_ChunkZ) override; + virtual void SendUpdateBlockEntity (cBlockEntity & a_BlockEntity) override; 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; @@ -189,6 +192,7 @@ protected: void WriteEntityMetadata(const cEntity & a_Entity); // Writes the metadata for the specified entity, not including the terminating 0x7f void WriteMobMetadata(const cMonster & a_Mob); // Writes the mob-specific metadata for the specified mob void WriteEntityProperties(const cEntity & a_Entity); // Writes the entity properties for the specified entity, including the Count field + void WriteBlockEntity(const cBlockEntity & a_BlockEntity); protected: cProtocol172 & m_Protocol; @@ -215,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 diff --git a/src/Protocol/ProtocolRecognizer.cpp b/src/Protocol/ProtocolRecognizer.cpp index a21f4f042..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); @@ -636,6 +666,16 @@ void cProtocolRecognizer::SendUnloadChunk(int a_ChunkX, int a_ChunkZ) +void cProtocolRecognizer::SendUpdateBlockEntity(cBlockEntity & a_BlockEntity) +{ + ASSERT(m_Protocol != NULL); + m_Protocol->SendUpdateBlockEntity(a_BlockEntity); +} + + + + + void cProtocolRecognizer::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) { ASSERT(m_Protocol != NULL); @@ -797,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; @@ -925,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 e94f4cde8..daeed1845 100644 --- a/src/Protocol/ProtocolRecognizer.h +++ b/src/Protocol/ProtocolRecognizer.h @@ -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; @@ -114,6 +117,7 @@ public: virtual void SendThunderbolt (int a_BlockX, int a_BlockY, int a_BlockZ) override; virtual void SendTimeUpdate (Int64 a_WorldAge, Int64 a_TimeOfDay) override; virtual void SendUnloadChunk (int a_ChunkX, int a_ChunkZ) override; + virtual void SendUpdateBlockEntity (cBlockEntity & a_BlockEntity) override; 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; 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 5280270b9..3f9f8a4ac 100644 --- a/src/Server.cpp +++ b/src/Server.cpp @@ -118,7 +118,7 @@ cServer::cServer(void) : void cServer::ClientDestroying(const cClientHandle * a_Client) { - m_SocketThreads.StopReading(a_Client); + m_SocketThreads.RemoveClient(a_Client); } @@ -143,15 +143,6 @@ void cServer::WriteToClient(const cClientHandle * a_Client, const AString & a_Da -void cServer::QueueClientClose(const cClientHandle * a_Client) -{ - m_SocketThreads.QueueClose(a_Client); -} - - - - - void cServer::RemoveClient(const cClientHandle * a_Client) { m_SocketThreads.RemoveClient(a_Client); @@ -203,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) { @@ -293,17 +284,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(); } @@ -501,7 +484,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 703a7077e..15eafc00a 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) @@ -88,14 +87,14 @@ public: // tolua_export const AString & GetServerID(void) const { return m_ServerID; } // tolua_export - void ClientDestroying(const cClientHandle * a_Client); // Called by cClientHandle::Destroy(); stop m_SocketThreads from calling back into a_Client + /** Called by cClientHandle's destructor; stop m_SocketThreads from calling back into a_Client */ + void ClientDestroying(const cClientHandle * a_Client); - void NotifyClientWrite(const cClientHandle * a_Client); // Notifies m_SocketThreads that client has something to be written + /** Notifies m_SocketThreads that client has something to be written */ + void NotifyClientWrite(const cClientHandle * a_Client); void WriteToClient(const cClientHandle * a_Client, const AString & a_Data); // Queues outgoing data for the client through m_SocketThreads - void QueueClientClose(const cClientHandle * a_Client); // Queues the clienthandle to close when all its outgoing data is sent - 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 @@ -110,8 +109,8 @@ public: // tolua_export /** 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; } private: @@ -180,8 +179,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; 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 8e7b6171c..5205c2616 100644 --- a/src/World.cpp +++ b/src/World.cpp @@ -12,6 +12,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 +22,8 @@ #include "Entities/Player.h" #include "Entities/TNTEntity.h" +#include "BlockEntities/CommandBlockEntity.h" + // Simulators: #include "Simulator/SimulatorManager.h" #include "Simulator/FloodyFluidSimulator.h" @@ -231,7 +234,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 +249,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 +278,10 @@ cWorld::~cWorld() m_Storage.WaitForFinish(); + // Unload the scoreboard + cScoreboardSerializer Serializer(m_WorldName, &m_Scoreboard); + Serializer.Save(); + delete m_ChunkMap; } @@ -513,7 +529,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 +546,7 @@ 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_GameMode = (eGameMode)IniFile.GetValueSetI("GameMode", "GameMode", m_GameMode); @@ -865,28 +882,27 @@ void cWorld::TickQueuedTasks(void) } // for itr - m_Tasks[] } -void cWorld::TickScheduledTasks() + + + + +void cWorld::TickScheduledTasks(void) { - ScheduledTaskList Tasks; // Make a copy of the tasks to avoid deadlocks on accessing m_Tasks + cScheduledTasks Tasks; { cCSLock Lock(m_CSScheduledTasks); - ScheduledTaskList::iterator itr = m_ScheduledTasks.begin(); - while (itr != m_ScheduledTasks.end() && (*itr)->Ticks > 0) + while (!m_ScheduledTasks.empty() && (m_ScheduledTasks.front()->m_TargetTick < m_WorldAge)) { Tasks.push_back(m_ScheduledTasks.front()); m_ScheduledTasks.pop_front(); } - for(;itr != m_ScheduledTasks.end(); itr++) - { - (*itr)->Ticks--; - } } // Execute and delete each task: - for (ScheduledTaskList::iterator itr = Tasks.begin(), end = Tasks.end(); itr != end; ++itr) + for (cScheduledTasks::iterator itr = Tasks.begin(), end = Tasks.end(); itr != end; ++itr) { - (*itr)->Run(*this); + (*itr)->m_Task->Run(*this); delete *itr; } // for itr - m_Tasks[] } @@ -1982,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); @@ -2353,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); @@ -2368,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; @@ -2548,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); @@ -2632,18 +2729,25 @@ void cWorld::QueueTask(cTask * a_Task) m_Tasks.push_back(a_Task); } -void cWorld::ScheduleTask(cScheduledTask * a_Task) + + + + +void cWorld::ScheduleTask(int a_DelayTicks, cTask * a_Task) { + Int64 TargetTick = a_DelayTicks + m_WorldAge; + + // Insert the task into the list of scheduled tasks, ordered by its target tick cCSLock Lock(m_CSScheduledTasks); - for(ScheduledTaskList::iterator itr = m_ScheduledTasks.begin(); itr != m_ScheduledTasks.end(); itr++) + for (cScheduledTasks::iterator itr = m_ScheduledTasks.begin(), end = m_ScheduledTasks.end(); itr != end; ++itr) { - if((*itr)->Ticks >= a_Task->Ticks) + if ((*itr)->m_TargetTick >= TargetTick) { - m_ScheduledTasks.insert(itr, a_Task); + m_ScheduledTasks.insert(itr, new cScheduledTask(TargetTick, a_Task)); return; } } - m_ScheduledTasks.push_back(a_Task); + m_ScheduledTasks.push_back(new cScheduledTask(TargetTick, a_Task)); } diff --git a/src/World.h b/src/World.h index 1a7ad0cb1..d7a7241d1 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,13 +62,13 @@ typedef cItemCallback<cCommandBlockEntity> cCommandBlockCallback; // tolua_begin -class cWorld +class cWorld : public cForEachChunkProvider { public: // tolua_end - /// A simple RAII locker for the chunkmap - locks the chunkmap in its constructor, unlocks it in the destructor + /** A simple RAII locker for the chunkmap - locks the chunkmap in its constructor, unlocks it in the destructor */ class cLock : public cCSLock { @@ -74,8 +76,9 @@ public: public: cLock(cWorld & a_World); } ; + - /// A common ancestor for all tasks queued onto the tick thread + /** A common ancestor for all tasks queued onto the tick thread */ class cTask { public: @@ -83,18 +86,8 @@ public: virtual void Run(cWorld & a_World) = 0; } ; - /// A common ancestor for all scheduled tasks queued onto the tick thread - class cScheduledTask - { - public: - cScheduledTask(const int a_Ticks) : Ticks(a_Ticks) {}; - virtual ~cScheduledTask() {}; - virtual void Run(cWorld & a_World) = 0; - int Ticks; - }; - typedef std::vector<cTask *> cTasks; - typedef std::list<cScheduledTask *> ScheduledTaskList; + class cTaskSaveAllChunks : public cTask @@ -128,16 +121,16 @@ public: BroadcastTimeUpdate(); } - /// Returns the current game mode. Partly OBSOLETE, you should use IsGameModeXXX() functions wherever applicable + /** Returns the current game mode. Partly OBSOLETE, you should use IsGameModeXXX() functions wherever applicable */ eGameMode GetGameMode(void) const { return m_GameMode; } - /// Returns true if the world is in Creative mode + /** Returns true if the world is in Creative mode */ bool IsGameModeCreative(void) const { return (m_GameMode == gmCreative); } - /// Returns true if the world is in Survival mode + /** Returns true if the world is in Survival mode */ bool IsGameModeSurvival(void) const { return (m_GameMode == gmSurvival); } - /// Returns true if the world is in Adventure mode + /** Returns true if the world is in Adventure mode */ bool IsGameModeAdventure(void) const { return (m_GameMode == gmAdventure); } bool IsPVPEnabled(void) const { return m_bEnabledPVP; } @@ -147,12 +140,12 @@ public: eDimension GetDimension(void) const { return m_Dimension; } - /// Returns the world height at the specified coords; waits for the chunk to get loaded / generated + /** Returns the world height at the specified coords; waits for the chunk to get loaded / generated */ int GetHeight(int a_BlockX, int a_BlockZ); // tolua_end - /// Retrieves the world height at the specified coords; returns false if chunk not loaded / generated + /** Retrieves the world height at the specified coords; returns false if chunk not loaded / generated */ bool TryGetHeight(int a_BlockX, int a_BlockZ, int & a_Height); // Exported in ManualBindings.cpp // Broadcast respective packets to all clients of the chunk where the event is taking place @@ -178,6 +171,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); @@ -187,7 +183,7 @@ public: void BroadcastUseBed (const cEntity & a_Entity, int a_BlockX, int a_BlockY, int a_BlockZ ); void BroadcastWeather (eWeather a_Weather, const cClientHandle * a_Exclude = NULL); - /// If there is a block entity at the specified coords, sends it to the client specified + /** If there is a block entity at the specified coords, sends it to the client specified */ void SendBlockEntity(int a_BlockX, int a_BlockY, int a_BlockZ, cClientHandle & a_Client); void MarkChunkDirty (int a_ChunkX, int a_ChunkZ); @@ -221,7 +217,7 @@ public: bool GetChunkData (int a_ChunkX, int a_ChunkZ, cChunkDataCallback & a_Callback); - /// Gets the chunk's blocks, only the block types + /** Gets the chunk's blocks, only the block types */ bool GetChunkBlockTypes(int a_ChunkX, int a_ChunkZ, BLOCKTYPE * a_BlockTypes); bool IsChunkValid (int a_ChunkX, int a_ChunkZ) const; @@ -234,89 +230,92 @@ public: void AddPlayer( cPlayer* a_Player ); void RemovePlayer( cPlayer* a_Player ); - /// Calls the callback for each player in the list; returns true if all players processed, false if the callback aborted by returning true - bool ForEachPlayer(cPlayerListCallback & a_Callback); // >> EXPORTED IN MANUALBINDINGS << - - /// Calls the callback for the player of the given name; returns true if the player was found and the callback called, false if player not found. Callback return ignored - bool DoWithPlayer(const AString & a_PlayerName, cPlayerListCallback & a_Callback); // >> EXPORTED IN MANUALBINDINGS << + /** Calls the callback for each player in the list; returns true if all players processed, false if the callback aborted by returning true */ + bool ForEachPlayer(cPlayerListCallback & a_Callback); // >> EXPORTED IN MANUALBINDINGS << + + /** Calls the callback for the player of the given name; returns true if the player was found and the callback called, false if player not found. Callback return ignored */ + bool DoWithPlayer(const AString & a_PlayerName, cPlayerListCallback & a_Callback); // >> EXPORTED IN MANUALBINDINGS << - /// Finds a player from a partial or complete player name and calls the callback - case-insensitive + /** Finds a player from a partial or complete player name and calls the callback - case-insensitive */ 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 - /// Adds the entity into its appropriate chunk; takes ownership of the entity ptr + /** Adds the entity into its appropriate chunk; takes ownership of the entity ptr */ void AddEntity(cEntity * a_Entity); bool HasEntity(int a_UniqueID); - /// Removes the entity, the entity ptr ownership is assumed taken by the caller + /** Removes the entity, the entity ptr ownership is assumed taken by the caller */ void RemoveEntity(cEntity * a_Entity); - /// Calls the callback for each entity in the entire world; returns true if all entities processed, false if the callback aborted by returning true + /** Calls the callback for each entity in the entire world; returns true if all entities processed, false if the callback aborted by returning true */ bool ForEachEntity(cEntityCallback & a_Callback); // Exported in ManualBindings.cpp - /// Calls the callback for each entity in the specified chunk; returns true if all entities processed, false if the callback aborted by returning true + /** Calls the callback for each entity in the specified chunk; returns true if all entities processed, false if the callback aborted by returning true */ bool ForEachEntityInChunk(int a_ChunkX, int a_ChunkZ, cEntityCallback & a_Callback); // Exported in ManualBindings.cpp - /// Calls the callback if the entity with the specified ID is found, with the entity object as the callback param. Returns true if entity found and callback returned false. + /** Calls the callback if the entity with the specified ID is found, with the entity object as the callback param. Returns true if entity found and callback returned false. */ bool DoWithEntityByID(int a_UniqueID, cEntityCallback & a_Callback); // Exported in ManualBindings.cpp - /// Compares clients of two chunks, calls the callback accordingly + /** Compares clients of two chunks, calls the callback accordingly */ void CompareChunkClients(int a_ChunkX1, int a_ChunkZ1, int a_ChunkX2, int a_ChunkZ2, cClientDiffCallback & a_Callback); - /// Adds client to a chunk, if not already present; returns true if added, false if present + /** Adds client to a chunk, if not already present; returns true if added, false if present */ bool AddChunkClient(int a_ChunkX, int a_ChunkZ, cClientHandle * a_Client); - /// Removes client from the chunk specified + /** Removes client from the chunk specified */ void RemoveChunkClient(int a_ChunkX, int a_ChunkZ, cClientHandle * a_Client); - /// Removes the client from all chunks it is present in + /** Removes the client from all chunks it is present in */ void RemoveClientFromChunks(cClientHandle * a_Client); - /// Sends the chunk to the client specified, if the chunk is valid. If not valid, the request is postponed (ChunkSender will send that chunk when it becomes valid+lighted) + /** Sends the chunk to the client specified, if the chunk is valid. If not valid, the request is postponed (ChunkSender will send that chunk when it becomes valid+lighted) */ void SendChunkTo(int a_ChunkX, int a_ChunkZ, cClientHandle * a_Client); - /// Removes client from ChunkSender's queue of chunks to be sent + /** Removes client from ChunkSender's queue of chunks to be sent */ void RemoveClientFromChunkSender(cClientHandle * a_Client); - /// Touches the chunk, causing it to be loaded or generated + /** Touches the chunk, causing it to be loaded or generated */ void TouchChunk(int a_ChunkX, int a_ChunkY, int a_ChunkZ); - /// Loads the chunk, if not already loaded. Doesn't generate. Returns true if chunk valid (even if already loaded before) + /** Loads the chunk, if not already loaded. Doesn't generate. Returns true if chunk valid (even if already loaded before) */ bool LoadChunk(int a_ChunkX, int a_ChunkY, int a_ChunkZ); - /// Loads the chunks specified. Doesn't report failure, other than chunks being !IsValid() + /** Loads the chunks specified. Doesn't report failure, other than chunks being !IsValid() */ void LoadChunks(const cChunkCoordsList & a_Chunks); - /// Marks the chunk as failed-to-load: + /** Marks the chunk as failed-to-load: */ void ChunkLoadFailed(int a_ChunkX, int a_ChunkY, int a_ChunkZ); - /// 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 UpdateSign() + /** 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 UpdateSign() */ bool SetSignLines(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, cPlayer * a_Player = NULL); // Exported in ManualBindings.cpp - /// 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() + /** 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 - /// Marks (a_Stay == true) or unmarks (a_Stay == false) chunks as non-unloadable. To be used only by cChunkStay! + /** 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); - /// Regenerate the given chunk: + /** Regenerate the given chunk: */ void RegenerateChunk(int a_ChunkX, int a_ChunkZ); // tolua_export - /// Generates the given chunk, if not already generated + /** Generates the given chunk, if not already generated */ void GenerateChunk(int a_ChunkX, int a_ChunkZ); // tolua_export - /// Queues a chunk for lighting; a_Callback is called after the chunk is lighted + /** Queues a chunk for lighting; a_Callback is called after the chunk is lighted */ void QueueLightChunk(int a_ChunkX, int a_ChunkZ, cChunkCoordCallback * a_Callback = NULL); 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); + /** Calls the callback for each chunk in the coords specified (all cords are inclusive). Returns true if all chunks have been processed successfully */ + virtual bool ForEachChunkInRect(int a_MinChunkX, int a_MaxChunkX, int a_MinChunkZ, int a_MaxChunkZ, cChunkDataCallback & a_Callback); // tolua_begin @@ -362,34 +361,34 @@ 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 - /// Spawns item pickups for each item in the list. May compress pickups if too many entities: + /** Spawns item pickups for each item in the list. May compress pickups if too many entities: */ void SpawnItemPickups(const cItems & a_Pickups, double a_BlockX, double a_BlockY, double a_BlockZ, double a_FlyAwaySpeed = 1.0, bool IsPlayerCreated = false); - /// Spawns item pickups for each item in the list. May compress pickups if too many entities. All pickups get the speed specified: + /** Spawns item pickups for each item in the list. May compress pickups if too many entities. All pickups get the speed specified: */ void SpawnItemPickups(const cItems & a_Pickups, double a_BlockX, double a_BlockY, double a_BlockZ, double a_SpeedX, double a_SpeedY, double a_SpeedZ, bool IsPlayerCreated = false); - /// Spawns an falling block entity at the given position. It returns the UniqueID of the spawned falling block. + /** Spawns an falling block entity at the given position. It returns the UniqueID of the spawned falling block. */ int SpawnFallingBlock(int a_X, int a_Y, int a_Z, BLOCKTYPE BlockType, NIBBLETYPE BlockMeta); - /// Spawns an minecart at the given coordinates. + /** Spawns an minecart at the given coordinates. */ int SpawnMinecart(double a_X, double a_Y, double a_Z, int a_MinecartType, const cItem & a_Content = cItem(), int a_BlockHeight = 1); - /// Spawns an experience orb at the given location with the given reward. It returns the UniqueID of the spawned experience orb. + /** Spawns an experience orb at the given location with the given reward. It returns the UniqueID of the spawned experience orb. */ int SpawnExperienceOrb(double a_X, double a_Y, double a_Z, int a_Reward); - /// Spawns a new primed TNT entity at the specified block coords and specified fuse duration. Initial velocity is given based on the relative coefficient provided + /** Spawns a new primed TNT entity at the specified block coords and specified fuse duration. Initial velocity is given based on the relative coefficient provided */ void SpawnPrimedTNT(double a_X, double a_Y, double a_Z, double a_FuseTimeInSec, double a_InitialVelocityCoeff = 1); // tolua_end - /// Replaces world blocks with a_Blocks, if they are of type a_FilterBlockType + /** Replaces world blocks with a_Blocks, if they are of type a_FilterBlockType */ void ReplaceBlocks(const sSetBlockVector & a_Blocks, BLOCKTYPE a_FilterBlockType); - /// Retrieves block types of the specified blocks. If a chunk is not loaded, doesn't modify the block. Returns true if all blocks were read. + /** Retrieves block types of the specified blocks. If a chunk is not loaded, doesn't modify the block. Returns true if all blocks were read. */ bool GetBlocks(sSetBlockVector & a_Blocks, bool a_ContinueOnFailure); // tolua_begin @@ -400,10 +399,10 @@ public: double GetSpawnY(void) const { return m_SpawnY; } double GetSpawnZ(void) const { return m_SpawnZ; } - /// Wakes up the simulators for the specified block + /** Wakes up the simulators for the specified block */ void WakeUpSimulators(int a_BlockX, int a_BlockY, int a_BlockZ); - /// Wakes up the simulators for the specified area of blocks + /** Wakes up the simulators for the specified area of blocks */ void WakeUpSimulatorsInArea(int a_MinBlockX, int a_MaxBlockX, int a_MinBlockY, int a_MaxBlockY, int a_MinBlockZ, int a_MaxBlockZ); // tolua_end @@ -414,22 +413,22 @@ public: inline cFluidSimulator * GetLavaSimulator (void) { return m_LavaSimulator; } inline cRedstoneSimulator * GetRedstoneSimulator(void) { return m_RedstoneSimulator; } - /// Calls the callback for each block entity in the specified chunk; returns true if all block entities processed, false if the callback aborted by returning true + /** Calls the callback for each block entity in the specified chunk; returns true if all block entities processed, false if the callback aborted by returning true */ bool ForEachBlockEntityInChunk(int a_ChunkX, int a_ChunkZ, cBlockEntityCallback & a_Callback); // Exported in ManualBindings.cpp - /// Calls the callback for each chest in the specified chunk; returns true if all chests processed, false if the callback aborted by returning true + /** Calls the callback for each chest in the specified chunk; returns true if all chests processed, false if the callback aborted by returning true */ bool ForEachChestInChunk(int a_ChunkX, int a_ChunkZ, cChestCallback & a_Callback); // Exported in ManualBindings.cpp - /// Calls the callback for each dispenser in the specified chunk; returns true if all dispensers processed, false if the callback aborted by returning true + /** Calls the callback for each dispenser in the specified chunk; returns true if all dispensers processed, false if the callback aborted by returning true */ bool ForEachDispenserInChunk(int a_ChunkX, int a_ChunkZ, cDispenserCallback & a_Callback); - /// Calls the callback for each dropper in the specified chunk; returns true if all droppers processed, false if the callback aborted by returning true + /** Calls the callback for each dropper in the specified chunk; returns true if all droppers processed, false if the callback aborted by returning true */ bool ForEachDropperInChunk(int a_ChunkX, int a_ChunkZ, cDropperCallback & a_Callback); - /// Calls the callback for each dropspenser in the specified chunk; returns true if all dropspensers processed, false if the callback aborted by returning true + /** Calls the callback for each dropspenser in the specified chunk; returns true if all dropspensers processed, false if the callback aborted by returning true */ bool ForEachDropSpenserInChunk(int a_ChunkX, int a_ChunkZ, cDropSpenserCallback & a_Callback); - /// Calls the callback for each furnace in the specified chunk; returns true if all furnaces processed, false if the callback aborted by returning true + /** Calls the callback for each furnace in the specified chunk; returns true if all furnaces processed, false if the callback aborted by returning true */ bool ForEachFurnaceInChunk(int a_ChunkX, int a_ChunkZ, cFurnaceCallback & a_Callback); // Exported in ManualBindings.cpp /** Does an explosion with the specified strength at the specified coordinate @@ -447,72 +446,79 @@ public: */ void DoExplosionAt(double a_ExplosionSize, double a_BlockX, double a_BlockY, double a_BlockZ, bool a_CanCauseFire, eExplosionSource a_Source, void * a_SourceData); // tolua_export - /// Calls the callback for the block entity at the specified coords; returns false if there's no block entity at those coords, true if found + /** Calls the callback for the block entity at the specified coords; returns false if there's no block entity at those coords, true if found */ bool DoWithBlockEntityAt(int a_BlockX, int a_BlockY, int a_BlockZ, cBlockEntityCallback & a_Callback); // Exported in ManualBindings.cpp - /// Calls the callback for the chest at the specified coords; returns false if there's no chest at those coords, true if found + /** Calls the callback for the chest at the specified coords; returns false if there's no chest at those coords, true if found */ bool DoWithChestAt(int a_BlockX, int a_BlockY, int a_BlockZ, cChestCallback & a_Callback); // Exported in ManualBindings.cpp - /// Calls the callback for the dispenser at the specified coords; returns false if there's no dispenser at those coords or callback returns true, returns true if found + /** Calls the callback for the dispenser at the specified coords; returns false if there's no dispenser at those coords or callback returns true, returns true if found */ bool DoWithDispenserAt(int a_BlockX, int a_BlockY, int a_BlockZ, cDispenserCallback & a_Callback); // Exported in ManualBindings.cpp - /// Calls the callback for the dropper at the specified coords; returns false if there's no dropper at those coords or callback returns true, returns true if found + /** Calls the callback for the dropper at the specified coords; returns false if there's no dropper at those coords or callback returns true, returns true if found */ bool DoWithDropperAt(int a_BlockX, int a_BlockY, int a_BlockZ, cDropperCallback & a_Callback); // Exported in ManualBindings.cpp - /// Calls the callback for the dropspenser at the specified coords; returns false if there's no dropspenser at those coords or callback returns true, returns true if found + /** Calls the callback for the dropspenser at the specified coords; returns false if there's no dropspenser at those coords or callback returns true, returns true if found */ bool DoWithDropSpenserAt(int a_BlockX, int a_BlockY, int a_BlockZ, cDropSpenserCallback & a_Callback); // Exported in ManualBindings.cpp - /// Calls the callback for the furnace at the specified coords; returns false if there's no furnace at those coords or callback returns true, returns true if found + /** Calls the callback for the furnace at the specified coords; returns false if there's no furnace at those coords or callback returns true, returns true if found */ bool DoWithFurnaceAt(int a_BlockX, int a_BlockY, int a_BlockZ, cFurnaceCallback & a_Callback); // Exported in ManualBindings.cpp - /// Calls the callback for the noteblock at the specified coords; returns false if there's no noteblock at those coords or callback returns true, returns true if found + /** Calls the callback for the noteblock at the specified coords; returns false if there's no noteblock at those coords or callback returns true, returns true if found */ bool DoWithNoteBlockAt(int a_BlockX, int a_BlockY, int a_BlockZ, cNoteBlockCallback & a_Callback); // Exported in ManualBindings.cpp - /// Calls the callback for the command block at the specified coords; returns false if there's no command block at those coords or callback returns true, returns true if found + /** Calls the callback for the command block at the specified coords; returns false if there's no command block at those coords or callback returns true, returns true if found */ bool DoWithCommandBlockAt(int a_BlockX, int a_BlockY, int a_BlockZ, cCommandBlockCallback & a_Callback); // Exported in ManualBindings.cpp - /// Retrieves the test on the sign at the specified coords; returns false if there's no sign at those coords, true if found + /** Retrieves the test on the sign at the specified coords; returns false if there's no sign at those coords, true if found */ bool GetSignLines (int a_BlockX, int a_BlockY, int a_BlockZ, AString & a_Line1, AString & a_Line2, AString & a_Line3, AString & a_Line4); // Exported in ManualBindings.cpp - /// a_Player is using block entity at [x, y, z], handle that: + /** a_Player is using block entity at [x, y, z], handle that: */ void UseBlockEntity(cPlayer * a_Player, int a_BlockX, int a_BlockY, int a_BlockZ) {m_ChunkMap->UseBlockEntity(a_Player, a_BlockX, a_BlockY, a_BlockZ); } // tolua_export - /// Calls the callback for the chunk specified, with ChunkMapCS locked; returns false if the chunk doesn't exist, otherwise returns the same value as the callback + /** Calls the callback for the chunk specified, with ChunkMapCS locked; returns false if the chunk doesn't exist, otherwise returns the same value as the callback */ bool DoWithChunk(int a_ChunkX, int a_ChunkZ, cChunkCallback & a_Callback); void GrowTreeImage(const sSetBlockVector & a_Blocks); // tolua_begin - /// Grows a tree at the specified coords, either from a sapling there, or based on the biome + /** Grows a tree at the specified coords, either from a sapling there, or based on the biome */ void GrowTree (int a_BlockX, int a_BlockY, int a_BlockZ); - /// Grows a tree at the specified coords, based on the sapling meta provided + /** Grows a tree at the specified coords, based on the sapling meta provided */ void GrowTreeFromSapling(int a_BlockX, int a_BlockY, int a_BlockZ, NIBBLETYPE a_SaplingMeta); - /// Grows a tree at the specified coords, based on the biome in the place + /** Grows a tree at the specified coords, based on the biome in the place */ void GrowTreeByBiome (int a_BlockX, int a_BlockY, int a_BlockZ); - /// Grows the plant at the specified block to its ripe stage (bonemeal used); returns false if the block is not growable. If a_IsBonemeal is true, block is not grown if not allowed in world.ini + /** Grows the plant at the specified block to its ripe stage (bonemeal used); returns false if the block is not growable. If a_IsBonemeal is true, block is not grown if not allowed in world.ini */ bool GrowRipePlant(int a_BlockX, int a_BlockY, int a_BlockZ, bool a_IsByBonemeal = false); - /// Grows a cactus present at the block specified by the amount of blocks specified, up to the max height specified in the config + /** Grows a cactus present at the block specified by the amount of blocks specified, up to the max height specified in the config */ void GrowCactus(int a_BlockX, int a_BlockY, int a_BlockZ, int a_NumBlocksToGrow); - /// Grows a melon or a pumpkin next to the block specified (assumed to be the stem) + /** Grows a melon or a pumpkin next to the block specified (assumed to be the stem) */ void GrowMelonPumpkin(int a_BlockX, int a_BlockY, int a_BlockZ, BLOCKTYPE a_BlockType); - /// Grows a sugarcane present at the block specified by the amount of blocks specified, up to the max height specified in the config + /** Grows a sugarcane present at the block specified by the amount of blocks specified, up to the max height specified in the config */ void GrowSugarcane(int a_BlockX, int a_BlockY, int a_BlockZ, int a_NumBlocksToGrow); - /// Returns the biome at the specified coords. Reads the biome from the chunk, if loaded, otherwise uses the world generator to provide the biome value + /** Returns the biome at the specified coords. Reads the biome from the chunk, if loaded, otherwise uses the world generator to provide the biome value */ int GetBiomeAt(int a_BlockX, int a_BlockZ); - /// Returns the name of the world + /** Returns the name of the world */ const AString & GetName(void) const { return m_WorldName; } - /// Returns the name of the world.ini file used by this world + /** 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 @@ -543,22 +549,23 @@ public: if(a_Z < 0 && a_Z % cChunkDef::Width != 0) a_ChunkZ--; } - /// Saves all chunks immediately. Dangerous interface, may deadlock, use QueueSaveAllChunks() instead + /** Saves all chunks immediately. Dangerous interface, may deadlock, use QueueSaveAllChunks() instead */ void SaveAllChunks(void); - /// Queues a task to save all chunks onto the tick thread. The prefferred way of saving chunks from external sources + /** Queues a task to save all chunks onto the tick thread. The prefferred way of saving chunks from external sources */ void QueueSaveAllChunks(void); // tolua_export - /// Queues a task onto the tick thread. The task object will be deleted once the task is finished + /** Queues a task onto the tick thread. The task object will be deleted once the task is finished */ void QueueTask(cTask * a_Task); // Exported in ManualBindings.cpp - // Queues a task onto the tick thread. The task object will be deleted once the task is finished - void ScheduleTask(cScheduledTask * a_Task); + /** Queues a task onto the tick thread, with the specified delay. + The task object will be deleted once the task is finished */ + void ScheduleTask(int a_DelayTicks, cTask * a_Task); - /// Returns the number of chunks loaded + /** Returns the number of chunks loaded */ int GetNumChunks() const; // tolua_export - /// Returns the number of chunks loaded and dirty, and in the lighting queue + /** Returns the number of chunks loaded and dirty, and in the lighting queue */ void GetChunkStats(int & a_NumValid, int & a_NumDirty, int & a_NumInLightingQueue); // Various queues length queries (cannot be const, they lock their CS): @@ -569,13 +576,13 @@ public: void InitializeSpawn(void); - /// Starts threads that belong to this world + /** Starts threads that belong to this world */ void Start(void); - /// Stops threads that belong to this world (part of deinit) + /** Stops threads that belong to this world (part of deinit) */ void Stop(void); - /// Processes the blocks queued for ticking with a delay (m_BlockTickQueue[]) + /** Processes the blocks queued for ticking with a delay (m_BlockTickQueue[]) */ void TickQueuedBlocks(void); struct BlockTickQueueItem @@ -586,27 +593,27 @@ public: int TicksToWait; }; - /// Queues the block to be ticked after the specified number of game ticks + /** Queues the block to be ticked after the specified number of game ticks */ void QueueBlockForTick(int a_BlockX, int a_BlockY, int a_BlockZ, int a_TicksToWait); // tolua_export // tolua_begin - /// Casts a thunderbolt at the specified coords + /** Casts a thunderbolt at the specified coords */ void CastThunderbolt(int a_BlockX, int a_BlockY, int a_BlockZ); - /// Sets the specified weather; resets weather interval; asks and notifies plugins of the change + /** Sets the specified weather; resets weather interval; asks and notifies plugins of the change */ void SetWeather (eWeather a_NewWeather); - /// Forces a weather change in the next game tick + /** Forces a weather change in the next game tick */ void ChangeWeather (void); - /// Returns the current weather. Instead of comparing values directly to the weather constants, use IsWeatherXXX() functions, if possible + /** Returns the current weather. Instead of comparing values directly to the weather constants, use IsWeatherXXX() functions, if possible */ eWeather GetWeather (void) const { return m_Weather; }; bool IsWeatherSunny(void) const { return (m_Weather == wSunny); } bool IsWeatherRain (void) const { return (m_Weather == wRain); } bool IsWeatherStorm(void) const { return (m_Weather == wStorm); } - /// Returns true if the current weather has any precipitation - rain or storm + /** Returns true if the current weather has any precipitation - rain or storm */ bool IsWeatherWet (void) const { return (m_Weather != wSunny); } // tolua_end @@ -615,7 +622,7 @@ public: cWorldStorage & GetStorage (void) { return m_Storage; } cChunkMap * GetChunkMap (void) { return m_ChunkMap; } - /// Sets the blockticking to start at the specified block. Only one blocktick per chunk may be set, second call overwrites the first call + /** Sets the blockticking to start at the specified block. Only one blocktick per chunk may be set, second call overwrites the first call */ void SetNextBlockTick(int a_BlockX, int a_BlockY, int a_BlockZ); // tolua_export int GetMaxSugarcaneHeight(void) const { return m_MaxSugarcaneHeight; } // tolua_export @@ -623,20 +630,20 @@ public: bool IsBlockDirectlyWatered(int a_BlockX, int a_BlockY, int a_BlockZ); // tolua_export - /// Spawns a mob of the specified type. Returns the mob's EntityID if recognized and spawned, <0 otherwise + /** Spawns a mob of the specified type. Returns the mob's EntityID if recognized and spawned, <0 otherwise */ int SpawnMob(double a_PosX, double a_PosY, double a_PosZ, cMonster::eType a_MonsterType); // tolua_export int SpawnMobFinalize(cMonster* a_Monster); - /// Creates a projectile of the specified type. Returns the projectile's EntityID if successful, <0 otherwise + /** Creates a projectile of the specified type. Returns the projectile's EntityID if successful, <0 otherwise */ int CreateProjectile(double a_PosX, double a_PosY, double a_PosZ, cProjectileEntity::eKind a_Kind, cEntity * a_Creator, const Vector3d * a_Speed = NULL); // tolua_export - /// Returns a random number from the m_TickRand in range [0 .. a_Range]. To be used only in the tick thread! + /** Returns a random number from the m_TickRand in range [0 .. a_Range]. To be used only in the tick thread! */ int GetTickRandomNumber(unsigned a_Range) { return (int)(m_TickRand.randInt(a_Range)); } - /// Appends all usernames starting with a_Text (case-insensitive) into Results + /** Appends all usernames starting with a_Text (case-insensitive) into Results */ void TabCompleteUserName(const AString & a_Text, AStringVector & a_Results); - /// Get the current darkness level based on the time + /** Get the current darkness level based on the time */ NIBBLETYPE GetSkyDarkness() { return m_SkyDarkness; } private: @@ -679,18 +686,41 @@ private: } ; + /** A container for tasks that have been scheduled for a specific game tick */ + class cScheduledTask + { + public: + Int64 m_TargetTick; + cTask * m_Task; + + /** Creates a new scheduled task; takes ownership of the task object passed to it. */ + cScheduledTask(Int64 a_TargetTick, cTask * a_Task) : + m_TargetTick(a_TargetTick), + m_Task(a_Task) + { + } + + virtual ~cScheduledTask() + { + delete m_Task; + } + }; + + typedef std::list<cScheduledTask *> cScheduledTasks; + + AString m_WorldName; AString m_IniFileName; - /// Name of the storage schema used to load and save chunks + /** Name of the storage schema used to load and save chunks */ AString m_StorageSchema; int m_StorageCompressionFactor; - /// The dimension of the world, used by the client to provide correct lighting scheme + /** The dimension of the world, used by the client to provide correct lighting scheme */ eDimension m_Dimension; - /// This random generator is to be used only in the Tick() method, and thus only in the World-Tick-thread (MTRand is not exactly thread-safe) + /** This random generator is to be used only in the Tick() method, and thus only in the World-Tick-thread (MTRand is not exactly thread-safe) */ MTRand m_TickRand; bool m_IsSpawnExplicitlySet; @@ -752,11 +782,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; @@ -765,29 +799,30 @@ private: cLightingThread m_Lighting; cTickThread m_TickThread; - /// Guards the m_Tasks + /** Guards the m_Tasks */ cCriticalSection m_CSTasks; - /// Guards the m_ScheduledTasks - cCriticalSection m_CSScheduledTasks; - - /// Tasks that have been queued onto the tick thread; guarded by m_CSTasks + /** Tasks that have been queued onto the tick thread; guarded by m_CSTasks */ cTasks m_Tasks; - /// Tasks that have been queued to be executed on the tick thread at some number of ticks in - /// the future; guarded by m_CSScheduledTasks - ScheduledTaskList m_ScheduledTasks; + /** Guards the m_ScheduledTasks */ + cCriticalSection m_CSScheduledTasks; + + /** Tasks that have been queued to be executed on the tick thread at target tick in the future. + Ordered by increasing m_TargetTick. + Guarded by m_CSScheduledTasks */ + cScheduledTasks m_ScheduledTasks; - /// Guards m_Clients + /** Guards m_Clients */ cCriticalSection m_CSClients; - /// List of clients in this world, these will be ticked by this world + /** List of clients in this world, these will be ticked by this world */ cClientHandleList m_Clients; - /// Clients that are scheduled for removal (ticked in another world), waiting for TickClients() to remove them + /** Clients that are scheduled for removal (ticked in another world), waiting for TickClients() to remove them */ cClientHandleList m_ClientsToRemove; - /// Clients that are scheduled for adding, waiting for TickClients to add them + /** Clients that are scheduled for adding, waiting for TickClients to add them */ cClientHandleList m_ClientsToAdd; @@ -796,27 +831,27 @@ private: void Tick(float a_Dt, int a_LastTickDurationMSec); - /// Handles the weather in each tick + /** Handles the weather in each tick */ void TickWeather(float a_Dt); - /// Handles the mob spawning/moving/destroying each tick + /** Handles the mob spawning/moving/destroying each tick */ void TickMobs(float a_Dt); - /// Executes all tasks queued onto the tick thread + /** Executes all tasks queued onto the tick thread */ void TickQueuedTasks(void); - /// Executes all tasks queued onto the tick thread + /** Executes all tasks queued onto the tick thread */ void TickScheduledTasks(void); - /// Ticks all clients that are in this world + /** Ticks all clients that are in this world */ void TickClients(float a_Dt); void UpdateSkyDarkness(void); - /// <summary>Generates a random spawnpoint on solid land by walking chunks and finding their biomes</summary> + /** <summary>Generates a random spawnpoint on solid land by walking chunks and finding their biomes</summary> */ void GenerateRandomSpawn(void); - /// Creates a new fluid simulator, loads its settings from the inifile (a_FluidName section) + /** Creates a new fluid simulator, loads its settings from the inifile (a_FluidName section) */ cFluidSimulator * InitializeFluidSimulator(cIniFile & a_IniFile, const char * a_FluidName, BLOCKTYPE a_SimulateBlock, BLOCKTYPE a_StationaryBlock); }; // tolua_export diff --git a/src/WorldStorage/EnchantmentSerializer.cpp b/src/WorldStorage/EnchantmentSerializer.cpp new file mode 100644 index 000000000..56072207f --- /dev/null +++ b/src/WorldStorage/EnchantmentSerializer.cpp @@ -0,0 +1,88 @@ + +#include "Globals.h" + +#include "EnchantmentSerializer.h" +#include "Enchantments.h" +#include "FastNBT.h" + +void EnchantmentSerializer::WriteToNBTCompound(cEnchantments const& a_Enchantments, cFastNBTWriter & a_Writer, const AString & a_ListTagName) +{ + // Write the enchantments into the specified NBT writer + // begin with the LIST tag of the specified name ("ench" or "StoredEnchantments") + + a_Writer.BeginList(a_ListTagName, TAG_Compound); + for (cEnchantments::cMap::const_iterator itr = a_Enchantments.m_Enchantments.begin(), end = a_Enchantments.m_Enchantments.end(); itr != end; ++itr) + { + a_Writer.BeginCompound(""); + a_Writer.AddShort("id", itr->first); + a_Writer.AddShort("lvl", itr->second); + a_Writer.EndCompound(); + } // for itr - m_Enchantments[] + a_Writer.EndList(); +} + + + + + +void EnchantmentSerializer::ParseFromNBT(cEnchantments& a_Enchantments, const cParsedNBT & a_NBT, int a_EnchListTagIdx) +{ + // Read the enchantments from the specified NBT list tag (ench or StoredEnchantments) + + // Verify that the tag is a list: + if (a_NBT.GetType(a_EnchListTagIdx) != TAG_List) + { + LOGWARNING("%s: Invalid EnchListTag type: exp %d, got %d. Enchantments not parsed", + __FUNCTION__, TAG_List, a_NBT.GetType(a_EnchListTagIdx) + ); + ASSERT(!"Bad EnchListTag type"); + return; + } + + // Verify that the list is of Compounds: + if (a_NBT.GetChildrenType(a_EnchListTagIdx) != TAG_Compound) + { + LOGWARNING("%s: Invalid NBT list children type: exp %d, got %d. Enchantments not parsed", + __FUNCTION__, TAG_Compound, a_NBT.GetChildrenType(a_EnchListTagIdx) + ); + ASSERT(!"Bad EnchListTag children type"); + return; + } + + a_Enchantments.Clear(); + + // Iterate over all the compound children, parse an enchantment from each: + for (int tag = a_NBT.GetFirstChild(a_EnchListTagIdx); tag >= 0; tag = a_NBT.GetNextSibling(tag)) + { + // tag is the compound inside the "ench" list tag + ASSERT(a_NBT.GetType(tag) == TAG_Compound); + + // Search for the id and lvl tags' values: + int id = -1, lvl = -1; + for (int ch = a_NBT.GetFirstChild(tag); ch >= 0; ch = a_NBT.GetNextSibling(ch)) + { + if (a_NBT.GetType(ch) != TAG_Short) + { + continue; + } + if (a_NBT.GetName(ch) == "id") + { + id = a_NBT.GetShort(ch); + } + else if (a_NBT.GetName(ch) == "lvl") + { + lvl = a_NBT.GetShort(ch); + } + } // for ch - children of the compound tag + + if ((id == -1) || (lvl <= 0)) + { + // Failed to parse either the id or the lvl, skip this compound + continue; + } + + // Store the enchantment: + a_Enchantments.m_Enchantments[id] = lvl; + } // for tag - children of the ench list tag +} + diff --git a/src/WorldStorage/EnchantmentSerializer.h b/src/WorldStorage/EnchantmentSerializer.h new file mode 100644 index 000000000..9ed362900 --- /dev/null +++ b/src/WorldStorage/EnchantmentSerializer.h @@ -0,0 +1,17 @@ + +#pragma once + +class cEnchantments; +class cFastNBTWriter; +class cParsedNBT; + +namespace EnchantmentSerializer +{ + + /// Writes the enchantments into the specified NBT writer; begins with the LIST tag of the specified name ("ench" or "StoredEnchantments") + void WriteToNBTCompound(cEnchantments const& a_Enchantments, cFastNBTWriter & a_Writer, const AString & a_ListTagName); + + /// Reads the enchantments from the specified NBT list tag (ench or StoredEnchantments) + void ParseFromNBT(cEnchantments& a_Enchantments, const cParsedNBT & a_NBT, int a_EnchListTagIdx); + +}; diff --git a/src/WorldStorage/NBTChunkSerializer.cpp b/src/WorldStorage/NBTChunkSerializer.cpp index 447296c7f..e46a28caa 100644 --- a/src/WorldStorage/NBTChunkSerializer.cpp +++ b/src/WorldStorage/NBTChunkSerializer.cpp @@ -4,6 +4,7 @@ #include "Globals.h" #include "NBTChunkSerializer.h" +#include "EnchantmentSerializer.h" #include "../BlockID.h" #include "../ItemGrid.h" #include "../StringCompression.h" @@ -92,7 +93,7 @@ void cNBTChunkSerializer::AddItem(const cItem & a_Item, int a_Slot, const AStrin { const char * TagName = (a_Item.m_ItemType == E_ITEM_BOOK) ? "StoredEnchantments" : "ench"; m_Writer.BeginCompound("tag"); - a_Item.m_Enchantments.WriteToNBTCompound(m_Writer, TagName); + EnchantmentSerializer::WriteToNBTCompound(a_Item.m_Enchantments, m_Writer, TagName); m_Writer.EndCompound(); } @@ -651,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 96a77152b..e2a882f65 100644 --- a/src/WorldStorage/WSSAnvil.cpp +++ b/src/WorldStorage/WSSAnvil.cpp @@ -7,6 +7,7 @@ #include "WSSAnvil.h" #include "NBTChunkSerializer.h" #include "FastNBT.h" +#include "EnchantmentSerializer.h" #include "zlib/zlib.h" #include "../World.h" #include "../BlockID.h" @@ -644,7 +645,7 @@ bool cWSSAnvil::LoadItemFromNBT(cItem & a_Item, const cParsedNBT & a_NBT, int a_ int EnchTag = a_NBT.FindChildByName(TagTag, EnchName); if (EnchTag > 0) { - a_Item.m_Enchantments.ParseFromNBT(a_NBT, EnchTag); + EnchantmentSerializer::ParseFromNBT(a_Item.m_Enchantments, a_NBT, EnchTag); } return true; @@ -1925,17 +1926,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..06b344c25 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -19,6 +19,13 @@ bool g_SERVER_TERMINATED = false; // Set to true when the server terminates, so +/** If set to true, the protocols will log each player's communication to a separate logfile */ +bool g_ShouldLogComm; + + + + + /// If defined, a thorough leak finder will be used (debug MSVC only); leaks will be output to the Output window #define ENABLE_LEAK_FINDER @@ -216,12 +223,24 @@ int main( int argc, char **argv ) #ifndef _DEBUG std::signal(SIGSEGV, NonCtrlHandler); std::signal(SIGTERM, NonCtrlHandler); - std::signal(SIGINT, NonCtrlHandler); + std::signal(SIGINT, NonCtrlHandler); #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_ShouldLogComm = true; + } + } + #if !defined(ANDROID_NDK) try #endif |