diff options
30 files changed, 2255 insertions, 276 deletions
diff --git a/MCServer/Plugins/APIDump/APIDesc.lua b/MCServer/Plugins/APIDump/APIDesc.lua index 347299a50..07345d51e 100644 --- a/MCServer/Plugins/APIDump/APIDesc.lua +++ b/MCServer/Plugins/APIDump/APIDesc.lua @@ -1667,7 +1667,7 @@ a_Player:OpenWindow(Window); ]], Functions = { - Call = { Params = "Function name, [All the parameters divided with commas]", Notes = "This function allows you to call a function from another plugin. It can only use pass: integers, booleans, strings and usertypes (cPlayer, cEntity, cCuboid, etc.)." }, + Call = { Params = "Function name, [All the parameters divided with commas]", Notes = "(<b>OBSOLETE</b>) This function allows you to call a function from another plugin. It can only use pass: integers, booleans, strings and usertypes (cPlayer, cEntity, cCuboid, etc.).<br /><br /><b>This function is obsolete and unsafe, use {{cPluginManager}}:CallPlugin() instead!</b>" }, GetDirectory = { Return = "string", Notes = "Returns the name of the folder where the plugin's files are. (APIDump)" }, GetLocalDirectory = { Notes = "OBSOLETE use GetLocalFolder instead." }, GetLocalFolder = { Return = "string", Notes = "Returns the path where the plugin's files are. (Plugins/APIDump)" }, @@ -1719,6 +1719,7 @@ cPluginManager.AddHook(cPluginManager.HOOK_CHAT, OnChatMessage); { Params = "Command, Callback, HelpString", Return = "", Notes = "(STATIC) Binds a console command with the specified callback function and help string. By common convention, providing an empty string for HelpString will hide the command from the \"help\" console command." }, { Params = "Command, Callback, HelpString", Return = "", Notes = "Binds a console command with the specified callback function and help string. By common convention, providing an empty string for HelpString will hide the command from the \"help\" console command." }, }, + CallPlugin = { Params = "PluginName, FunctionName, [FunctionArgs...]", Return = "[FunctionRets]", Notes = "(STATIC) Calls the specified function in the specified plugin, passing all the given arguments to it. If it succeeds, it returns all the values returned by that function. If it fails, returns no value at all. Note that only strings, numbers, bools, nils and classes can be used for parameters and return values; tables and functions cannot be copied across plugins." }, DisablePlugin = { Params = "PluginName", Return = "bool", Notes = "Disables a plugin specified by its name. Returns true if the plugin was disabled, false if it wasn't found or wasn't active." }, ExecuteCommand = { Params = "{{cPlayer|Player}}, CommandStr", Return = "bool", Notes = "Executes the command as if given by the specified Player. Checks permissions. Returns true if executed." }, FindPlugins = { Params = "", Return = "", Notes = "Refreshes the list of plugins to include all folders inside the Plugins folder (potentially new disabled plugins)" }, diff --git a/MCServer/Plugins/Debuggers/Debuggers.lua b/MCServer/Plugins/Debuggers/Debuggers.lua index 2d2d2736d..624261cbf 100644 --- a/MCServer/Plugins/Debuggers/Debuggers.lua +++ b/MCServer/Plugins/Debuggers/Debuggers.lua @@ -64,7 +64,8 @@ function Initialize(Plugin) -- TestBlockAreas(); -- TestSQLiteBindings(); -- TestExpatBindings(); - + TestPluginCalls(); + return true end; @@ -72,6 +73,38 @@ end; +function TestPluginCalls() + -- In order to test the inter-plugin communication, we're going to call Core's ReturnColorFromChar() function + -- It is a rather simple function that doesn't need any tables as its params and returns a value, too + -- Note the signature: function ReturnColorFromChar( Split, char ) ... return cChatColog.Gray ... end + -- The Split parameter should be a table, but it is not used in that function anyway, + -- so we can get away with passing nil to it. + + -- Use the old, deprecated and unsafe method: + local Core = cPluginManager:Get():GetPlugin("Core") + if (Core ~= nil) then + LOGINFO("Calling Core::ReturnColorFromChar() the old-fashioned way...") + local Gray = Core:Call("ReturnColorFromChar", nil, "8") + if (Gray ~= cChatColor.Gray) then + LOGWARNING("Call failed, exp " .. cChatColor.Gray .. ", got " .. (Gray or "<nil>")) + else + LOGINFO("Call succeeded") + end + end + + -- Use the new method: + LOGINFO("Calling Core::ReturnColorFromChar() the recommended way...") + local Gray = cPluginManager:CallPlugin("Core", "ReturnColorFromChar", nil, "8") + if (Gray ~= cChatColor.Gray) then + LOGWARNING("Call failed, exp " .. cChatColor.Gray .. ", got " .. (Gray or "<nil>")) + else + LOGINFO("Call succeeded") + end +end + + + + function TestBlockAreas() LOG("Testing block areas..."); diff --git a/MCServer/Plugins/InfoDump.lua b/MCServer/Plugins/InfoDump.lua index df47d566b..6b1da7a77 100644 --- a/MCServer/Plugins/InfoDump.lua +++ b/MCServer/Plugins/InfoDump.lua @@ -2,10 +2,17 @@ -- InfoDump.lua --- Goes through all subfolders, loads Info.lua and dumps its g_PluginInfo into various text formats --- This is used for generating plugin documentation for the forum and for GitHub's INFO.md files +--[[ +Loads plugins' Info.lua and dumps its g_PluginInfo into various text formats +This is used for generating plugin documentation for the forum and for GitHub's INFO.md files --- This script requires LuaRocks with LFS installed, instructions are printed when this is not present. +This script can be used in two ways: +Executing "lua InfoDump.lua" will go through all subfolders and dump each Info.lua file it can find + Note that this mode of operation requires LuaRocks with LFS installed; instructions are printed + when the prerequisites are not met. +Executing "lua InfoDump.lua PluginName" will load the Info.lua file from PluginName's folder and dump +only that one plugin's documentation. This mode of operation doesn't require LuaRocks +--]] @@ -17,33 +24,6 @@ if (_VERSION ~= "Lua 5.1") then return; end --- Try to load lfs, do not abort if not found -local lfs, err = pcall( - function() - return require("lfs") - end -); - --- Rather, print a nice message with instructions: -if not(lfs) then - print([[ -Cannot load LuaFileSystem -Install it through luarocks by executing the following command: - sudo luarocks install luafilesystem - -If you don't have luarocks installed, you need to install them using your OS's package manager, usually: - sudo apt-get install luarocks -On windows, a binary distribution can be downloaded from the LuaRocks homepage, http://luarocks.org/en/Download -]]); - - print("Original error text: ", err); - return; -end - --- We now know that LFS is present, get it normally: -lfs = require("lfs"); - - @@ -161,6 +141,21 @@ end +--- Returns a string specifying the command. +-- If a_Command is a simple string, returns a_Command colorized to blue +-- If a_Command is a table, expects members Name (full command name) and Params (command parameters), +-- colorizes command name blue and params green +local function GetCommandRefForum(a_Command) + if (type(a_Command) == "string") then + return "[color=blue]" .. a_Command .. "[/color]"; + end + return "[color=blue]" .. a_Command.Name .. "[/color] [color=green]" .. a_Command.Params .. "[/color]"; +end + + + + + --- Writes the specified command detailed help array to the output file, in the forum dump format local function WriteCommandParameterCombinationsForum(a_CmdString, a_ParameterCombinations, f) assert(type(a_CmdString) == "string"); @@ -270,6 +265,97 @@ end +--- Collects all permissions mentioned in the info, returns them as a sorted array +-- Each array item is {Name = "PermissionName", Info = { PermissionInfo }} +local function BuildPermissions(a_PluginInfo) + -- Collect all used permissions from Commands, reference the commands that use the permission: + local Permissions = a_PluginInfo.Permissions or {}; + local function CollectPermissions(a_CmdPrefix, a_Commands) + for cmd, info in pairs(a_Commands) do + CommandString = a_CmdPrefix .. cmd; + if ((info.Permission ~= nil) and (info.Permission ~= "")) then + -- Add the permission to the list of permissions: + local Permission = Permissions[info.Permission] or {}; + Permissions[info.Permission] = Permission; + -- Add the command to the list of commands using this permission: + Permission.CommandsAffected = Permission.CommandsAffected or {}; + table.insert(Permission.CommandsAffected, CommandString); + end + + -- Process the command param combinations for permissions: + local ParamCombinations = info.ParameterCombinations or {}; + for idx, comb in ipairs(ParamCombinations) do + if ((comb.Permission ~= nil) and (comb.Permission ~= "")) then + -- Add the permission to the list of permissions: + local Permission = Permissions[comb.Permission] or {}; + Permissions[info.Permission] = Permission; + -- Add the command to the list of commands using this permission: + Permission.CommandsAffected = Permission.CommandsAffected or {}; + table.insert(Permission.CommandsAffected, {Name = CommandString, Params = comb.Params}); + end + end + + -- Process subcommands: + if (info.Subcommands ~= nil) then + CollectPermissions(CommandString .. " ", info.Subcommands); + end + end + end + CollectPermissions("", a_PluginInfo.Commands); + + -- Copy the list of permissions to an array: + local PermArray = {}; + for name, perm in pairs(Permissions) do + table.insert(PermArray, {Name = name, Info = perm}); + end + + -- Sort the permissions array: + table.sort(PermArray, + function(p1, p2) + return (p1.Name < p2.Name); + end + ); + return PermArray; +end + + + + + +local function DumpPermissionsForum(a_PluginInfo, f) + -- Get the processed sorted array of permissions: + local Permissions = BuildPermissions(a_PluginInfo); + if ((Permissions == nil) or (#Permissions <= 0)) then + return; + end + + -- Dump the permissions: + f:write("\n[size=X-Large]Permissions[/size]\n[list]\n"); + for idx, perm in ipairs(Permissions) do + f:write(" - [color=red]", perm.Name, "[/color] - "); + f:write(perm.Info.Description or ""); + local CommandsAffected = perm.Info.CommandsAffected or {}; + if (#CommandsAffected > 0) then + f:write("\n[list] Commands affected:\n- "); + local Affects = {}; + for idx2, cmd in ipairs(CommandsAffected) do + table.insert(Affects, GetCommandRefForum(cmd)); + end + f:write(table.concat(Affects, "\n - ")); + f:write("\n[/list]"); + end + if (perm.Info.RecommendedGroups ~= nil) then + f:write("\n[list] Recommended groups: ", perm.Info.RecommendedGroups, "[/list]"); + end + f:write("\n"); + end + f:write("[/list]"); +end + + + + + local function DumpPluginInfoForum(a_PluginFolder, a_PluginInfo) -- Open the output file: local f, msg = io.open(a_PluginInfo.Name .. "_forum.txt", "w"); @@ -282,6 +368,7 @@ local function DumpPluginInfoForum(a_PluginFolder, a_PluginInfo) f:write(ForumizeString(a_PluginInfo.Description), "\n"); DumpAdditionalInfoForum(a_PluginInfo, f); DumpCommandsForum(a_PluginInfo, f); + DumpPermissionsForum(a_PluginInfo, f); f:close(); end @@ -301,12 +388,6 @@ end --- Tries to load the g_PluginInfo from the plugin's Info.lua file -- Returns the g_PluginInfo table on success, or nil and error message on failure local function LoadPluginInfo(a_FolderName) - -- Check if the Info file is present at all: - local Attribs = lfs.attributes(a_FolderName .. "/Info.lua"); - if ((Attribs == nil) or (Attribs.mode ~= "file")) then - return nil; - end - -- Load and compile the Info file: local cfg, err = loadfile(a_FolderName .. "/Info.lua"); if (cfg == nil) then @@ -332,7 +413,7 @@ local function ProcessPluginFolder(a_FolderName) local PluginInfo, Msg = LoadPluginInfo(a_FolderName); if (PluginInfo == nil) then if (Msg ~= nil) then - print("\tCannot load Info.lua: " .. Msg); + print("\t" .. Msg); end return; end @@ -343,19 +424,64 @@ end -print("Processing plugin subfolders:"); -for fnam in lfs.dir(".") do - if ((fnam ~= ".") and (fnam ~= "..")) then - local Attributes = lfs.attributes(fnam); - if (Attributes ~= nil) then - if (Attributes.mode == "directory") then - print(fnam); - ProcessPluginFolder(fnam); - end +--- Tries to load LFS through LuaRocks, returns the LFS instance, or nil on error +local function LoadLFS() + -- Try to load lfs, do not abort if not found ... + local lfs, err = pcall( + function() + return require("lfs") end + ); + + -- ... rather, print a nice message with instructions: + if not(lfs) then + print([[ + Cannot load LuaFileSystem + Install it through luarocks by executing the following command: + luarocks install luafilesystem (Windows) + sudo luarocks install luafilesystem (*nix) + + If you don't have luarocks installed, you need to install them using your OS's package manager, usually: + sudo apt-get install luarocks (Ubuntu / Debian) + On windows, a binary distribution can be downloaded from the LuaRocks homepage, http://luarocks.org/en/Download + ]]); + + print("Original error text: ", err); + return nil; end + + -- We now know that LFS is present, get it normally: + return require("lfs"); end + +local Arg1 = ...; +if ((Arg1 ~= nil) and (Arg1 ~= "")) then + -- Called with a plugin folder name, export only that one + ProcessPluginFolder(Arg1) +else + -- Called without any arguments, process all subfolders: + local lfs = LoadLFS(); + if (lfs == nil) then + -- LFS not loaded, error has already been printed, just bail out + return; + end + print("Processing plugin subfolders:"); + for fnam in lfs.dir(".") do + if ((fnam ~= ".") and (fnam ~= "..")) then + local Attributes = lfs.attributes(fnam); + if (Attributes ~= nil) then + if (Attributes.mode == "directory") then + print(fnam); + ProcessPluginFolder(fnam); + end + end + end + end +end +print("Done."); + + diff --git a/src/Bindings/LuaState.cpp b/src/Bindings/LuaState.cpp index bfee1d037..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; } @@ -1025,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) { - 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"; + // 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; } - return Printf("Unknown (%d)", Type); + + // 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) + { + a_String.assign(s, len); + } +} + + + + + +void cLuaState::LogStack(const char * a_Header) +{ + LogStack(m_LuaState, a_Header); +} + + + + + +void cLuaState::LogStack(lua_State * a_LuaState, const char * a_Header) +{ + LOGD((a_Header != NULL) ? a_Header : "Lua C API Stack contents:"); + for (int i = lua_gettop(a_LuaState); i >= 0; i--) + { + AString Value; + int Type = lua_type(a_LuaState, i); + switch (Type) + { + case LUA_TBOOLEAN: Value.assign((lua_toboolean(a_LuaState, i) != 0) ? "true" : "false"); break; + case LUA_TLIGHTUSERDATA: Printf(Value, "%p", lua_touserdata(a_LuaState, i)); break; + case LUA_TNUMBER: Printf(Value, "%f", (double)lua_tonumber(a_LuaState, i)); break; + case LUA_TSTRING: Printf(Value, "%s", lua_tostring(a_LuaState, i)); break; + default: break; + } + LOGD(" Idx %d: type %d (%s) %s", i, Type, lua_typename(a_LuaState, Type), Value.c_str()); + } // for i - stack idx } diff --git a/src/Bindings/LuaState.h b/src/Bindings/LuaState.h index f8b67f5cd..dda45bb28 100644 --- a/src/Bindings/LuaState.h +++ b/src/Bindings/LuaState.h @@ -60,23 +60,23 @@ class cBlockEntity; -/// Encapsulates a Lua state and provides some syntactic sugar for common operations +/** Encapsulates a Lua state and provides some syntactic sugar for common operations */ class cLuaState { public: - /// Used for storing references to object in the global registry + /** Used for storing references to object in the global registry */ class cRef { public: - /// Creates a reference in the specified LuaState for object at the specified StackPos + /** Creates a reference in the specified LuaState for object at the specified StackPos */ cRef(cLuaState & a_LuaState, int a_StackPos); ~cRef(); - /// Returns true if the reference is valid + /** Returns true if the reference is valid */ bool IsValid(void) const {return (m_Ref != LUA_REFNIL); } - /// Allows to use this class wherever an int (i. e. ref) is to be used + /** Allows to use this class wherever an int (i. e. ref) is to be used */ operator int(void) const { return m_Ref; } protected: @@ -102,7 +102,7 @@ public: } ; - /// A dummy class that's used only to delimit function args from return values for cLuaState::Call() + /** A dummy class that's used only to delimit function args from return values for cLuaState::Call() */ class cRet { } ; @@ -123,22 +123,22 @@ public: ~cLuaState(); - /// Allows this object to be used in the same way as a lua_State *, for example in the LuaLib functions + /** Allows this object to be used in the same way as a lua_State *, for example in the LuaLib functions */ operator lua_State * (void) { return m_LuaState; } - /// Creates the m_LuaState, if not closed already. This state will be automatically closed in the destructor + /** Creates the m_LuaState, if not closed already. This state will be automatically closed in the destructor */ void Create(void); - /// Closes the m_LuaState, if not closed already + /** Closes the m_LuaState, if not closed already */ void Close(void); - /// Attaches the specified state. Operations will be carried out on this state, but it will not be closed in the destructor + /** Attaches the specified state. Operations will be carried out on this state, but it will not be closed in the destructor */ void Attach(lua_State * a_State); - /// Detaches a previously attached state. + /** Detaches a previously attached state. */ void Detach(void); - /// Returns true if the m_LuaState is valid + /** Returns true if the m_LuaState is valid */ bool IsValid(void) const { return (m_LuaState != NULL); } /** Loads the specified file @@ -147,7 +147,7 @@ public: */ bool LoadFile(const AString & a_FileName); - /// Returns true if a_FunctionName is a valid Lua function that can be called + /** Returns true if a_FunctionName is a valid Lua function that can be called */ bool HasFunction(const char * a_FunctionName); // Push a value onto the stack @@ -182,7 +182,7 @@ public: void Push(cHopperEntity * a_Hopper); void Push(cBlockEntity * a_BlockEntity); - /// Call any 0-param 0-return Lua function in a single line: + /** Call any 0-param 0-return Lua function in a single line: */ template <typename FnT> bool Call(FnT a_FnName) { @@ -193,7 +193,7 @@ public: return CallFunction(0); } - /// Call any 1-param 0-return Lua function in a single line: + /** Call any 1-param 0-return Lua function in a single line: */ template< typename FnT, typename ArgT1 @@ -208,7 +208,7 @@ public: return CallFunction(0); } - /// Call any 2-param 0-return Lua function in a single line: + /** Call any 2-param 0-return Lua function in a single line: */ template< typename FnT, typename ArgT1, typename ArgT2 > @@ -223,7 +223,7 @@ public: return CallFunction(0); } - /// Call any 3-param 0-return Lua function in a single line: + /** Call any 3-param 0-return Lua function in a single line: */ template< typename FnT, typename ArgT1, typename ArgT2, typename ArgT3 > @@ -239,7 +239,7 @@ public: return CallFunction(0); } - /// Call any 0-param 1-return Lua function in a single line: + /** Call any 0-param 1-return Lua function in a single line: */ template< typename FnT, typename RetT1 > @@ -259,12 +259,13 @@ public: return true; } - /// Call any 1-param 1-return Lua function in a single line: + /** Call any 1-param 1-return Lua function in a single line: */ template< typename FnT, typename ArgT1, typename RetT1 > bool Call(FnT a_FnName, ArgT1 a_Arg1, const cRet & a_Mark, RetT1 & a_Ret1) { + int InitialTop = lua_gettop(m_LuaState); UNUSED(a_Mark); if (!PushFunction(a_FnName)) { @@ -277,10 +278,11 @@ public: } GetReturn(-1, a_Ret1); lua_pop(m_LuaState, 1); + ASSERT(InitialTop == lua_gettop(m_LuaState)); return true; } - /// Call any 2-param 1-return Lua function in a single line: + /** Call any 2-param 1-return Lua function in a single line: */ template< typename FnT, typename ArgT1, typename ArgT2, typename RetT1 > @@ -302,7 +304,7 @@ public: return true; } - /// Call any 3-param 1-return Lua function in a single line: + /** Call any 3-param 1-return Lua function in a single line: */ template< typename FnT, typename ArgT1, typename ArgT2, typename ArgT3, typename RetT1 > @@ -325,7 +327,7 @@ public: return true; } - /// Call any 4-param 1-return Lua function in a single line: + /** Call any 4-param 1-return Lua function in a single line: */ template< typename FnT, typename ArgT1, typename ArgT2, typename ArgT3, typename ArgT4, typename RetT1 > @@ -349,7 +351,7 @@ public: return true; } - /// Call any 5-param 1-return Lua function in a single line: + /** Call any 5-param 1-return Lua function in a single line: */ template< typename FnT, typename ArgT1, typename ArgT2, typename ArgT3, typename ArgT4, typename ArgT5, typename RetT1 > @@ -374,7 +376,7 @@ public: return true; } - /// Call any 6-param 1-return Lua function in a single line: + /** Call any 6-param 1-return Lua function in a single line: */ template< typename FnT, typename ArgT1, typename ArgT2, typename ArgT3, typename ArgT4, typename ArgT5, typename ArgT6, typename RetT1 @@ -401,7 +403,7 @@ public: return true; } - /// Call any 7-param 1-return Lua function in a single line: + /** Call any 7-param 1-return Lua function in a single line: */ template< typename FnT, typename ArgT1, typename ArgT2, typename ArgT3, typename ArgT4, typename ArgT5, typename ArgT6, typename ArgT7, typename RetT1 @@ -429,7 +431,7 @@ public: return true; } - /// Call any 8-param 1-return Lua function in a single line: + /** Call any 8-param 1-return Lua function in a single line: */ template< typename FnT, typename ArgT1, typename ArgT2, typename ArgT3, typename ArgT4, typename ArgT5, typename ArgT6, typename ArgT7, typename ArgT8, typename RetT1 @@ -458,7 +460,7 @@ public: return true; } - /// Call any 9-param 1-return Lua function in a single line: + /** Call any 9-param 1-return Lua function in a single line: */ template< typename FnT, typename ArgT1, typename ArgT2, typename ArgT3, typename ArgT4, typename ArgT5, typename ArgT6, typename ArgT7, typename ArgT8, typename ArgT9, typename RetT1 @@ -488,7 +490,7 @@ public: return true; } - /// Call any 10-param 1-return Lua function in a single line: + /** Call any 10-param 1-return Lua function in a single line: */ template< typename FnT, typename ArgT1, typename ArgT2, typename ArgT3, typename ArgT4, typename ArgT5, typename ArgT6, typename ArgT7, typename ArgT8, typename ArgT9, typename ArgT10, typename RetT1 @@ -519,7 +521,7 @@ public: return true; } - /// Call any 1-param 2-return Lua function in a single line: + /** Call any 1-param 2-return Lua function in a single line: */ template< typename FnT, typename ArgT1, typename RetT1, typename RetT2 > @@ -541,7 +543,7 @@ public: return true; } - /// Call any 2-param 2-return Lua function in a single line: + /** Call any 2-param 2-return Lua function in a single line: */ template< typename FnT, typename ArgT1, typename ArgT2, typename RetT1, typename RetT2 > @@ -564,7 +566,7 @@ public: return true; } - /// Call any 3-param 2-return Lua function in a single line: + /** Call any 3-param 2-return Lua function in a single line: */ template< typename FnT, typename ArgT1, typename ArgT2, typename ArgT3, typename RetT1, typename RetT2 @@ -589,7 +591,7 @@ public: return true; } - /// Call any 4-param 2-return Lua function in a single line: + /** Call any 4-param 2-return Lua function in a single line: */ template< typename FnT, typename ArgT1, typename ArgT2, typename ArgT3, typename ArgT4, typename RetT1, typename RetT2 @@ -615,7 +617,7 @@ public: return true; } - /// Call any 5-param 2-return Lua function in a single line: + /** Call any 5-param 2-return Lua function in a single line: */ template< typename FnT, typename ArgT1, typename ArgT2, typename ArgT3, typename ArgT4, typename ArgT5, typename RetT1, typename RetT2 @@ -642,7 +644,7 @@ public: return true; } - /// Call any 6-param 2-return Lua function in a single line: + /** Call any 6-param 2-return Lua function in a single line: */ template< typename FnT, typename ArgT1, typename ArgT2, typename ArgT3, typename ArgT4, typename ArgT5, typename ArgT6, @@ -671,7 +673,7 @@ public: return true; } - /// Call any 7-param 2-return Lua function in a single line: + /** Call any 7-param 2-return Lua function in a single line: */ template< typename FnT, typename ArgT1, typename ArgT2, typename ArgT3, typename ArgT4, typename ArgT5, typename ArgT6, typename ArgT7, @@ -701,7 +703,7 @@ public: return true; } - /// Call any 7-param 3-return Lua function in a single line: + /** Call any 7-param 3-return Lua function in a single line: */ template< typename FnT, typename ArgT1, typename ArgT2, typename ArgT3, typename ArgT4, typename ArgT5, typename ArgT6, typename ArgT7, @@ -732,7 +734,7 @@ public: return true; } - /// Call any 8-param 3-return Lua function in a single line: + /** Call any 8-param 3-return Lua function in a single line: */ template< typename FnT, typename ArgT1, typename ArgT2, typename ArgT3, typename ArgT4, typename ArgT5, typename ArgT6, typename ArgT7, typename ArgT8, @@ -764,7 +766,7 @@ public: return true; } - /// Call any 9-param 5-return Lua function in a single line: + /** Call any 9-param 5-return Lua function in a single line: */ template< typename FnT, typename ArgT1, typename ArgT2, typename ArgT3, typename ArgT4, typename ArgT5, typename ArgT6, typename ArgT7, typename ArgT8, typename ArgT9, @@ -800,46 +802,71 @@ public: } - /// Returns true if the specified parameters on the stack are of the specified usertable type; also logs warning if not. Used for static functions + /** Returns true if the specified parameters on the stack are of the specified usertable type; also logs warning if not. Used for static functions */ bool CheckParamUserTable(int a_StartParam, const char * a_UserTable, int a_EndParam = -1); - /// Returns true if the specified parameters on the stack are of the specified usertype; also logs warning if not. Used for regular functions + /** Returns true if the specified parameters on the stack are of the specified usertype; also logs warning if not. Used for regular functions */ bool CheckParamUserType(int a_StartParam, const char * a_UserType, int a_EndParam = -1); - /// Returns true if the specified parameters on the stack are tables; also logs warning if not + /** Returns true if the specified parameters on the stack are tables; also logs warning if not */ bool CheckParamTable(int a_StartParam, int a_EndParam = -1); - /// Returns true if the specified parameters on the stack are numbers; also logs warning if not + /** Returns true if the specified parameters on the stack are numbers; also logs warning if not */ bool CheckParamNumber(int a_StartParam, int a_EndParam = -1); - /// Returns true if the specified parameters on the stack are strings; also logs warning if not + /** Returns true if the specified parameters on the stack are strings; also logs warning if not */ bool CheckParamString(int a_StartParam, int a_EndParam = -1); - /// Returns true if the specified parameters on the stack are functions; also logs warning if not + /** Returns true if the specified parameters on the stack are functions; also logs warning if not */ bool CheckParamFunction(int a_StartParam, int a_EndParam = -1); - /// Returns true if the specified parameter on the stack is nil (indicating an end-of-parameters) + /** Returns true if the specified parameter on the stack is nil (indicating an end-of-parameters) */ bool CheckParamEnd(int a_Param); - /// If the status is nonzero, prints the text on the top of Lua stack and returns true + /** If the status is nonzero, prints the text on the top of Lua stack and returns true */ bool ReportErrors(int status); - /// If the status is nonzero, prints the text on the top of Lua stack and returns true + /** If the status is nonzero, prints the text on the top of Lua stack and returns true */ static bool ReportErrors(lua_State * a_LuaState, int status); - /// Logs all items in the current stack trace to the server console + /** Logs all items in the current stack trace to the server console */ void LogStackTrace(void); - /// Logs all items in the current stack trace to the server console + /** Logs all items in the current stack trace to the server console */ static void LogStackTrace(lua_State * a_LuaState); - /// Returns the type of the item on the specified position in the stack + /** Returns the type of the item on the specified position in the stack */ AString GetTypeText(int a_StackPos); + /** Calls the function specified by its name, with arguments copied off the foreign state. + If successful, keeps the return values on the stack and returns their number. + If unsuccessful, returns a negative number and keeps the stack position unchanged. */ + int CallFunctionWithForeignParams( + const AString & a_FunctionName, + cLuaState & a_SrcLuaState, + int a_SrcParamStart, + int a_SrcParamEnd + ); + + /** Copies objects on the stack from the specified state. + Only numbers, bools, strings and userdatas are copied. + If successful, returns the number of objects copied. + If failed, returns a negative number and rewinds the stack position. */ + int CopyStackFrom(cLuaState & a_SrcLuaState, int a_SrcStart, int a_SrcEnd); + + /** Reads the value at the specified stack position as a string and sets it to a_String. */ + void ToString(int a_StackPos, AString & a_String); + + /** Logs all the elements' types on the API stack, with an optional header for the listing. */ + void LogStack(const char * a_Header); + + /** Logs all the elements' types on the API stack, with an optional header for the listing. */ + static void LogStack(lua_State * a_LuaState, const char * a_Header = NULL); + protected: lua_State * m_LuaState; - /// If true, the state is owned by this object and will be auto-Closed. False => attached state + /** If true, the state is owned by this object and will be auto-Closed. False => attached state */ bool m_IsOwned; /** The subsystem name is used for reporting errors to the console, it is either "plugin %s" or "LuaScript" @@ -847,10 +874,10 @@ protected: */ AString m_SubsystemName; - /// Name of the currently pushed function (for the Push / Call chain) + /** Name of the currently pushed function (for the Push / Call chain) */ AString m_CurrentFunctionName; - /// Number of arguments currently pushed (for the Push / Call chain) + /** Number of arguments currently pushed (for the Push / Call chain) */ int m_NumCurrentFunctionArgs; @@ -869,19 +896,19 @@ protected: */ bool PushFunction(const cTableRef & a_TableRef); - /// Pushes a usertype of the specified class type onto the stack + /** Pushes a usertype of the specified class type onto the stack */ void PushUserType(void * a_Object, const char * a_Type); - /// Retrieve value returned at a_StackPos, if it is a valid bool. If not, a_ReturnedVal is unchanged + /** Retrieve value returned at a_StackPos, if it is a valid bool. If not, a_ReturnedVal is unchanged */ void GetReturn(int a_StackPos, bool & a_ReturnedVal); - /// Retrieve value returned at a_StackPos, if it is a valid string. If not, a_ReturnedVal is unchanged + /** Retrieve value returned at a_StackPos, if it is a valid string. If not, a_ReturnedVal is unchanged */ void GetReturn(int a_StackPos, AString & a_ReturnedVal); - /// Retrieve value returned at a_StackPos, if it is a valid number. If not, a_ReturnedVal is unchanged + /** Retrieve value returned at a_StackPos, if it is a valid number. If not, a_ReturnedVal is unchanged */ void GetReturn(int a_StackPos, int & a_ReturnedVal); - /// Retrieve value returned at a_StackPos, if it is a valid number. If not, a_ReturnedVal is unchanged + /** Retrieve value returned at a_StackPos, if it is a valid number. If not, a_ReturnedVal is unchanged */ void GetReturn(int a_StackPos, double & a_ReturnedVal); /** diff --git a/src/Bindings/ManualBindings.cpp b/src/Bindings/ManualBindings.cpp index 2206dd371..f1160f941 100644 --- a/src/Bindings/ManualBindings.cpp +++ b/src/Bindings/ManualBindings.cpp @@ -1540,6 +1540,85 @@ static int tolua_cPluginManager_BindConsoleCommand(lua_State * L) +static int tolua_cPluginManager_CallPlugin(lua_State * tolua_S) +{ + /* + Function signature: + cPluginManager:CallPlugin("PluginName", "FunctionName", args...) + */ + + // Check the parameters: + cLuaState L(tolua_S); + if ( + !L.CheckParamUserTable(1, "cPluginManager") || + !L.CheckParamString(2, 3)) + { + return 0; + } + + // Retrieve the plugin name and function name + AString PluginName, FunctionName; + L.ToString(2, PluginName); + L.ToString(3, FunctionName); + if (PluginName.empty() || FunctionName.empty()) + { + LOGWARNING("cPluginManager:CallPlugin(): Invalid plugin name or function name"); + L.LogStackTrace(); + return 0; + } + + // If requesting calling the current plugin, refuse: + cPluginLua * ThisPlugin = GetLuaPlugin(L); + if (ThisPlugin == NULL) + { + return 0; + } + if (ThisPlugin->GetName() == PluginName) + { + LOGWARNING("cPluginManager::CallPlugin(): Calling self is not implemented (why would it?)"); + L.LogStackTrace(); + return 0; + } + + // Call the destination plugin using a plugin callback: + class cCallback : + public cPluginManager::cPluginCallback + { + public: + int m_NumReturns; + + cCallback(const AString & a_FunctionName, cLuaState & a_SrcLuaState) : + m_FunctionName(a_FunctionName), + m_SrcLuaState(a_SrcLuaState), + m_NumReturns(0) + { + } + protected: + const AString & m_FunctionName; + cLuaState & m_SrcLuaState; + + virtual bool Item(cPlugin * a_Plugin) override + { + m_NumReturns = ((cPluginLua *)a_Plugin)->CallFunctionFromForeignState( + m_FunctionName, m_SrcLuaState, 4, lua_gettop(m_SrcLuaState) + ); + return true; + } + } Callback(FunctionName, L); + if (!cPluginManager::Get()->DoWithPlugin(PluginName, Callback)) + { + // TODO 2014_01_20 _X: This might be too much logging, plugins cannot know if other plugins are loaded (async) + LOGWARNING("cPluginManager::CallPlugin: No such plugin name (\"%s\")", PluginName.c_str()); + L.LogStackTrace(); + return 0; + } + return Callback.m_NumReturns; +} + + + + + static int tolua_cPlayer_GetGroups(lua_State* tolua_S) { cPlayer* self = (cPlayer*) tolua_tousertype(tolua_S,1,0); @@ -1734,112 +1813,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; } @@ -2305,6 +2300,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..92c06487c 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()) + { + 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/ClientHandle.cpp b/src/ClientHandle.cpp index 74d192129..0a2d3c1be 100644 --- a/src/ClientHandle.cpp +++ b/src/ClientHandle.cpp @@ -263,6 +263,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); } @@ -2111,6 +2117,33 @@ void cClientHandle::SendExperienceOrb(const cExpOrb & a_ExpOrb) +void cClientHandle::SendScoreboardObjective(const AString & a_Name, const AString & a_DisplayName, Byte a_Mode) +{ + m_Protocol->SendScoreboardObjective(a_Name, a_DisplayName, a_Mode); +} + + + + + +void cClientHandle::SendScoreUpdate(const AString & a_Objective, const AString & a_Player, cObjective::Score a_Score, Byte a_Mode) +{ + m_Protocol->SendScoreUpdate(a_Objective, a_Player, a_Score, a_Mode); +} + + + + + +void cClientHandle::SendDisplayObjective(const AString & a_Objective, cScoreboard::eDisplaySlot a_Display) +{ + m_Protocol->SendDisplayObjective(a_Objective, a_Display); +} + + + + + void cClientHandle::SendSoundEffect(const AString & a_SoundName, int a_SrcX, int a_SrcY, int a_SrcZ, float a_Volume, float a_Pitch) { m_Protocol->SendSoundEffect(a_SoundName, a_SrcX, a_SrcY, a_SrcZ, a_Volume, a_Pitch); diff --git a/src/ClientHandle.h b/src/ClientHandle.h index 4add022a6..e1f326543 100644 --- a/src/ClientHandle.h +++ b/src/ClientHandle.h @@ -16,6 +16,7 @@ #include "OSSupport/SocketThreads.h" #include "ChunkDef.h" #include "ByteBuffer.h" +#include "Scoreboard.h" @@ -125,6 +126,9 @@ public: void SendRespawn (void); void SendExperience (void); void SendExperienceOrb (const cExpOrb & a_ExpOrb); + void SendScoreboardObjective (const AString & a_Name, const AString & a_DisplayName, Byte a_Mode); + void SendScoreUpdate (const AString & a_Objective, const AString & a_Player, cObjective::Score a_Score, Byte a_Mode); + void SendDisplayObjective (const AString & a_Objective, cScoreboard::eDisplaySlot a_Display); void SendSoundEffect (const AString & a_SoundName, int a_SrcX, int a_SrcY, int a_SrcZ, float a_Volume, float a_Pitch); // a_Src coords are Block * 8 void SendSoundParticleEffect (int a_EffectID, int a_SrcX, int a_SrcY, int a_SrcZ, int a_Data); void SendSpawnFallingBlock (const cFallingBlock & a_FallingBlock); diff --git a/src/Entities/Player.cpp b/src/Entities/Player.cpp index c1f2456eb..e5def0156 100644 --- a/src/Entities/Player.cpp +++ b/src/Entities/Player.cpp @@ -74,6 +74,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(), @@ -790,6 +791,20 @@ void cPlayer::DoTakeDamage(TakeDamageInfo & a_TDI) 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 +851,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 +950,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) diff --git a/src/Entities/Player.h b/src/Entities/Player.h index bf3ca08e8..52ba2065c 100644 --- a/src/Entities/Player.h +++ b/src/Entities/Player.h @@ -13,6 +13,7 @@ class cGroup; class cWindow; class cClientHandle; +class cTeam; @@ -153,6 +154,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); @@ -456,6 +466,8 @@ protected: int m_FloaterID; + cTeam* m_Team; + void ResolvePermissions(void); @@ -463,7 +475,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 diff --git a/src/Protocol/Protocol.h b/src/Protocol/Protocol.h index 1bc5d528e..791082537 100644 --- a/src/Protocol/Protocol.h +++ b/src/Protocol/Protocol.h @@ -12,6 +12,7 @@ #include "../Defines.h" #include "../Endianness.h" +#include "../Scoreboard.h" @@ -92,6 +93,9 @@ public: virtual void SendRespawn (void) = 0; virtual void SendExperience (void) = 0; virtual void SendExperienceOrb (const cExpOrb & a_ExpOrb) = 0; + virtual void SendScoreboardObjective (const AString & a_Name, const AString & a_DisplayName, Byte a_Mode) = 0; + virtual void SendScoreUpdate (const AString & a_Objective, const AString & a_Player, cObjective::Score a_Score, Byte a_Mode) = 0; + virtual void SendDisplayObjective (const AString & a_Objective, cScoreboard::eDisplaySlot a_Display) = 0; virtual void SendSoundEffect (const AString & a_SoundName, int a_SrcX, int a_SrcY, int a_SrcZ, float a_Volume, float a_Pitch) = 0; // a_Src coords are Block * 8 virtual void SendSoundParticleEffect (int a_EffectID, int a_SrcX, int a_SrcY, int a_SrcZ, int a_Data) = 0; virtual void SendSpawnFallingBlock (const cFallingBlock & a_FallingBlock) = 0; diff --git a/src/Protocol/Protocol125.h b/src/Protocol/Protocol125.h index 310f9dd78..cd15ab518 100644 --- a/src/Protocol/Protocol125.h +++ b/src/Protocol/Protocol125.h @@ -68,6 +68,9 @@ public: virtual void SendRespawn (void) override; virtual void SendExperience (void) override; virtual void SendExperienceOrb (const cExpOrb & a_ExpOrb) override; + virtual void SendScoreboardObjective (const AString & a_Name, const AString & a_DisplayName, Byte a_Mode) override {} // This protocol doesn't support such message + virtual void SendScoreUpdate (const AString & a_Objective, const AString & a_Player, cObjective::Score a_Score, Byte a_Mode) override {} // This protocol doesn't support such message + virtual void SendDisplayObjective (const AString & a_Objective, cScoreboard::eDisplaySlot a_Display) override {} // This protocol doesn't support such message virtual void SendSoundEffect (const AString & a_SoundName, int a_SrcX, int a_SrcY, int a_SrcZ, float a_Volume, float a_Pitch) override; // a_Src coords are Block * 8 virtual void SendSoundParticleEffect (int a_EffectID, int a_SrcX, int a_SrcY, int a_SrcZ, int a_Data) override; virtual void SendSpawnFallingBlock (const cFallingBlock & a_FallingBlock) override; @@ -83,8 +86,8 @@ public: virtual void SendUpdateSign (int a_BlockX, int a_BlockY, int a_BlockZ, const AString & a_Line1, const AString & a_Line2, const AString & a_Line3, const AString & a_Line4) override; virtual void SendUseBed (const cEntity & a_Entity, int a_BlockX, int a_BlockY, int a_BlockZ ) override; virtual void SendWeather (eWeather a_Weather) override; - virtual void SendWholeInventory (const cWindow & a_Window) override; - virtual void SendWindowClose (const cWindow & a_Window) override; + virtual void SendWholeInventory (const cWindow & a_Window) override; + virtual void SendWindowClose (const cWindow & a_Window) override; virtual void SendWindowOpen (const cWindow & a_Window) override; virtual void SendWindowProperty (const cWindow & a_Window, short a_Property, short a_Value) override; diff --git a/src/Protocol/Protocol15x.cpp b/src/Protocol/Protocol15x.cpp index 0f1e59f10..c33aec7d5 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 = 0x3B, + PACKET_SCORE_UPDATE = 0x3C, + PACKET_DISPLAY_OBJECTIVE = 0x3D } ; @@ -97,6 +100,53 @@ void cProtocol150::SendParticleEffect(const AString & a_ParticleName, float a_Sr +void cProtocol150::SendScoreboardObjective(const AString & a_Name, const AString & a_DisplayName, Byte a_Mode) +{ + cCSLock Lock(m_CSPacket); + WriteByte(PACKET_SCOREBOARD_OBJECTIVE); + WriteString(a_Name); + WriteString(a_DisplayName); + WriteByte(a_Mode); + Flush(); +} + + + + + +void cProtocol150::SendScoreUpdate(const AString & a_Objective, const AString & a_Player, cObjective::Score a_Score, Byte a_Mode) +{ + cCSLock Lock(m_CSPacket); + WriteByte(PACKET_SCORE_UPDATE); + WriteString(a_Player); + WriteByte(a_Mode); + + if (a_Mode != 1) + { + WriteString(a_Objective); + WriteInt((int) a_Score); + } + + Flush(); +} + + + + + +void cProtocol150::SendDisplayObjective(const AString & a_Objective, cScoreboard::eDisplaySlot a_Display) +{ + cCSLock Lock(m_CSPacket); + WriteByte(PACKET_DISPLAY_OBJECTIVE); + WriteByte((int) a_Display); + WriteString(a_Objective); + Flush(); +} + + + + + int cProtocol150::ParseWindowClick(void) { HANDLE_PACKET_READ(ReadChar, char, WindowID); diff --git a/src/Protocol/Protocol15x.h b/src/Protocol/Protocol15x.h index 0074b3a83..0d171a67c 100644 --- a/src/Protocol/Protocol15x.h +++ b/src/Protocol/Protocol15x.h @@ -30,6 +30,9 @@ public: virtual void SendWindowOpen (const cWindow & a_Window) override; virtual void SendParticleEffect (const AString & a_ParticleName, float a_SrcX, float a_SrcY, float a_SrcZ, float a_OffsetX, float a_OffsetY, float a_OffsetZ, float a_ParticleData, int a_ParticleAmmount) override; + virtual void SendScoreboardObjective (const AString & a_Name, const AString & a_DisplayName, Byte a_Mode) override; + virtual void SendScoreUpdate (const AString & a_Objective, const AString & a_Player, cObjective::Score a_Score, Byte a_Mode) override; + virtual void SendDisplayObjective (const AString & a_Objective, cScoreboard::eDisplaySlot a_Display) override; virtual int ParseWindowClick(void); } ; diff --git a/src/Protocol/Protocol17x.cpp b/src/Protocol/Protocol17x.cpp index e5a380f8a..9506332dc 100644 --- a/src/Protocol/Protocol17x.cpp +++ b/src/Protocol/Protocol17x.cpp @@ -1,4 +1,3 @@ - // Protocol17x.cpp /* @@ -124,7 +123,7 @@ void cProtocol172::SendBlockAction(int a_BlockX, int a_BlockY, int a_BlockZ, cha void cProtocol172::SendBlockBreakAnim(int a_EntityID, int a_BlockX, int a_BlockY, int a_BlockZ, char a_Stage) { cPacketizer Pkt(*this, 0x25); // Block Break Animation packet - Pkt.WriteInt(a_EntityID); + Pkt.WriteVarInt(a_EntityID); Pkt.WriteInt(a_BlockX); Pkt.WriteInt(a_BlockY); Pkt.WriteInt(a_BlockZ); @@ -707,6 +706,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 diff --git a/src/Protocol/Protocol17x.h b/src/Protocol/Protocol17x.h index 3ae774c18..3f440f313 100644 --- a/src/Protocol/Protocol17x.h +++ b/src/Protocol/Protocol17x.h @@ -92,6 +92,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; diff --git a/src/Protocol/ProtocolRecognizer.cpp b/src/Protocol/ProtocolRecognizer.cpp index 5524af136..de5dd3fb9 100644 --- a/src/Protocol/ProtocolRecognizer.cpp +++ b/src/Protocol/ProtocolRecognizer.cpp @@ -526,6 +526,36 @@ void cProtocolRecognizer::SendExperienceOrb(const cExpOrb & a_ExpOrb) +void cProtocolRecognizer::SendScoreboardObjective(const AString & a_Name, const AString & a_DisplayName, Byte a_Mode) +{ + ASSERT(m_Protocol != NULL); + m_Protocol->SendScoreboardObjective(a_Name, a_DisplayName, a_Mode); +} + + + + + +void cProtocolRecognizer::SendScoreUpdate(const AString & a_Objective, const AString & a_Player, cObjective::Score a_Score, Byte a_Mode) +{ + ASSERT(m_Protocol != NULL); + m_Protocol->SendScoreUpdate(a_Objective, a_Player, a_Score, a_Mode); +} + + + + + +void cProtocolRecognizer::SendDisplayObjective(const AString & a_Objective, cScoreboard::eDisplaySlot a_Display) +{ + ASSERT(m_Protocol != NULL); + m_Protocol->SendDisplayObjective(a_Objective, a_Display); +} + + + + + void cProtocolRecognizer::SendSoundEffect(const AString & a_SoundName, int a_SrcX, int a_SrcY, int a_SrcZ, float a_Volume, float a_Pitch) { ASSERT(m_Protocol != NULL); @@ -807,7 +837,7 @@ bool cProtocolRecognizer::TryRecognizeLengthlessProtocol(void) } switch (ch) { - case PROTO_VERSION_1_3_2: + case PROTO_VERSION_1_3_2: { m_Protocol = new cProtocol132(m_Client); return true; diff --git a/src/Protocol/ProtocolRecognizer.h b/src/Protocol/ProtocolRecognizer.h index 2dccace6e..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; diff --git a/src/Scoreboard.cpp b/src/Scoreboard.cpp new file mode 100644 index 000000000..b2edd613b --- /dev/null +++ b/src/Scoreboard.cpp @@ -0,0 +1,510 @@ + +// Scoreboard.cpp + +// Implementation of a scoreboard that keeps track of specified objectives + +#include "Globals.h" + +#include "Scoreboard.h" +#include "World.h" +#include "ClientHandle.h" + + + + + +AString cObjective::TypeToString(eType a_Type) +{ + switch (a_Type) + { + case E_TYPE_DUMMY: return "dummy"; + case E_TYPE_DEATH_COUNT: return "deathCount"; + case E_TYPE_PLAYER_KILL_COUNT: return "playerKillCount"; + case E_TYPE_TOTAL_KILL_COUNT: return "totalKillCount"; + case E_TYPE_HEALTH: return "health"; + case E_TYPE_ACHIEVEMENT: return "achievement"; + case E_TYPE_STAT: return "stat"; + case E_TYPE_STAT_ITEM_CRAFT: return "stat.craftItem"; + case E_TYPE_STAT_ITEM_USE: return "stat.useItem"; + case E_TYPE_STAT_ITEM_BREAK: return "stat.breakItem"; + case E_TYPE_STAT_BLOCK_MINE: return "stat.mineBlock"; + case E_TYPE_STAT_ENTITY_KILL: return "stat.killEntity"; + case E_TYPE_STAT_ENTITY_KILLED_BY: return "stat.entityKilledBy"; + + default: return ""; + } +} + + + + + +cObjective::eType cObjective::StringToType(const AString & a_Name) +{ + static struct { + eType m_Type; + const char * m_String; + } TypeMap [] = + { + {E_TYPE_DUMMY, "dummy"}, + {E_TYPE_DEATH_COUNT, "deathCount"}, + {E_TYPE_PLAYER_KILL_COUNT, "playerKillCount"}, + {E_TYPE_TOTAL_KILL_COUNT, "totalKillCount"}, + {E_TYPE_HEALTH, "health"}, + {E_TYPE_ACHIEVEMENT, "achievement"}, + {E_TYPE_STAT, "stat"}, + {E_TYPE_STAT_ITEM_CRAFT, "stat.craftItem"}, + {E_TYPE_STAT_ITEM_USE, "stat.useItem"}, + {E_TYPE_STAT_ITEM_BREAK, "stat.breakItem"}, + {E_TYPE_STAT_BLOCK_MINE, "stat.mineBlock"}, + {E_TYPE_STAT_ENTITY_KILL, "stat.killEntity"}, + {E_TYPE_STAT_ENTITY_KILLED_BY, "stat.entityKilledBy"} + }; + for (size_t i = 0; i < ARRAYCOUNT(TypeMap); i++) + { + if (NoCaseCompare(TypeMap[i].m_String, a_Name) == 0) + { + return TypeMap[i].m_Type; + } + } // for i - TypeMap[] + return E_TYPE_DUMMY; +} + + + + + +cObjective::cObjective(const AString & a_Name, const AString & a_DisplayName, cObjective::eType a_Type, cWorld * a_World) + : m_DisplayName(a_DisplayName) + , m_Name(a_Name) + , m_Type(a_Type) + , m_World(a_World) +{ +} + + + + + +void cObjective::Reset(void) +{ + for (cScoreMap::iterator it = m_Scores.begin(); it != m_Scores.end(); ++it) + { + m_World->BroadcastScoreUpdate(m_Name, it->first, 0, 1); + } + + m_Scores.clear(); +} + + + + + +cObjective::Score cObjective::GetScore(const AString & a_Name) const +{ + cScoreMap::const_iterator it = m_Scores.find(a_Name); + + if (it == m_Scores.end()) + { + return 0; + } + else + { + return it->second; + } +} + + + + + +void cObjective::SetScore(const AString & a_Name, cObjective::Score a_Score) +{ + m_Scores[a_Name] = a_Score; + + m_World->BroadcastScoreUpdate(m_Name, a_Name, a_Score, 0); +} + + + + + +void cObjective::ResetScore(const AString & a_Name) +{ + m_Scores.erase(a_Name); + + m_World->BroadcastScoreUpdate(m_Name, a_Name, 0, 1); +} + + + + + +cObjective::Score cObjective::AddScore(const AString & a_Name, cObjective::Score a_Delta) +{ + // TODO 2014-01-19 xdot: Potential optimization - Reuse iterator + Score NewScore = m_Scores[a_Name] + a_Delta; + + SetScore(a_Name, NewScore); + + return NewScore; +} + + + + + +cObjective::Score cObjective::SubScore(const AString & a_Name, cObjective::Score a_Delta) +{ + // TODO 2014-01-19 xdot: Potential optimization - Reuse iterator + Score NewScore = m_Scores[a_Name] - a_Delta; + + SetScore(a_Name, NewScore); + + return NewScore; +} + + + + + +void cObjective::SetDisplayName(const AString & a_Name) +{ + m_DisplayName = a_Name; + + m_World->BroadcastScoreboardObjective(m_Name, m_DisplayName, 2); +} + + + + + +void cObjective::SendTo(cClientHandle & a_Client) +{ + a_Client.SendScoreboardObjective(m_Name, m_DisplayName, 0); + + for (cScoreMap::const_iterator it = m_Scores.begin(); it != m_Scores.end(); ++it) + { + a_Client.SendScoreUpdate(m_Name, it->first, it->second, 0); + } +} + + + + + +cTeam::cTeam(const AString & a_Name, const AString & a_DisplayName, + const AString & a_Prefix, const AString & a_Suffix) + : m_AllowsFriendlyFire(true) + , m_CanSeeFriendlyInvisible(false) + , m_Name(a_Name) + , m_DisplayName(a_DisplayName) + , m_Prefix(a_Prefix) + , m_Suffix(a_Suffix) +{} + + + + + +bool cTeam::AddPlayer(const AString & a_Name) +{ + return m_Players.insert(a_Name).second; +} + + + + + +bool cTeam::RemovePlayer(const AString & a_Name) +{ + return m_Players.erase(a_Name) > 0; +} + + + + + +bool cTeam::HasPlayer(const AString & a_Name) const +{ + cPlayerNameSet::const_iterator it = m_Players.find(a_Name); + + return it != m_Players.end(); +} + + + + + +void cTeam::Reset(void) +{ + // TODO 2014-01-22 xdot: Inform online players + + m_Players.clear(); +} + + + + +unsigned int cTeam::GetNumPlayers(void) const +{ + return m_Players.size(); +} + + + + + +cScoreboard::cScoreboard(cWorld * a_World) : m_World(a_World) +{ + for (int i = 0; i < (int) E_DISPLAY_SLOT_COUNT; ++i) + { + m_Display[i] = NULL; + } +} + + + + + +cObjective* cScoreboard::RegisterObjective(const AString & a_Name, const AString & a_DisplayName, cObjective::eType a_Type) +{ + cObjective Objective(a_Name, a_DisplayName, a_Type, m_World); + + std::pair<cObjectiveMap::iterator, bool> Status = m_Objectives.insert(cNamedObjective(a_Name, Objective)); + + if (Status.second) + { + ASSERT(m_World != NULL); + m_World->BroadcastScoreboardObjective(a_Name, a_DisplayName, 0); + + return &Status.first->second; + } + else + { + return NULL; + } +} + + + + + +bool cScoreboard::RemoveObjective(const AString & a_Name) +{ + cCSLock Lock(m_CSObjectives); + + cObjectiveMap::iterator it = m_Objectives.find(a_Name); + + if (it == m_Objectives.end()) + { + return false; + } + + m_Objectives.erase(it); + + ASSERT(m_World != NULL); + m_World->BroadcastScoreboardObjective(it->second.GetName(), it->second.GetDisplayName(), 1); + + return true; +} + + + + + +cObjective * cScoreboard::GetObjective(const AString & a_Name) +{ + cCSLock Lock(m_CSObjectives); + + cObjectiveMap::iterator it = m_Objectives.find(a_Name); + + if (it == m_Objectives.end()) + { + return NULL; + } + else + { + return &it->second; + } +} + + + + + +cTeam * cScoreboard::RegisterTeam( + const AString & a_Name, const AString & a_DisplayName, + const AString & a_Prefix, const AString & a_Suffix +) +{ + cTeam Team(a_Name, a_DisplayName, a_Prefix, a_Suffix); + + std::pair<cTeamMap::iterator, bool> Status = m_Teams.insert(cNamedTeam(a_Name, Team)); + + return Status.second ? &Status.first->second : NULL; +} + + + + + +bool cScoreboard::RemoveTeam(const AString & a_Name) +{ + cCSLock Lock(m_CSTeams); + + cTeamMap::iterator it = m_Teams.find(a_Name); + + if (it == m_Teams.end()) + { + return false; + } + + m_Teams.erase(it); + + return true; +} + + + + + +cTeam * cScoreboard::GetTeam(const AString & a_Name) +{ + cCSLock Lock(m_CSTeams); + + cTeamMap::iterator it = m_Teams.find(a_Name); + + if (it == m_Teams.end()) + { + return NULL; + } + else + { + return &it->second; + } +} + + + + + +cTeam * cScoreboard::QueryPlayerTeam(const AString & a_Name) +{ + cCSLock Lock(m_CSTeams); + + for (cTeamMap::iterator it = m_Teams.begin(); it != m_Teams.end(); ++it) + { + if (it->second.HasPlayer(a_Name)) + { + return &it->second; + } + } + + return NULL; +} + + + + + +void cScoreboard::SetDisplay(const AString & a_Objective, eDisplaySlot a_Slot) +{ + ASSERT(a_Slot < E_DISPLAY_SLOT_COUNT); + + cObjective * Objective = GetObjective(a_Objective); + + SetDisplay(Objective, a_Slot); +} + + + + + +void cScoreboard::SetDisplay(cObjective * a_Objective, eDisplaySlot a_Slot) +{ + m_Display[a_Slot] = a_Objective; + + ASSERT(m_World != NULL); + m_World->BroadcastDisplayObjective(a_Objective ? a_Objective->GetName() : "", a_Slot); +} + + + + + +cObjective * cScoreboard::GetObjectiveIn(eDisplaySlot a_Slot) +{ + ASSERT(a_Slot < E_DISPLAY_SLOT_COUNT); + + return m_Display[a_Slot]; +} + + + + + +void cScoreboard::ForEachObjectiveWith(cObjective::eType a_Type, cObjectiveCallback& a_Callback) +{ + cCSLock Lock(m_CSObjectives); + + for (cObjectiveMap::iterator it = m_Objectives.begin(); it != m_Objectives.end(); ++it) + { + if (it->second.GetType() == a_Type) + { + // Call callback + if (a_Callback.Item(&it->second)) + { + return; + } + } + } +} + + + + + +void cScoreboard::SendTo(cClientHandle & a_Client) +{ + cCSLock Lock(m_CSObjectives); + + for (cObjectiveMap::iterator it = m_Objectives.begin(); it != m_Objectives.end(); ++it) + { + it->second.SendTo(a_Client); + } + + for (int i = 0; i < (int) E_DISPLAY_SLOT_COUNT; ++i) + { + // Avoid race conditions + cObjective * Objective = m_Display[i]; + + if (Objective) + { + a_Client.SendDisplayObjective(Objective->GetName(), (eDisplaySlot) i); + } + } +} + + + + + +unsigned int cScoreboard::GetNumObjectives(void) const +{ + return m_Objectives.size(); +} + + + + + +unsigned int cScoreboard::GetNumTeams(void) const +{ + return m_Teams.size(); +} + + + + + diff --git a/src/Scoreboard.h b/src/Scoreboard.h new file mode 100644 index 000000000..f64ba2bce --- /dev/null +++ b/src/Scoreboard.h @@ -0,0 +1,276 @@ + +// Scoreboard.h + +// Implementation of a scoreboard that keeps track of specified objectives + + + + + +#pragma once + + + + + +class cObjective; +class cWorld; + +typedef cItemCallback<cObjective> cObjectiveCallback; + + + + + +// tolua_begin +class cObjective +{ +public: + + typedef int Score; + + enum eType + { + E_TYPE_DUMMY, + + E_TYPE_DEATH_COUNT, + E_TYPE_PLAYER_KILL_COUNT, + E_TYPE_TOTAL_KILL_COUNT, + E_TYPE_HEALTH, + + E_TYPE_ACHIEVEMENT, + + E_TYPE_STAT, + E_TYPE_STAT_ITEM_CRAFT, + E_TYPE_STAT_ITEM_USE, + E_TYPE_STAT_ITEM_BREAK, + + E_TYPE_STAT_BLOCK_MINE, + E_TYPE_STAT_ENTITY_KILL, + E_TYPE_STAT_ENTITY_KILLED_BY + }; + + // tolua_end + + static AString TypeToString(eType a_Type); + + static eType StringToType(const AString & a_Name); + +public: + + cObjective(const AString & a_Name, const AString & a_DisplayName, eType a_Type, cWorld * a_World); + + // tolua_begin + + eType GetType(void) const { return m_Type; } + + const AString & GetName(void) const { return m_Name; } + const AString & GetDisplayName(void) const { return m_DisplayName; } + + /// Resets the objective + void Reset(void); + + /// Returns the score of the specified player + Score GetScore(const AString & a_Name) const; + + /// Sets the score of the specified player + void SetScore(const AString & a_Name, Score a_Score); + + /// Resets the score of the specified player + void ResetScore(const AString & a_Name); + + /// Adds a_Delta and returns the new score + Score AddScore(const AString & a_Name, Score a_Delta); + + /// Subtracts a_Delta and returns the new score + Score SubScore(const AString & a_Name, Score a_Delta); + + void SetDisplayName(const AString & a_Name); + + // tolua_end + + /// Send this objective to the specified client + void SendTo(cClientHandle & a_Client); + +private: + + typedef std::pair<AString, Score> cTrackedPlayer; + + typedef std::map<AString, Score> cScoreMap; + + cScoreMap m_Scores; + + AString m_DisplayName; + AString m_Name; + + eType m_Type; + + cWorld * m_World; + + friend class cScoreboardSerializer; + +}; + + + + + +// tolua_begin +class cTeam +{ +public: + + // tolua_end + + cTeam( + const AString & a_Name, const AString & a_DisplayName, + const AString & a_Prefix, const AString & a_Suffix + ); + + /// Adds a new player to the team + bool AddPlayer(const AString & a_Name); + + /// Removes a player from the team + bool RemovePlayer(const AString & a_Name); + + /// Returns whether the specified player is in this team + bool HasPlayer(const AString & a_Name) const; + + /// Removes all registered players + void Reset(void); + + // tolua_begin + + /// Returns the number of registered players + unsigned int GetNumPlayers(void) const; + + bool AllowsFriendlyFire(void) const { return m_AllowsFriendlyFire; } + bool CanSeeFriendlyInvisible(void) const { return m_CanSeeFriendlyInvisible; } + + const AString & GetDisplayName(void) const { return m_DisplayName; } + const AString & GetName(void) const { return m_DisplayName; } + + const AString & GetPrefix(void) const { return m_Prefix; } + const AString & GetSuffix(void) const { return m_Suffix; } + + void SetFriendlyFire(bool a_Flag) { m_AllowsFriendlyFire = a_Flag; } + void SetCanSeeFriendlyInvisible(bool a_Flag) { m_CanSeeFriendlyInvisible = a_Flag; } + + void SetDisplayName(const AString & a_Name); + + void SetPrefix(const AString & a_Prefix) { m_Prefix = a_Prefix; } + void SetSuffix(const AString & a_Suffix) { m_Suffix = a_Suffix; } + + // tolua_end + +private: + + typedef std::set<AString> cPlayerNameSet; + + bool m_AllowsFriendlyFire; + bool m_CanSeeFriendlyInvisible; + + AString m_DisplayName; + AString m_Name; + + AString m_Prefix; + AString m_Suffix; + + cPlayerNameSet m_Players; + + friend class cScoreboardSerializer; + +}; + + + + + +// tolua_begin +class cScoreboard +{ +public: + + enum eDisplaySlot + { + E_DISPLAY_SLOT_LIST = 0, + E_DISPLAY_SLOT_SIDEBAR, + E_DISPLAY_SLOT_NAME, + + E_DISPLAY_SLOT_COUNT + }; + + // tolua_end + + +public: + + cScoreboard(cWorld * a_World); + + // tolua_begin + + /// Registers a new scoreboard objective, returns the cObjective instance, NULL on name collision + cObjective * RegisterObjective(const AString & a_Name, const AString & a_DisplayName, cObjective::eType a_Type); + + /// Removes a registered objective, returns true if operation was successful + bool RemoveObjective(const AString & a_Name); + + /// Retrieves the objective with the specified name, NULL if not found + cObjective * GetObjective(const AString & a_Name); + + /// Registers a new team, returns the cTeam instance, NULL on name collision + cTeam * RegisterTeam(const AString & a_Name, const AString & a_DisplayName, const AString & a_Prefix, const AString & a_Suffix); + + /// Removes a registered team, returns true if operation was successful + bool RemoveTeam(const AString & a_Name); + + /// Retrieves the team with the specified name, NULL if not found + cTeam * GetTeam(const AString & a_Name); + + cTeam * QueryPlayerTeam(const AString & a_Name); // WARNING: O(n logn) + + void SetDisplay(const AString & a_Objective, eDisplaySlot a_Slot); + + void SetDisplay(cObjective * a_Objective, eDisplaySlot a_Slot); + + cObjective * GetObjectiveIn(eDisplaySlot a_Slot); + + /// Execute callback for each objective with the specified type + void ForEachObjectiveWith(cObjective::eType a_Type, cObjectiveCallback& a_Callback); + + unsigned int GetNumObjectives(void) const; + + unsigned int GetNumTeams(void) const; + + // tolua_end + + /// Send this scoreboard to the specified client + void SendTo(cClientHandle & a_Client); + + +private: + + typedef std::pair<AString, cObjective> cNamedObjective; + typedef std::pair<AString, cTeam> cNamedTeam; + + typedef std::map<AString, cObjective> cObjectiveMap; + typedef std::map<AString, cTeam> cTeamMap; + + // TODO 2014-01-19 xdot: Potential optimization - Sort objectives based on type + cCriticalSection m_CSObjectives; + cObjectiveMap m_Objectives; + + cCriticalSection m_CSTeams; + cTeamMap m_Teams; + + cWorld * m_World; + + cObjective* m_Display[E_DISPLAY_SLOT_COUNT]; + + friend class cScoreboardSerializer; + +} ; + + + + diff --git a/src/Server.cpp b/src/Server.cpp index 4d551e164..34ee066cb 100644 --- a/src/Server.cpp +++ b/src/Server.cpp @@ -492,7 +492,7 @@ void cServer::ExecuteConsoleCommand(const AString & a_Cmd, cCommandOutputCallbac } } #endif - + if (cPluginManager::Get()->ExecuteConsoleCommand(split, a_Output)) { a_Output.Finished(); diff --git a/src/World.cpp b/src/World.cpp index 5e9f4a38a..49d42f08e 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" @@ -242,11 +243,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 +272,10 @@ cWorld::~cWorld() m_Storage.WaitForFinish(); + // Unload the scoreboard + cScoreboardSerializer Serializer(m_WorldName, &m_Scoreboard); + Serializer.Save(); + delete m_ChunkMap; } @@ -1981,6 +1991,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); diff --git a/src/World.h b/src/World.h index f0dd45c90..d941772aa 100644 --- a/src/World.h +++ b/src/World.h @@ -23,6 +23,7 @@ #include "Mobs/Monster.h" #include "Entities/ProjectileEntity.h" #include "ForEachChunkProvider.h" +#include "Scoreboard.h" @@ -170,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); @@ -229,10 +233,10 @@ public: /** 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 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) @@ -505,6 +509,9 @@ public: /** Returns the name of the world.ini file used by this world */ const AString & GetIniFileName(void) const {return m_IniFileName; } + + /// Returns the associated scoreboard instance + cScoreboard & GetScoreBoard(void) { return m_Scoreboard; } // tolua_end @@ -773,6 +780,8 @@ private: sSetBlockList m_FastSetBlockQueue; cChunkGenerator m_Generator; + + cScoreboard m_Scoreboard; /** The callbacks that the ChunkGenerator uses to store new chunks and interface to plugins */ cChunkGeneratorCallbacks m_GeneratorCallbacks; diff --git a/src/WorldStorage/ScoreboardSerializer.cpp b/src/WorldStorage/ScoreboardSerializer.cpp new file mode 100644 index 000000000..dabc5e2e1 --- /dev/null +++ b/src/WorldStorage/ScoreboardSerializer.cpp @@ -0,0 +1,389 @@ + +// ScoreboardSerializer.cpp + + +#include "Globals.h" +#include "ScoreboardSerializer.h" +#include "../StringCompression.h" +#include "zlib/zlib.h" +#include "FastNBT.h" + +#include "../Scoreboard.h" + + + + +#define SCOREBOARD_INFLATE_MAX 16 KiB + + + + + +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) +{ + cFile File; + + if (!File.Open(FILE_IO_PREFIX + m_Path, cFile::fmReadWrite)) + { + return false; + } + + AString Data; + + File.ReadRestOfFile(Data); + + File.Close(); + + char Uncompressed[SCOREBOARD_INFLATE_MAX]; + z_stream strm; + strm.zalloc = (alloc_func)NULL; + strm.zfree = (free_func)NULL; + strm.opaque = NULL; + inflateInit(&strm); + strm.next_out = (Bytef *)Uncompressed; + strm.avail_out = sizeof(Uncompressed); + strm.next_in = (Bytef *)Data.data(); + strm.avail_in = Data.size(); + int res = inflate(&strm, Z_FINISH); + inflateEnd(&strm); + if (res != Z_STREAM_END) + { + return false; + } + + // Parse the NBT data: + cParsedNBT NBT(Uncompressed, strm.total_out); + if (!NBT.IsValid()) + { + // NBT Parsing failed + return false; + } + + return LoadScoreboardFromNBT(NBT); +} + + + + + +bool cScoreboardSerializer::Save(void) +{ + cFastNBTWriter Writer; + + Writer.BeginCompound(""); + m_ScoreBoard->RegisterObjective("test","test",cObjective::E_TYPE_DUMMY)->AddScore("dot", 2); + SaveScoreboardToNBT(Writer); + + Writer.EndCompound(); + Writer.Finish(); + + #ifdef _DEBUG + cParsedNBT TestParse(Writer.GetResult().data(), Writer.GetResult().size()); + ASSERT(TestParse.IsValid()); + #endif // _DEBUG + + gzFile gz = gzopen((FILE_IO_PREFIX + m_Path).c_str(), "wb"); + if (gz != NULL) + { + gzwrite(gz, Writer.GetResult().data(), Writer.GetResult().size()); + } + gzclose(gz); + + 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(); + + 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(); + + 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(); + a_Writer.EndCompound(); + + 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(); +} + + + + + +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); + } + + CurrLine = a_NBT.FindChildByName(Child, "SeeFriendlyInvisibles"); + if (CurrLine >= 0) + { + CanSeeFriendlyInvisible = a_NBT.GetInt(CurrLine); + } + + 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(0, "DisplaySlots"); + if (DisplaySlots < 0) + { + return false; + } + + int CurrLine = a_NBT.FindChildByName(DisplaySlots, "slot_0"); + if (CurrLine >= 0) + { + AString Name = a_NBT.GetString(CurrLine); + + m_ScoreBoard->SetDisplay(Name, cScoreboard::E_DISPLAY_SLOT_LIST); + } + + CurrLine = a_NBT.FindChildByName(DisplaySlots, "slot_1"); + if (CurrLine >= 0) + { + AString Name = a_NBT.GetString(CurrLine); + + m_ScoreBoard->SetDisplay(Name, cScoreboard::E_DISPLAY_SLOT_SIDEBAR); + } + + CurrLine = a_NBT.FindChildByName(DisplaySlots, "slot_2"); + if (CurrLine >= 0) + { + AString Name = a_NBT.GetString(CurrLine); + + m_ScoreBoard->SetDisplay(Name, cScoreboard::E_DISPLAY_SLOT_NAME); + } + + return true; +} + + + + + + + + diff --git a/src/WorldStorage/ScoreboardSerializer.h b/src/WorldStorage/ScoreboardSerializer.h new file mode 100644 index 000000000..048fa3ab4 --- /dev/null +++ b/src/WorldStorage/ScoreboardSerializer.h @@ -0,0 +1,52 @@ + +// ScoreboardSerializer.h + +// Declares the cScoreboardSerializer class that is used for saving scoreboards into NBT format used by Anvil + + + + + +#pragma once + + + + + +// fwd: +class cFastNBTWriter; +class cParsedNBT; +class cScoreboard; + + + + +class cScoreboardSerializer +{ +public: + + cScoreboardSerializer(const AString & a_WorldName, cScoreboard* a_ScoreBoard); + + /// Try to load the scoreboard + bool Load(void); + + /// Try to save the scoreboard + bool Save(void); + + +private: + + void SaveScoreboardToNBT(cFastNBTWriter & a_Writer); + + bool LoadScoreboardFromNBT(const cParsedNBT & a_NBT); + + cScoreboard* m_ScoreBoard; + + AString m_Path; + + +} ; + + + + diff --git a/src/WorldStorage/WSSAnvil.cpp b/src/WorldStorage/WSSAnvil.cpp index 8983f25ba..8be6372e2 100644 --- a/src/WorldStorage/WSSAnvil.cpp +++ b/src/WorldStorage/WSSAnvil.cpp @@ -1936,7 +1936,7 @@ bool cWSSAnvil::LoadEntityBaseFromNBT(cEntity & a_Entity, const cParsedNBT & a_N return false; } a_Entity.SetYaw(Rotation[0]); - a_Entity.SetRoll (Rotation[1]); + a_Entity.SetRoll(Rotation[1]); return true; } |