summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMattes D <github@xoft.cz>2015-04-19 19:53:14 +0200
committerMattes D <github@xoft.cz>2015-04-19 19:53:14 +0200
commitba6f5aea4c088b92365b6636d96873a82706c8c8 (patch)
treed8c6f3a3eda7c0ed1f70bc5a01b97b90d940eab4
parentClientHandle: Fixed re-sending refused right-clicks. (diff)
parentRefresh plugin list before trying to load. (diff)
downloadcuberite-ba6f5aea4c088b92365b6636d96873a82706c8c8.tar
cuberite-ba6f5aea4c088b92365b6636d96873a82706c8c8.tar.gz
cuberite-ba6f5aea4c088b92365b6636d96873a82706c8c8.tar.bz2
cuberite-ba6f5aea4c088b92365b6636d96873a82706c8c8.tar.lz
cuberite-ba6f5aea4c088b92365b6636d96873a82706c8c8.tar.xz
cuberite-ba6f5aea4c088b92365b6636d96873a82706c8c8.tar.zst
cuberite-ba6f5aea4c088b92365b6636d96873a82706c8c8.zip
-rw-r--r--MCServer/Plugins/APIDump/APIDesc.lua151
-rw-r--r--MCServer/Plugins/APIDump/Classes/Plugins.lua205
-rw-r--r--MCServer/webadmin/(original).html375
-rw-r--r--MCServer/webadmin/template.lua20
-rw-r--r--MCServer/webadmin/template_orig.lua137
-rw-r--r--src/Bindings/ManualBindings.cpp228
-rw-r--r--src/Bindings/Plugin.cpp34
-rw-r--r--src/Bindings/Plugin.h95
-rw-r--r--src/Bindings/PluginLua.cpp67
-rw-r--r--src/Bindings/PluginLua.h14
-rw-r--r--src/Bindings/PluginManager.cpp349
-rw-r--r--src/Bindings/PluginManager.h147
-rw-r--r--src/Bindings/WebPlugin.cpp110
-rw-r--r--src/Bindings/WebPlugin.h61
-rw-r--r--src/Server.cpp20
-rw-r--r--src/WebAdmin.cpp32
16 files changed, 884 insertions, 1161 deletions
diff --git a/MCServer/Plugins/APIDump/APIDesc.lua b/MCServer/Plugins/APIDump/APIDesc.lua
index 671a044e0..b4424203c 100644
--- a/MCServer/Plugins/APIDump/APIDesc.lua
+++ b/MCServer/Plugins/APIDump/APIDesc.lua
@@ -1917,157 +1917,6 @@ a_Player:OpenWindow(Window);
Inherits = "cPawn",
}, -- cPlayer
- cPlugin =
- {
- Desc = [[cPlugin describes a Lua plugin. This page is dedicated to new-style plugins and contain their functions. Each plugin has its own Plugin object.
-]],
- Functions =
- {
- 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)" },
- GetName = { Return = "string", Notes = "Returns the name of the plugin." },
- SetName = { Params = "string", Notes = "Sets the name of the Plugin." },
- GetVersion = { Return = "number", Notes = "Returns the version of the plugin." },
- SetVersion = { Params = "number", Notes = "Sets the version of the plugin." },
- GetFileName = { Return = "string" },
- CreateWebPlugin = { Notes = "{{cWebPlugin|cWebPlugin}}" },
- },
- }, -- cPlugin
-
- cPluginLua =
- {
- Desc = "",
- Functions = {},
- Inherits = "cPlugin",
- }, -- cPluginLua
-
- cPluginManager =
- {
- Desc = [[
- This class is used for generic plugin-related functionality. The plugin manager has a list of all
- plugins, can enable or disable plugins, manages hooks and in-game console commands.</p>
- <p>
- There is one instance of cPluginManager in MCServer, to get it, call either
- {{cRoot|cRoot}}:Get():GetPluginManager() or cPluginManager:Get() function.</p>
- <p>
- Note that some functions are "static", that means that they are called using a dot operator instead
- of the colon operator. For example:
-<pre class="prettyprint lang-lua">
-cPluginManager.AddHook(cPluginManager.HOOK_CHAT, OnChatMessage);
-</pre></p>
- ]],
- Functions =
- {
- AddHook =
- {
- { Params = "HookType, [HookFunction]", Return = "", Notes = "(STATIC) Informs the plugin manager that it should call the specified function when the specified hook event occurs. If a function is not specified, a default function name is looked up, based on the hook type" },
- { Params = "{{cPlugin|Plugin}}, HookType, [HookFunction]", Return = "", Notes = "(STATIC, <b>DEPRECATED</b>) Informs the plugin manager that it should call the specified function when the specified hook event occurs. If a function is not specified, a default function name is looked up, based on the hook type. NOTE: This format is deprecated and the server outputs a warning if it is used!" },
- },
- BindCommand =
- {
- { Params = "Command, Permission, Callback, HelpString", Return = "[bool]", Notes = "(STATIC) Binds an in-game command with the specified callback function, permission and help string. By common convention, providing an empty string for HelpString will hide the command from the /help display. Returns true if successful, logs to console and returns no value on error. The callback uses the following signature: <pre class=\"prettyprint lang-lua\">function(Split, {{cPlayer|Player}})</pre> The Split parameter contains an array-table of the words that the player has sent, Player is the {{cPlayer}} object representing the player who sent the command. If the callback returns true, the command is assumed to have executed successfully; in all other cases the server sends a warning to the player that the command is unknown (this is so that subcommands can be implemented)." },
- { Params = "Command, Permission, Callback, HelpString", Return = "[bool]", Notes = "Binds an in-game command with the specified callback function, permission and help string. By common convention, providing an empty string for HelpString will hide the command from the /help display. Returns true if successful, logs to console and returns no value on error. The callback uses the following signature: <pre class=\"prettyprint lang-lua\">function(Split, {{cPlayer|Player}})</pre> The Split parameter contains an array-table of the words that the player has sent, Player is the {{cPlayer}} object representing the player who sent the command. If the callback returns true, the command is assumed to have executed successfully; in all other cases the server sends a warning to the player that the command is unknown (this is so that subcommands can be implemented)." },
- },
- BindConsoleCommand =
- {
- { Params = "Command, Callback, HelpString", Return = "[bool]", 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. Returns true if successful, logs to console and returns no value on error. The callback uses the following signature: <pre class=\"prettyprint lang-lua\">function(Split)</pre> The Split parameter contains an array-table of the words that the admin has typed. If the callback returns true, the command is assumed to have executed successfully; in all other cases the server issues a warning to the console that the command is unknown (this is so that subcommands can be implemented)." },
- { Params = "Command, Callback, HelpString", Return = "[bool]", 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. Returns true if successful, logs to console and returns no value on error. The callback uses the following signature: <pre class=\"prettyprint lang-lua\">function(Split)</pre> The Split parameter contains an array-table of the words that the admin has typed. If the callback returns true, the command is assumed to have executed successfully; in all other cases the server issues a warning to the console that the command is unknown (this is so that subcommands can be implemented)." },
- },
- 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 = "{{cPluginManager#CommandResult|CommandResult}}", Notes = "Executes the command as if given by the specified Player. Checks permissions." },
- FindPlugins = { Params = "", Return = "", Notes = "Refreshes the list of plugins to include all folders inside the Plugins folder (potentially new disabled plugins)" },
- ForceExecuteCommand = { Params = "{{cPlayer|Player}}, CommandStr", Return = "{{cPluginManager#CommandResult|CommandResult}}", Notes = "Same as ExecuteCommand, but doesn't check permissions" },
- ForEachCommand = { Params = "CallbackFn", Return = "bool", Notes = "Calls the CallbackFn function for each command that has been bound using BindCommand(). The CallbackFn has the following signature: <pre class=\"prettyprint lang-lua\">function(Command, Permission, HelpString)</pre>. If the callback returns true, the enumeration is aborted and this API function returns false; if it returns false or no value, the enumeration continues with the next command, and the API function returns true." },
- ForEachConsoleCommand = { Params = "CallbackFn", Return = "bool", Notes = "Calls the CallbackFn function for each command that has been bound using BindConsoleCommand(). The CallbackFn has the following signature: <pre class=\"prettyprint lang-lua\">function (Command, HelpString)</pre>. If the callback returns true, the enumeration is aborted and this API function returns false; if it returns false or no value, the enumeration continues with the next command, and the API function returns true." },
- Get = { Params = "", Return = "cPluginManager", Notes = "(STATIC) Returns the single instance of the plugin manager" },
- GetAllPlugins = { Params = "", Return = "table", Notes = "Returns a table (dictionary) of all plugins, [name => value], where value is a valid {{cPlugin}} if the plugin is loaded, or the bool value false if the plugin is not loaded." },
- GetCommandPermission = { Params = "Command", Return = "Permission", Notes = "Returns the permission needed for executing the specified command" },
- GetCurrentPlugin = { Params = "", Return = "{{cPlugin}}", Notes = "Returns the {{cPlugin}} object for the calling plugin. This is the same object that the Initialize function receives as the argument." },
- GetNumPlugins = { Params = "", Return = "number", Notes = "Returns the number of plugins, including the disabled ones" },
- GetPlugin = { Params = "PluginName", Return = "{{cPlugin}}", Notes = "(<b>DEPRECATED, UNSAFE</b>) Returns a plugin handle of the specified plugin, or nil if such plugin is not loaded. Note thatdue to multithreading the handle is not guaranteed to be safe for use when stored - a single-plugin reload may have been triggered in the mean time for the requested plugin." },
- GetPluginsPath = { Params = "", Return = "string", Notes = "Returns the path where the individual plugin folders are located. Doesn't include the path separator at the end of the returned string." },
- IsCommandBound = { Params = "Command", Return = "bool", Notes = "Returns true if in-game Command is already bound (by any plugin)" },
- IsConsoleCommandBound = { Params = "Command", Return = "bool", Notes = "Returns true if console Command is already bound (by any plugin)" },
- LoadPlugin = { Params = "PluginFolder", Return = "", Notes = "(<b>DEPRECATED</b>) Loads a plugin from the specified folder. NOTE: Loading plugins may be an unsafe operation and may result in a deadlock or a crash. This API is deprecated and might be removed." },
- LogStackTrace = { Params = "", Return = "", Notes = "(STATIC) Logs a current stack trace of the Lua engine to the server console log. Same format as is used when the plugin fails." },
- ReloadPlugins = { Params = "", Return = "", Notes = "Reloads all active plugins" },
- },
- ConstantGroups=
- {
- CommandResult =
- {
- Include = "^cr.*",
- TextBefore = [[
- Results that the (Force)ExecuteCommand return. This gives information if the command is executed or not and the reason.
- ]],
- },
- },
- Constants =
- {
- crBlocked = { Notes = "When a plugin stopped the command using the OnExecuteCommand hook" },
- crError = { Notes = "When the command handler for the given command results in an error" },
- crExecuted = { Notes = "When the command is successfully executed." },
- crNoPermission = { Notes = "When the player doesn't have permission to execute the given command." },
- crUnknownCommand = { Notes = "When the given command doesn't exist." },
- HOOK_BLOCK_SPREAD = { Notes = "Called when a block spreads based on world conditions" },
- HOOK_BLOCK_TO_PICKUPS = { Notes = "Called when a block has been dug and is being converted to pickups. The server has provided the default pickups and the plugins may modify them." },
- HOOK_CHAT = { Notes = "Called when a client sends a chat message that is not a command. The plugin may modify the chat message" },
- HOOK_CHUNK_AVAILABLE = { Notes = "Called when a chunk is loaded or generated and becomes available in the {{cWorld|world}}." },
- HOOK_CHUNK_GENERATED = { Notes = "Called after a chunk is generated. A plugin may do last modifications on the generated chunk before it is handed of to the {{cWorld|world}}." },
- HOOK_CHUNK_GENERATING = { Notes = "Called before a chunk is generated. A plugin may override some parts of the generation algorithm." },
- HOOK_CHUNK_UNLOADED = { Notes = "Called after a chunk has been unloaded from a {{cWorld|world}}." },
- HOOK_CHUNK_UNLOADING = { Notes = "Called before a chunk is unloaded from a {{cWorld|world}}. The chunk has already been saved." },
- HOOK_COLLECTING_PICKUP = { Notes = "Called when a player is about to collect a pickup." },
- HOOK_CRAFTING_NO_RECIPE = { Notes = "Called when a player has items in the crafting slots and the server cannot locate any recipe. Plugin may provide a recipe." },
- HOOK_DISCONNECT = { Notes = "Called after the player has disconnected." },
- HOOK_EXECUTE_COMMAND = { Notes = "Called when a client sends a chat message that is recognized as a command, before handing that command to the regular command handler. A plugin may stop the command from being handled. This hook is called even when the player doesn't have permissions for the command." },
- HOOK_EXPLODED = { Notes = "Called after an explosion has been processed in a {{cWorld|world}}." },
- HOOK_EXPLODING = { Notes = "Called before an explosion is processed in a {{cWorld|world}}. A plugin may alter the explosion parameters or cancel the explosion altogether." },
- HOOK_HANDSHAKE = { Notes = "Called when a Handshake packet is received from a client." },
- HOOK_HOPPER_PULLING_ITEM = { Notes = "Called when a hopper is pulling an item from the container above it." },
- HOOK_HOPPER_PUSHING_ITEM = { Notes = "Called when a hopper is pushing an item into the container it is aimed at." },
- HOOK_KILLING = { Notes = "Called when an entity has just been killed. A plugin may resurrect the entity by setting its health to above zero." },
- HOOK_LOGIN = { Notes = "Called when a Login packet is sent to the client, before the client is queued for authentication." },
- HOOK_PLAYER_ANIMATION = { Notes = "Called when a client send the Animation packet." },
- HOOK_PLAYER_BREAKING_BLOCK = { Notes = "Called when a player is about to break a block. A plugin may cancel the event." },
- HOOK_PLAYER_BROKEN_BLOCK = { Notes = "Called after a player has broken a block." },
- HOOK_PLAYER_EATING = { Notes = "Called when the player starts eating a held item. Plugins may abort the eating." },
- HOOK_PLAYER_FISHED = { Notes = "Called when the player reels the fishing rod back in, after the server decides the player's fishing reward." },
- HOOK_PLAYER_FISHING = { Notes = "Called when the player reels the fishing rod back in, plugins may alter the fishing reward." },
- HOOK_PLAYER_JOINED = { Notes = "Called when the player entity has been created. It has not yet been fully initialized." },
- HOOK_PLAYER_LEFT_CLICK = { Notes = "Called when the client sends the LeftClick packet." },
- HOOK_PLAYER_MOVING = { Notes = "Called when the player has moved and the movement is now being applied." },
- HOOK_PLAYER_PLACED_BLOCK = { Notes = "Called when the player has just placed a block" },
- HOOK_PLAYER_PLACING_BLOCK = { Notes = "Called when the player is about to place a block. A plugin may cancel the event." },
- HOOK_PLAYER_RIGHT_CLICK = { Notes = "Called when the client sends the RightClick packet." },
- HOOK_PLAYER_RIGHT_CLICKING_ENTITY = { Notes = "Called when the client sends the UseEntity packet." },
- HOOK_PLAYER_SHOOTING = { Notes = "Called when the player releases the mouse button to fire their bow." },
- HOOK_PLAYER_SPAWNED = { Notes = "Called after the player entity has been created. The entity is fully initialized and is spawning in the {{cWorld|world}}." },
- HOOK_PLAYER_TOSSING_ITEM = { Notes = "Called when the player is tossing the held item (keypress Q)" },
- HOOK_PLAYER_USED_BLOCK = { Notes = "Called after the player has right-clicked a block" },
- HOOK_PLAYER_USED_ITEM = { Notes = "Called after the player has right-clicked with a usable item in their hand." },
- HOOK_PLAYER_USING_BLOCK = { Notes = "Called when the player is about to use (right-click) a block" },
- HOOK_PLAYER_USING_ITEM = { Notes = "Called when the player is about to right-click with a usable item in their hand." },
- HOOK_POST_CRAFTING = { Notes = "Called after a valid recipe has been chosen for the current contents of the crafting grid. Plugins may modify the recipe." },
- HOOK_PRE_CRAFTING = { Notes = "Called before a recipe is searched for the current contents of the crafting grid. Plugins may provide a recipe and cancel the built-in search." },
- HOOK_SERVER_PING = { Notes = "Called when a client pings the server from the server list. Plugins may change the favicon, server description, players online and maximum players values." },
- HOOK_SPAWNED_ENTITY = { Notes = "Called after an entity is spawned in a {{cWorld|world}}. The entity is already part of the world." },
- HOOK_SPAWNED_MONSTER = { Notes = "Called after a mob is spawned in a {{cWorld|world}}. The mob is already part of the world." },
- HOOK_SPAWNING_ENTITY = { Notes = "Called just before an entity is spawned in a {{cWorld|world}}." },
- HOOK_SPAWNING_MONSTER = { Notes = "Called just before a mob is spawned in a {{cWorld|world}}." },
- HOOK_TAKE_DAMAGE = { Notes = "Called when an entity is taking any kind of damage. Plugins may modify the damage value, effects, source or cancel the damage." },
- HOOK_TICK = { Notes = "Called when the main server thread ticks - 20 times a second." },
- HOOK_UPDATED_SIGN = { Notes = "Called after a {{cSignEntity|sign}} text has been updated, either by a player or by any external means." },
- HOOK_UPDATING_SIGN = { Notes = "Called before a {{cSignEntity|sign}} text is updated, either by a player or by any external means." },
- HOOK_WEATHER_CHANGED = { Notes = "Called after the weather has changed." },
- HOOK_WEATHER_CHANGING = { Notes = "Called just before the weather changes" },
- HOOK_WORLD_TICK = { Notes = "Called in each world's tick thread when the game logic is about to tick (20 times a second)." },
- },
- }, -- cPluginManager
-
cRankManager =
{
Desc = [[
diff --git a/MCServer/Plugins/APIDump/Classes/Plugins.lua b/MCServer/Plugins/APIDump/Classes/Plugins.lua
new file mode 100644
index 000000000..fa502ccfc
--- /dev/null
+++ b/MCServer/Plugins/APIDump/Classes/Plugins.lua
@@ -0,0 +1,205 @@
+return
+{
+ cPlugin =
+ {
+ Desc = [[cPlugin describes a Lua plugin. This page is dedicated to new-style plugins and contain their functions. Each plugin has its own Plugin object.
+]],
+ Functions =
+ {
+ GetDirectory = { Return = "string", Notes = "<b>OBSOLETE</b>, use GetFolderName() instead!" },
+ GetFolderName = { Params = "", Return = "string", Notes = "Returns the name of the folder where the plugin's files are. (APIDump)" },
+ GetLoadError = { Params = "", Return = "string", Notes = "If the plugin failed to load, returns the error message for the failure." },
+ GetLocalDirectory = { Notes = "<b>OBSOLETE</b>, use GetLocalFolder instead." },
+ GetLocalFolder = { Return = "string", Notes = "Returns the path where the plugin's files are. (Plugins/APIDump)" },
+ GetName = { Return = "string", Notes = "Returns the name of the plugin." },
+ GetStatus = { Params = "", Return = "{{cPluginManager#PluginStatus|PluginStatus}}", Notes = "Returns the status of the plugin (loaded, disabled, unloaded, error, not found)" },
+ GetVersion = { Return = "number", Notes = "Returns the version of the plugin." },
+ IsLoaded = { Params = "", Return = "", Notes = "" },
+ SetName = { Params = "string", Notes = "Sets the name of the Plugin." },
+ SetVersion = { Params = "number", Notes = "Sets the version of the plugin." },
+ },
+ }, -- cPlugin
+
+ cPluginLua =
+ {
+ Desc = "",
+ Functions =
+ {
+ AddWebTab = { Params = "", Return = "", Notes = "Adds a new webadmin tab" },
+ },
+ Inherits = "cPlugin",
+ }, -- cPluginLua
+
+ cPluginManager =
+ {
+ Desc = [[
+ This class is used for generic plugin-related functionality. The plugin manager has a list of all
+ plugins, can enable or disable plugins, manages hooks and in-game console commands.</p>
+ <p>
+ Plugins can be identified by either the PluginFolder or PluginName. Note that these two can differ,
+ refer to <a href="http://forum.mc-server.org/showthread.php?tid=1877">the forum</a> for detailed discussion.
+ <p>
+ There is one instance of cPluginManager in MCServer, to get it, call either
+ {{cRoot|cRoot}}:Get():GetPluginManager() or cPluginManager:Get() function.</p>
+ <p>
+ Note that some functions are "static", that means that they are called using a dot operator instead
+ of the colon operator. For example:
+<pre class="prettyprint lang-lua">
+cPluginManager.AddHook(cPluginManager.HOOK_CHAT, OnChatMessage);
+</pre></p>
+ ]],
+ Functions =
+ {
+ AddHook =
+ {
+ { Params = "{{cPluginManager#Hooks|HookType}}, [HookFunction]", Return = "", Notes = "(STATIC) Informs the plugin manager that it should call the specified function when the specified hook event occurs. If a function is not specified, a default global function name is looked up, based on the hook type" },
+ { Params = "{{cPlugin|Plugin}}, {{cPluginManager#Hooks|HookType}}, [HookFunction]", Return = "", Notes = "(STATIC, <b>DEPRECATED</b>) Informs the plugin manager that it should call the specified function when the specified hook event occurs. If a function is not specified, a default function name is looked up, based on the hook type. NOTE: This format is deprecated and the server outputs a warning if it is used!" },
+ },
+ BindCommand =
+ {
+ { Params = "Command, Permission, Callback, HelpString", Return = "[bool]", Notes = "(STATIC) Binds an in-game command with the specified callback function, permission and help string. By common convention, providing an empty string for HelpString will hide the command from the /help display. Returns true if successful, logs to console and returns no value on error. The callback uses the following signature: <pre class=\"prettyprint lang-lua\">function(Split, {{cPlayer|Player}})</pre> The Split parameter contains an array-table of the words that the player has sent, Player is the {{cPlayer}} object representing the player who sent the command. If the callback returns true, the command is assumed to have executed successfully; in all other cases the server sends a warning to the player that the command is unknown (this is so that subcommands can be implemented)." },
+ { Params = "Command, Permission, Callback, HelpString", Return = "[bool]", Notes = "Binds an in-game command with the specified callback function, permission and help string. By common convention, providing an empty string for HelpString will hide the command from the /help display. Returns true if successful, logs to console and returns no value on error. The callback uses the following signature: <pre class=\"prettyprint lang-lua\">function(Split, {{cPlayer|Player}})</pre> The Split parameter contains an array-table of the words that the player has sent, Player is the {{cPlayer}} object representing the player who sent the command. If the callback returns true, the command is assumed to have executed successfully; in all other cases the server sends a warning to the player that the command is unknown (this is so that subcommands can be implemented)." },
+ },
+ BindConsoleCommand =
+ {
+ { Params = "Command, Callback, HelpString", Return = "[bool]", 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. Returns true if successful, logs to console and returns no value on error. The callback uses the following signature: <pre class=\"prettyprint lang-lua\">function(Split)</pre> The Split parameter contains an array-table of the words that the admin has typed. If the callback returns true, the command is assumed to have executed successfully; in all other cases the server issues a warning to the console that the command is unknown (this is so that subcommands can be implemented)." },
+ { Params = "Command, Callback, HelpString", Return = "[bool]", 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. Returns true if successful, logs to console and returns no value on error. The callback uses the following signature: <pre class=\"prettyprint lang-lua\">function(Split)</pre> The Split parameter contains an array-table of the words that the admin has typed. If the callback returns true, the command is assumed to have executed successfully; in all other cases the server issues a warning to the console that the command is unknown (this is so that subcommands can be implemented)." },
+ },
+ 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." },
+ ExecuteCommand = { Params = "{{cPlayer|Player}}, CommandStr", Return = "{{cPluginManager#CommandResult|CommandResult}}", Notes = "Executes the command as if given by the specified Player. Checks permissions." },
+ FindPlugins = { Params = "", Return = "", Notes = "<b>OBSOLETE</b>, use RefreshPluginList() instead"},
+ ForceExecuteCommand = { Params = "{{cPlayer|Player}}, CommandStr", Return = "{{cPluginManager#CommandResult|CommandResult}}", Notes = "Same as ExecuteCommand, but doesn't check permissions" },
+ ForEachCommand = { Params = "CallbackFn", Return = "bool", Notes = "Calls the CallbackFn function for each command that has been bound using BindCommand(). The CallbackFn has the following signature: <pre class=\"prettyprint lang-lua\">function(Command, Permission, HelpString)</pre>. If the callback returns true, the enumeration is aborted and this API function returns false; if it returns false or no value, the enumeration continues with the next command, and the API function returns true." },
+ ForEachConsoleCommand = { Params = "CallbackFn", Return = "bool", Notes = "Calls the CallbackFn function for each command that has been bound using BindConsoleCommand(). The CallbackFn has the following signature: <pre class=\"prettyprint lang-lua\">function (Command, HelpString)</pre>. If the callback returns true, the enumeration is aborted and this API function returns false; if it returns false or no value, the enumeration continues with the next command, and the API function returns true." },
+ ForEachPlugin = { Params = "CallbackFn", Return = "bool", Notes = "Calls the CallbackFn function for each command that has been bound using BindConsoleCommand(). The CallbackFn has the following signature: <pre class=\"prettyprint lang-lua\">function ({{cPlugin|Plugin}})</pre>. If the callback returns true, the enumeration is aborted and this API function returns false; if it returns false or no value, the enumeration continues with the next command, and the API function returns true." },
+ Get = { Params = "", Return = "cPluginManager", Notes = "(STATIC) Returns the single instance of the plugin manager" },
+ GetAllPlugins = { Params = "", Return = "table", Notes = "Returns a table (dictionary) of all plugins, [name => value], where value is a valid {{cPlugin}} if the plugin is loaded, or the bool value false if the plugin is not loaded." },
+ GetCommandPermission = { Params = "Command", Return = "Permission", Notes = "Returns the permission needed for executing the specified command" },
+ GetCurrentPlugin = { Params = "", Return = "{{cPlugin}}", Notes = "Returns the {{cPlugin}} object for the calling plugin. This is the same object that the Initialize function receives as the argument." },
+ GetNumLoadedPlugins = { Params = "", Return = "number", Notes = "Returns the number of loaded plugins (psLoaded only)" },
+ GetNumPlugins = { Params = "", Return = "number", Notes = "Returns the number of plugins, including the disabled, errored, unloaded and not-found ones" },
+ GetPlugin = { Params = "PluginName", Return = "{{cPlugin}}", Notes = "(<b>DEPRECATED, UNSAFE</b>) Returns a plugin handle of the specified plugin, or nil if such plugin is not loaded. Note thatdue to multithreading the handle is not guaranteed to be safe for use when stored - a single-plugin reload may have been triggered in the mean time for the requested plugin." },
+ GetPluginsPath = { Params = "", Return = "string", Notes = "Returns the path where the individual plugin folders are located. Doesn't include the path separator at the end of the returned string." },
+ IsCommandBound = { Params = "Command", Return = "bool", Notes = "Returns true if in-game Command is already bound (by any plugin)" },
+ IsConsoleCommandBound = { Params = "Command", Return = "bool", Notes = "Returns true if console Command is already bound (by any plugin)" },
+ IsPluginLoaded = { Params = "PluginName", Return = "", Notes = "Returns true if the specified plugin is loaded." },
+ LoadPlugin = { Params = "PluginFolder", Return = "", Notes = "(<b>DEPRECATED</b>) Loads a plugin from the specified folder. NOTE: Loading plugins may be an unsafe operation and may result in a deadlock or a crash. This API is deprecated and might be removed." },
+ LogStackTrace = { Params = "", Return = "", Notes = "(STATIC) Logs a current stack trace of the Lua engine to the server console log. Same format as is used when the plugin fails." },
+ RefreshPluginList = { Params = "", Return = "", Notes = "Refreshes the list of plugins to include all folders inside the Plugins folder (potentially new disabled plugins)" },
+ ReloadPlugins = { Params = "", Return = "", Notes = "Reloads all active plugins" },
+ UnloadPlugin = { Params = "PluginName", Return = "", Notes = "Queues the specified plugin to be unloaded. To avoid deadlocks, the unloading happens in the main tick thread asynchronously." },
+ },
+ ConstantGroups=
+ {
+ CommandResult =
+ {
+ Include = "^cr.*",
+ TextBefore = [[
+ Results that the (Force)ExecuteCommand return. This gives information if the command is executed or not and the reason.
+ ]],
+ },
+ },
+ Constants =
+ {
+ crBlocked = { Notes = "When a plugin stopped the command using the OnExecuteCommand hook" },
+ crError = { Notes = "When the command handler for the given command results in an error" },
+ crExecuted = { Notes = "When the command is successfully executed." },
+ crNoPermission = { Notes = "When the player doesn't have permission to execute the given command." },
+ crUnknownCommand = { Notes = "When the given command doesn't exist." },
+ HOOK_BLOCK_SPREAD = { Notes = "Called when a block spreads based on world conditions" },
+ HOOK_BLOCK_TO_PICKUPS = { Notes = "Called when a block has been dug and is being converted to pickups. The server has provided the default pickups and the plugins may modify them." },
+ HOOK_CHAT = { Notes = "Called when a client sends a chat message that is not a command. The plugin may modify the chat message" },
+ HOOK_CHUNK_AVAILABLE = { Notes = "Called when a chunk is loaded or generated and becomes available in the {{cWorld|world}}." },
+ HOOK_CHUNK_GENERATED = { Notes = "Called after a chunk is generated. A plugin may do last modifications on the generated chunk before it is handed of to the {{cWorld|world}}." },
+ HOOK_CHUNK_GENERATING = { Notes = "Called before a chunk is generated. A plugin may override some parts of the generation algorithm." },
+ HOOK_CHUNK_UNLOADED = { Notes = "Called after a chunk has been unloaded from a {{cWorld|world}}." },
+ HOOK_CHUNK_UNLOADING = { Notes = "Called before a chunk is unloaded from a {{cWorld|world}}. The chunk has already been saved." },
+ HOOK_COLLECTING_PICKUP = { Notes = "Called when a player is about to collect a pickup." },
+ HOOK_CRAFTING_NO_RECIPE = { Notes = "Called when a player has items in the crafting slots and the server cannot locate any recipe. Plugin may provide a recipe." },
+ HOOK_DISCONNECT = { Notes = "Called after the player has disconnected." },
+ HOOK_ENTITY_ADD_EFFECT = { Notes = "Called when an effect is being added to an {{cEntity|entity}}. Plugin may refuse the effect." },
+ HOOK_ENTITY_TELEPORT = { Notes = "Called when an {{cEntity|entity}} is being teleported. Plugin may refuse the teleportation." },
+ HOOK_EXECUTE_COMMAND = { Notes = "Called when a client sends a chat message that is recognized as a command, before handing that command to the regular command handler. A plugin may stop the command from being handled. This hook is called even when the player doesn't have permissions for the command." },
+ HOOK_EXPLODED = { Notes = "Called after an explosion has been processed in a {{cWorld|world}}." },
+ HOOK_EXPLODING = { Notes = "Called before an explosion is processed in a {{cWorld|world}}. A plugin may alter the explosion parameters or cancel the explosion altogether." },
+ HOOK_HANDSHAKE = { Notes = "Called when a Handshake packet is received from a client." },
+ HOOK_HOPPER_PULLING_ITEM = { Notes = "Called when a hopper is pulling an item from the container above it." },
+ HOOK_HOPPER_PUSHING_ITEM = { Notes = "Called when a hopper is pushing an item into the container it is aimed at." },
+ HOOK_KILLING = { Notes = "Called when an entity has just been killed. A plugin may resurrect the entity by setting its health to above zero." },
+ HOOK_LOGIN = { Notes = "Called when a Login packet is sent to the client, before the client is queued for authentication." },
+ HOOK_PLAYER_ANIMATION = { Notes = "Called when a client send the Animation packet." },
+ HOOK_PLAYER_BREAKING_BLOCK = { Notes = "Called when a player is about to break a block. A plugin may cancel the event." },
+ HOOK_PLAYER_BROKEN_BLOCK = { Notes = "Called after a player has broken a block." },
+ HOOK_PLAYER_DESTROYED = { Notes = "Called when the {{cPlayer}} object is destroyed - a player has disconnected." },
+ HOOK_PLAYER_EATING = { Notes = "Called when the player starts eating a held item. Plugins may abort the eating." },
+ HOOK_PLAYER_FISHED = { Notes = "Called when the player reels the fishing rod back in, after the server decides the player's fishing reward." },
+ HOOK_PLAYER_FISHING = { Notes = "Called when the player reels the fishing rod back in, plugins may alter the fishing reward." },
+ HOOK_PLAYER_FOOD_LEVEL_CHANGE = { Notes = "Called when the player's food level is changing. Plugins may refuse the change." },
+ HOOK_PLAYER_JOINED = { Notes = "Called when the player entity has been created. It has not yet been fully initialized." },
+ HOOK_PLAYER_LEFT_CLICK = { Notes = "Called when the client sends the LeftClick packet." },
+ HOOK_PLAYER_MOVING = { Notes = "Called when the player has moved and the movement is now being applied." },
+ HOOK_PLAYER_PLACED_BLOCK = { Notes = "Called when the player has just placed a block" },
+ HOOK_PLAYER_PLACING_BLOCK = { Notes = "Called when the player is about to place a block. A plugin may cancel the event." },
+ HOOK_PLAYER_RIGHT_CLICK = { Notes = "Called when the client sends the RightClick packet." },
+ HOOK_PLAYER_RIGHT_CLICKING_ENTITY = { Notes = "Called when the client sends the UseEntity packet." },
+ HOOK_PLAYER_SHOOTING = { Notes = "Called when the player releases the mouse button to fire their bow." },
+ HOOK_PLAYER_SPAWNED = { Notes = "Called after the player entity has been created. The entity is fully initialized and is spawning in the {{cWorld|world}}." },
+ HOOK_PLAYER_TOSSING_ITEM = { Notes = "Called when the player is tossing the held item (keypress Q)" },
+ HOOK_PLAYER_USED_BLOCK = { Notes = "Called after the player has right-clicked a block" },
+ HOOK_PLAYER_USED_ITEM = { Notes = "Called after the player has right-clicked with a usable item in their hand." },
+ HOOK_PLAYER_USING_BLOCK = { Notes = "Called when the player is about to use (right-click) a block" },
+ HOOK_PLAYER_USING_ITEM = { Notes = "Called when the player is about to right-click with a usable item in their hand." },
+ HOOK_PLUGINS_LOADED = { Notes = "Called after all plugins have loaded." },
+ HOOK_PLUGIN_MESSAGE = { Notes = "Called when a PluginMessage packet is received from a client." },
+ HOOK_POST_CRAFTING = { Notes = "Called after a valid recipe has been chosen for the current contents of the crafting grid. Plugins may modify the recipe." },
+ HOOK_PRE_CRAFTING = { Notes = "Called before a recipe is searched for the current contents of the crafting grid. Plugins may provide a recipe and cancel the built-in search." },
+ HOOK_PROJECTILE_HIT_BLOCK = { Notes = "Called when a {{cProjectileEntity|projectile}} hits a block." },
+ HOOK_PROJECTILE_HIT_ENTITY = { Notes = "Called when a {{cProjectileEntity|projectile}} hits an {{cEntity|entity}}." },
+ HOOK_SERVER_PING = { Notes = "Called when a client pings the server from the server list. Plugins may change the favicon, server description, players online and maximum players values." },
+ HOOK_SPAWNED_ENTITY = { Notes = "Called after an entity is spawned in a {{cWorld|world}}. The entity is already part of the world." },
+ HOOK_SPAWNED_MONSTER = { Notes = "Called after a mob is spawned in a {{cWorld|world}}. The mob is already part of the world." },
+ HOOK_SPAWNING_ENTITY = { Notes = "Called just before an entity is spawned in a {{cWorld|world}}." },
+ HOOK_SPAWNING_MONSTER = { Notes = "Called just before a mob is spawned in a {{cWorld|world}}." },
+ HOOK_TAKE_DAMAGE = { Notes = "Called when an entity is taking any kind of damage. Plugins may modify the damage value, effects, source or cancel the damage." },
+ HOOK_TICK = { Notes = "Called when the main server thread ticks - 20 times a second." },
+ HOOK_UPDATED_SIGN = { Notes = "Called after a {{cSignEntity|sign}} text has been updated, either by a player or by any external means." },
+ HOOK_UPDATING_SIGN = { Notes = "Called before a {{cSignEntity|sign}} text is updated, either by a player or by any external means." },
+ HOOK_WEATHER_CHANGED = { Notes = "Called after the weather has changed." },
+ HOOK_WEATHER_CHANGING = { Notes = "Called just before the weather changes" },
+ HOOK_WORLD_STARTED = { Notes = "Called when a world has been started." },
+ HOOK_WORLD_TICK = { Notes = "Called in each world's tick thread when the game logic is about to tick (20 times a second)." },
+
+ psDisabled = { Notes = "The plugin is not enabled in settings.ini" },
+ psError = { Notes = "The plugin is enabled in settings.ini, but it has run into an error while loading. Use {{cPlugin}}:GetLoadError() to identify the error." },
+ psLoaded = { Notes = "The plugin is enabled and loaded." },
+ psNotFound = { Notes = "The plugin has been loaded, but is no longer present on disk." },
+ psUnloaded = { Notes = "The plugin is enabled in settings.ini, but it has been unloaded (by a command)." },
+ }, -- constants
+
+ ConstantGroups =
+ {
+ Hooks =
+ {
+ Include = {"HOOK_.*"},
+ TextBefore = [[
+ These constants identify individual hooks. To register the plugin to receive notifications on hooks, use the
+ cPluginManager:AddHook() function. For detailed description of each hook, see the <a href='index.html#hooks'>
+ hooks reference</a>.]],
+ },
+ PluginStatus =
+ {
+ Include = {"ps.*"},
+ TextBefore = [[
+ These constants are used to report status of individual plugins. Use {{cPlugin}}:GetStatus() to query the
+ status of a plugin; use cPluginManager::ForEachPlugin() to iterate over plugins.]],
+ },
+ CommandResult =
+ {
+ Include = {"cr.*"},
+ TextBefore = [[
+ These constants are returned by the ExecuteCommand() function to identify the exact outcome of the
+ operation.]],
+ },
+ },
+ }, -- cPluginManager
+}
diff --git a/MCServer/webadmin/(original).html b/MCServer/webadmin/(original).html
deleted file mode 100644
index 673a93ada..000000000
--- a/MCServer/webadmin/(original).html
+++ /dev/null
@@ -1,375 +0,0 @@
-<!DOCTYPE html>
-<head>
-<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
-<link rel="icon" href="files/favicon.ico">
-<title>{TITLE}</title>
-
-<style type="text/css" media="screen">
-
- /* reset CSS */
-
- html, body, div, span, applet, object, iframe,
- h1, h2, h3, h4, h5, h6, p, blockquote, pre,
- a, abbr, acronym, address, big, cite, code,
- del, dfn, em, font, img, ins, kbd, q, s, samp,
- small, strike, strong, sub, sup, tt, var,
- b, u, i, center,
- dl, dt, dd, ol, ul, li,
- fieldset, form, label, legend,
- table, caption, tbody, tfoot, thead, tr, th, td {
- margin: 0;
- padding: 0;
- border: 0;
- outline: 0;
- font-size: 100%;
- vertical-align: baseline;
- background: transparent;
- }
- body {
- line-height: 1;
- }
- ol, ul {
- list-style: none;
- }
- blockquote, q {
- quotes: none;
- }
-
- /* remember to define focus styles! */
- :focus {
- outline: 0;
- }
-
- /* remove textarea resize at Safari */
- textarea {
- resize: none;
- }
-
- /* remember to highlight inserts somehow! */
- ins {
- text-decoration: none;
- }
- del {
- text-decoration: line-through;
- }
-
- /* tables still need 'cellspacing="0"' in the markup */
- table {
- border-collapse: collapse;
- border-spacing: 0;
- }
-
-
- /*
- Origional from http://www.perspectived.com/
- Modified by Ben Phelps
- Made for FakeTruth - MCServer
- */
-
- /* Basic ---------------------------------------- */
-
- .clear { clear: both; }
-
- body {
- background: white;
- font-family: Arial, Helvetica, sans-serif;
- font-size: 12px;
- color: #646464;
- text-align: center;
- }
-
- #wrapper {
- text-align: left;
- width: 930px;
- margin: 0 auto;
- }
-
- /* Logo ---------------------------------------- */
-
- h1 {
- margin: 15px 0 10px 5px;
- width: 180px;
- height: 36px;
- background: url(files/logo.png) no-repeat left top;
- }
-
- h1 a {
- display: block;
- width: 225px;
- height: 28px;
- }
-
- h1 span { display: none; }
-
- a {
- color: #646464;
- }
-
- /* Container ---------------------------------------- */
-
- #containerHolder {
- background: #eee;
- padding: 5px;
- }
-
-
- #container {
- background: #fff url(files/background.gif) repeat-y left top;
- border: 1px solid #ddd;
- width: 918px;
-
- }
-
- #connectHolder {
- background: #eee;
- padding: 5px;
- margin-bottom:8px;
- }
-
-
- #connect {
- border: 1px solid #ddd;
- background-color: #fff;
- padding:5px;
- width: 908px;
- }
-
- .pics {
- height: 375px;
- width: 600px;
- }
-
- .pics img {
- padding: 5px;
- border: 1px solid #ddd;
- background-color: #eee;
- width: 600px;
- height: 375px;
- margin-left: 15px;
- }
-
- /* Login -------------------------------------- */
-
- #loginLogo {
- margin: 0 auto;
- margin-top:100px;
- width: 180px;
- height: 36px;
- background-image: url(files/logo.png);
- }
-
- #loginHolder {
- background: #eee;
- padding: 5px;
- width: 310px;
- margin: 0 auto;
- height: 90px;
- margin-top:20px;
- }
-
- #login {
- padding:10px;
- width: 288px;
- height: 68px;
- border: 1px solid #ddd;
- background:#fff;
- text-align: left;
- }
-
-
- /* Sidebar ---------------------------------------- */
-
- #sidebar {
- width: 179px;
- float: left;
- }
-
- #sidebar .sideNav { width: 179px; }
-
- #sidebar .sideNav li { border-bottom: 1px solid #ddd; width: 179px; }
-
- #sidebar .sideNav li a {
- display: block;
- color: #646464;
- background: #f6f6f6;
- text-decoration: none;
- height: 29px;
- line-height: 29px;
- padding: 0 19px;
- width: 141px;
- }
-
- #sidebar .sideNav li a:hover { background: #fdfcf6; }
-
- #sidebar .sideNav li a.active, #sidebar .sideNav li a.active:hover {
- background: #f0f7fa;
- color: #c66653;
- }
-
- /* Breadcrumb ---------------------------------------- */
-
- h2 {
- width: 718px;
- float: right;
- color: #646464;
- font-size: 16px;
- line-height: 16px;
- font-weight: bold;
- margin: 20px 0 0 0;
- padding: 0 0 10px 0;
- border-bottom: 1px solid #ddd;
- }
-
- h2 a {
- color: #646464;
- text-decoration: none;
- }
-
- h2 a.active { color: #c66653; }
-
- h2 a:hover { text-decoration: underline; }
-
- /* Content ---------------------------------------- */
-
- #main {
- width: 700px;
- float: right;
- padding: 0 19px 0 0;
- }
-
- #main p {
-
- padding: 10px;
-
- }
-
- h3 {
- font-size: 14px;
- line-height: 14px;
- font-weight: bold;
- color: #5494af;
- padding: 0 0 0 10px;
- margin: 20px 0 10px;
- }
-
- h4 {
- padding: 0 0 0 10px;
- margin: 20px 0 10px;
- }
-
- #main ul {
- padding: 0 0 0 10px;
- list-style-type: circle;
- list-style-position: inside;
- }
-
- #main table {
- border-top: 1px solid #ddd;
- width: 700px;
- }
-
- #main table tr th {
- text-align: left;
- background: #f6f6f6;
- padding: 0px 20px;
- height: 20px;
- line-height: 20px;
- border-bottom: 1px solid #ddd;
- }
-
- #main table tr td {
- background: #f6f6f6;
- padding: 0px 20px;
- height: 29px;
- line-height: 29px;
- border-bottom: 1px solid #ddd;
- }
-
- #main table tr.odd td {
- background: #fbfbfb;
- }
-
- #main table tr:hover td { background: #fdfcf6; }
-
- #main table .action {
- text-align: right;
- padding: 0 20px 0 10px;
- }
-
- #main table tr .action a { margin: 0 0 0 10px; text-decoration: none; color: #9b9b9b; }
- #main table tr:hover .action .edit { color: #c5a059; }
- #main table tr:hover .action .delete { color: #a02b2b; }
- #main table tr:hover .action .view { color: #55a34a; }
-
- #main table tr:hover .action a:hover { text-decoration: underline; }
-
- fieldset {
- border: 1px solid #ddd;
- padding: 19px;
- margin: 0 0 20px 0;
- background: #fbfbfb;
- }
-
- form p { margin: 0 0 14px 0; float: left; width: 100%; }
-
- label {
- display: block;
- width: 100%;
- margin: 0 0 7px 0;
- line-height: 12px;
- }
-
- /* Footer ---------------------------------------- */
-
- #footer {
- margin: 10px 0 30px 0;
- font-size: 11px;
- line-height: 11px;
- color: #9B9B9B;
- padding: 0 0 0 5px;
- }
-
- #footer a { color: #9B9B9B; }
-
- #footer a:hover { text-decoration: none; }
-</style>
-
-</head>
-
-<body>
- <div id="wrapper">
- <!-- h1 tag stays for the logo, you can use the a tag for linking the index page -->
- <h1><a href="./"><span>{TITLE}</span></a></h1>
-
- <div id="containerHolder">
- <div id="container">
- <div id="sidebar">
- <ul class="sideNav">
- {MENU}
- </ul>
- <!-- // .sideNav -->
- </div>
- <!-- // #sidebar -->
-
- <!-- h2 stays for breadcrumbs -->
- <h2>Welcome {USERNAME}</h2>
-
- <div id="main">
- <h3>{PLUGIN_NAME}</h3>
-
- {CONTENT}
-
- </div>
- <!-- // #main -->
-
- <div class="clear"></div>
- </div>
- <!-- // #container -->
- </div>
- <!-- // #containerHolder -->
-
- <p id="footer">MCServer is using: {MEM}MB of memory; Current chunk count: {NUMCHUNKS} </p>
- </div>
- <!-- // #wrapper -->
-</body>
-</html>
diff --git a/MCServer/webadmin/template.lua b/MCServer/webadmin/template.lua
index 6ea7b69bc..9d0f860db 100644
--- a/MCServer/webadmin/template.lua
+++ b/MCServer/webadmin/template.lua
@@ -30,20 +30,22 @@ function GetDefaultPage()
Content = Content .. "<p>" .. cRoot:Get():GetServer():GetServerID() .. "</p>"
Content = Content .. "<h4>Plugins:</h4><ul>"
- local AllPlugins = PM:GetAllPlugins()
- for key,value in pairs(AllPlugins) do
- if( value ~= nil and value ~= false ) then
- Content = Content .. "<li>" .. key .. " (version " .. value:GetVersion() .. ")</li>"
+ PM:ForEachPlugin(
+ function (a_CBPlugin)
+ if (a_CBPlugin:IsLoaded()) then
+ Content = Content .. "<li>" .. a_CBPlugin:GetName() .. " (version " .. a_CBPlugin:GetVersion() .. ")</li>"
+ end
end
- end
+ )
Content = Content .. "</ul>"
Content = Content .. "<h4>Players:</h4><ul>"
- local AddPlayerToTable = function( Player )
- Content = Content .. "<li>" .. Player:GetName() .. "</li>"
- end
- cRoot:Get():ForEachPlayer( AddPlayerToTable )
+ cRoot:Get():ForEachPlayer(
+ function(a_CBPlayer)
+ Content = Content .. "<li>" .. Player:GetName() .. "</li>"
+ end
+ )
Content = Content .. "</ul><br>";
diff --git a/MCServer/webadmin/template_orig.lua b/MCServer/webadmin/template_orig.lua
deleted file mode 100644
index a7480f83e..000000000
--- a/MCServer/webadmin/template_orig.lua
+++ /dev/null
@@ -1,137 +0,0 @@
--- Use a table for fast concatenation of strings
-local SiteContent = {}
-function Output(String)
- table.insert(SiteContent, String)
-end
-
-
-
-
-
-function GetTableSize(Table)
- local Size = 0
- for key,value in pairs(Table) do
- Size = Size + 1
- end
- return Size
-end
-
-
-
-
-
-function GetDefaultPage()
- local PM = cRoot:Get():GetPluginManager()
-
- local SubTitle = "Current Game"
- local Content = ""
-
- Content = Content .. "<h4>Server Name:</h4>"
- Content = Content .. "<p>" .. cRoot:Get():GetServer():GetServerID() .. "</p>"
-
- Content = Content .. "<h4>Plugins:</h4><ul>"
- local AllPlugins = PM:GetAllPlugins()
- for key,value in pairs(AllPlugins) do
- if( value ~= nil and value ~= false ) then
- Content = Content .. "<li>" .. key .. " V." .. value:GetVersion() .. "</li>"
- end
- end
-
- Content = Content .. "</ul>"
- Content = Content .. "<h4>Players:</h4><ul>"
-
- local AddPlayerToTable = function( Player )
- Content = Content .. "<li>" .. Player:GetName() .. "</li>"
- end
- cRoot:Get():ForEachPlayer( AddPlayerToTable )
-
- Content = Content .. "</ul><br>";
-
- return Content, SubTitle
-end
-
-
-
-
-
-function ShowPage(WebAdmin, TemplateRequest)
- SiteContent = {}
- local BaseURL = WebAdmin:GetBaseURL(TemplateRequest.Request.Path)
- local Title = "MCServer WebAdmin"
- local MemoryUsageKiB = cRoot:GetPhysicalRAMUsage()
- local NumChunks = cRoot:Get():GetTotalChunkCount()
- local PluginPage = WebAdmin:GetPage(TemplateRequest.Request)
- local PageContent = PluginPage.Content
- local SubTitle = PluginPage.PluginName
- if (PluginPage.TabName ~= "") then
- SubTitle = PluginPage.PluginName .. " - " .. PluginPage.TabName
- end
- if (PageContent == "") then
- PageContent, SubTitle = GetDefaultPage()
- end
-
- Output([[
-<!DOCTYPE html>
-<head>
-<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
-<link rel="icon" href="/favicon.ico">
-<title>]] .. Title .. [[</title>
-<link rel="stylesheet" type="text/css" media="screen" href="/style.css">
-</head>
-
-<body>
- <div id="wrapper">
- <!-- h1 tag stays for the logo, you can use the a tag for linking the index page -->
- <h1>
- <a href="]] .. BaseURL .. [["><span>MCServer</span></a>
- </h1>
- <div id="containerHolder">
- <div id="container">
- <div id="sidebar">
- <ul class="sideNav">
- ]])
-
-
- local AllPlugins = WebAdmin:GetPlugins()
- for key,value in pairs(AllPlugins) do
- local PluginWebTitle = value:GetWebTitle()
- local TabNames = value:GetTabNames()
- if (GetTableSize(TabNames) > 0) then
- Output("<li>"..PluginWebTitle.."</li>\n");
-
- for webname,prettyname in pairs(TabNames) do
- Output("<li><a href='" .. BaseURL .. PluginWebTitle .. "/" .. webname .. "'>" .. prettyname .. "</a></li>\n")
- end
- end
- end
-
-
- Output([[
- </ul>
- <!-- // .sideNav -->
- </div>
- <!-- // #sidebar -->
- <!-- h2 stays for breadcrumbs -->
- <h2>Welcome ]] .. TemplateRequest.Request.Username .. [[</h2>
- <div id="main">
- <h3>]] .. SubTitle .. [[</h3>
- ]] .. PageContent .. [[
- </div>
- <!-- // #main -->
-
- <div class="clear"></div>
-
- </div>
- <!-- // #container -->
- </div>
- <!-- // #containerHolder -->
-
- <p id="footer">MCServer is using: ]] .. MemoryUsageKiB / 1024 .. [[ MiB of memory; Current chunk count: ]] .. NumChunks .. [[ </p>
- </div>
- <!-- // #wrapper -->
-</body>
-</html>
- ]])
-
- return table.concat(SiteContent)
-end
diff --git a/src/Bindings/ManualBindings.cpp b/src/Bindings/ManualBindings.cpp
index 6e579b364..4c8d9ff96 100644
--- a/src/Bindings/ManualBindings.cpp
+++ b/src/Bindings/ManualBindings.cpp
@@ -513,7 +513,7 @@ static int tolua_DoWith(lua_State* tolua_S)
{
return lua_do_error(tolua_S, "Error in function call '#funcname#': Expected a non-empty string for parameter #1", NumArgs);
}
- if (!lua_isfunction( tolua_S, 3))
+ if (!lua_isfunction(tolua_S, 3))
{
return lua_do_error(tolua_S, "Error in function call '#funcname#': Expected a function for parameter #2", NumArgs);
}
@@ -539,20 +539,21 @@ static int tolua_DoWith(lua_State* tolua_S)
class cLuaCallback : public cItemCallback<Ty2>
{
public:
- cLuaCallback(lua_State* a_LuaState, int a_FuncRef, int a_TableRef)
- : LuaState( a_LuaState)
- , FuncRef( a_FuncRef)
- , TableRef( a_TableRef)
- {}
+ cLuaCallback(lua_State* a_LuaState, int a_FuncRef, int a_TableRef):
+ LuaState(a_LuaState),
+ FuncRef(a_FuncRef),
+ TableRef(a_TableRef)
+ {
+ }
private:
virtual bool Item(Ty2 * a_Item) override
{
- lua_rawgeti( LuaState, LUA_REGISTRYINDEX, FuncRef); /* Push function reference */
+ lua_rawgeti(LuaState, LUA_REGISTRYINDEX, FuncRef); /* Push function reference */
tolua_pushusertype(LuaState, a_Item, Ty2::GetClassStatic());
if (TableRef != LUA_REFNIL)
{
- lua_rawgeti( LuaState, LUA_REGISTRYINDEX, TableRef); /* Push table reference */
+ lua_rawgeti(LuaState, LUA_REGISTRYINDEX, TableRef); /* Push table reference */
}
int s = lua_pcall(LuaState, (TableRef == LUA_REFNIL ? 1 : 2), 1, 0);
@@ -633,7 +634,8 @@ static int tolua_DoWithID(lua_State* tolua_S)
LuaState(a_LuaState),
FuncRef(a_FuncRef),
TableRef(a_TableRef)
- {}
+ {
+ }
private:
virtual bool Item(Ty2 * a_Item) override
@@ -699,7 +701,7 @@ static int tolua_DoWithXYZ(lua_State* tolua_S)
int ItemX = ((int)tolua_tonumber(tolua_S, 2, 0));
int ItemY = ((int)tolua_tonumber(tolua_S, 3, 0));
int ItemZ = ((int)tolua_tonumber(tolua_S, 4, 0));
- if (!lua_isfunction( tolua_S, 5))
+ if (!lua_isfunction(tolua_S, 5))
{
return lua_do_error(tolua_S, "Error in function call '#funcname#': Expected a function for parameter #4");
}
@@ -725,20 +727,21 @@ static int tolua_DoWithXYZ(lua_State* tolua_S)
class cLuaCallback : public cItemCallback<Ty2>
{
public:
- cLuaCallback(lua_State* a_LuaState, int a_FuncRef, int a_TableRef)
- : LuaState( a_LuaState)
- , FuncRef( a_FuncRef)
- , TableRef( a_TableRef)
- {}
+ cLuaCallback(lua_State* a_LuaState, int a_FuncRef, int a_TableRef):
+ LuaState(a_LuaState),
+ FuncRef(a_FuncRef),
+ TableRef(a_TableRef)
+ {
+ }
private:
virtual bool Item(Ty2 * a_Item) override
{
- lua_rawgeti( LuaState, LUA_REGISTRYINDEX, FuncRef); /* Push function reference */
+ lua_rawgeti(LuaState, LUA_REGISTRYINDEX, FuncRef); /* Push function reference */
tolua_pushusertype(LuaState, a_Item, Ty2::GetClassStatic());
if (TableRef != LUA_REFNIL)
{
- lua_rawgeti( LuaState, LUA_REGISTRYINDEX, TableRef); /* Push table reference */
+ lua_rawgeti(LuaState, LUA_REGISTRYINDEX, TableRef); /* Push table reference */
}
int s = lua_pcall(LuaState, (TableRef == LUA_REFNIL ? 1 : 2), 1, 0);
@@ -794,7 +797,7 @@ static int tolua_ForEachInChunk(lua_State * tolua_S)
int ChunkX = ((int)tolua_tonumber(tolua_S, 2, 0));
int ChunkZ = ((int)tolua_tonumber(tolua_S, 3, 0));
- if (!lua_isfunction( tolua_S, 4))
+ if (!lua_isfunction(tolua_S, 4))
{
return lua_do_error(tolua_S, "Error in function call '#funcname#': Expected a function for parameter #3");
}
@@ -820,20 +823,21 @@ static int tolua_ForEachInChunk(lua_State * tolua_S)
class cLuaCallback : public cItemCallback<Ty2>
{
public:
- cLuaCallback(lua_State* a_LuaState, int a_FuncRef, int a_TableRef)
- : LuaState( a_LuaState)
- , FuncRef( a_FuncRef)
- , TableRef( a_TableRef)
- {}
+ cLuaCallback(lua_State* a_LuaState, int a_FuncRef, int a_TableRef):
+ LuaState(a_LuaState),
+ FuncRef(a_FuncRef),
+ TableRef(a_TableRef)
+ {
+ }
private:
virtual bool Item(Ty2 * a_Item) override
{
- lua_rawgeti( LuaState, LUA_REGISTRYINDEX, FuncRef); /* Push function reference */
+ lua_rawgeti(LuaState, LUA_REGISTRYINDEX, FuncRef); /* Push function reference */
tolua_pushusertype(LuaState, a_Item, Ty2::GetClassStatic());
if (TableRef != LUA_REFNIL)
{
- lua_rawgeti( LuaState, LUA_REGISTRYINDEX, TableRef); /* Push table reference */
+ lua_rawgeti(LuaState, LUA_REGISTRYINDEX, TableRef); /* Push table reference */
}
int s = lua_pcall(LuaState, (TableRef == LUA_REFNIL ? 1 : 2), 1, 0);
@@ -908,7 +912,8 @@ static int tolua_ForEachInBox(lua_State * tolua_S)
cLuaCallback(cLuaState & a_LuaState, cLuaState::cRef & a_FuncRef) :
m_LuaState(a_LuaState),
m_FnRef(a_FuncRef)
- {}
+ {
+ }
private:
// cItemCallback<Ty2> overrides:
@@ -960,7 +965,7 @@ static int tolua_ForEach(lua_State * tolua_S)
return lua_do_error(tolua_S, "Error in function call '#funcname#': Not called on an object instance");
}
- if (!lua_isfunction( tolua_S, 2))
+ if (!lua_isfunction(tolua_S, 2))
{
return lua_do_error(tolua_S, "Error in function call '#funcname#': Expected a function for parameter #1");
}
@@ -986,20 +991,21 @@ static int tolua_ForEach(lua_State * tolua_S)
class cLuaCallback : public cItemCallback<Ty2>
{
public:
- cLuaCallback(lua_State* a_LuaState, int a_FuncRef, int a_TableRef)
- : LuaState( a_LuaState)
- , FuncRef( a_FuncRef)
- , TableRef( a_TableRef)
- {}
+ cLuaCallback(lua_State* a_LuaState, int a_FuncRef, int a_TableRef):
+ LuaState(a_LuaState),
+ FuncRef(a_FuncRef),
+ TableRef(a_TableRef)
+ {
+ }
private:
virtual bool Item(Ty2 * a_Item) override
{
- lua_rawgeti( LuaState, LUA_REGISTRYINDEX, FuncRef); /* Push function reference */
- tolua_pushusertype( LuaState, a_Item, Ty2::GetClassStatic());
+ lua_rawgeti(LuaState, LUA_REGISTRYINDEX, FuncRef); /* Push function reference */
+ tolua_pushusertype(LuaState, a_Item, Ty2::GetClassStatic());
if (TableRef != LUA_REFNIL)
{
- lua_rawgeti( LuaState, LUA_REGISTRYINDEX, TableRef); /* Push table reference */
+ lua_rawgeti(LuaState, LUA_REGISTRYINDEX, TableRef); /* Push table reference */
}
int s = lua_pcall(LuaState, (TableRef == LUA_REFNIL ? 1 : 2), 1, 0);
@@ -1010,7 +1016,7 @@ static int tolua_ForEach(lua_State * tolua_S)
if (lua_isboolean(LuaState, -1))
{
- return (tolua_toboolean( LuaState, -1, 0) > 0);
+ return (tolua_toboolean(LuaState, -1, 0) > 0);
}
return false; /* Continue enumeration */
}
@@ -1447,29 +1453,12 @@ static int tolua_cWorld_ScheduleTask(lua_State * tolua_S)
static int tolua_cPluginManager_GetAllPlugins(lua_State * tolua_S)
{
- cPluginManager * self = (cPluginManager *)tolua_tousertype(tolua_S, 1, nullptr);
-
- const cPluginManager::PluginMap & AllPlugins = self->GetAllPlugins();
+ // API function no longer available:
+ LOGWARNING("cPluginManager:GetAllPlugins() is no longer available, use cPluginManager:ForEachPlugin() instead");
+ cLuaState::LogStackTrace(tolua_S);
+ // Return an empty table:
lua_newtable(tolua_S);
- int index = 1;
- cPluginManager::PluginMap::const_iterator iter = AllPlugins.begin();
- while (iter != AllPlugins.end())
- {
- const cPlugin* Plugin = iter->second;
- tolua_pushstring(tolua_S, iter->first.c_str());
- if (Plugin != nullptr)
- {
- tolua_pushusertype(tolua_S, (void *)Plugin, "const cPlugin");
- }
- else
- {
- tolua_pushboolean(tolua_S, 0);
- }
- lua_rawset(tolua_S, -3);
- ++iter;
- ++index;
- }
return 1;
}
@@ -1493,6 +1482,18 @@ static int tolua_cPluginManager_GetCurrentPlugin(lua_State * S)
+static int tolua_cPluginManager_GetPlugin(lua_State * tolua_S)
+{
+ // API function no longer available:
+ LOGWARNING("cPluginManager:GetPlugin() is no longer available. Use cPluginManager:DoWithPlugin() or cPluginManager:CallPlugin() instead.");
+ cLuaState::LogStackTrace(tolua_S);
+ return 0;
+}
+
+
+
+
+
static int tolua_cPluginManager_LogStackTrace(lua_State * S)
{
cLuaState::LogStackTrace(S);
@@ -1694,10 +1695,11 @@ static int tolua_cPluginManager_ForEachCommand(lua_State * tolua_S)
class cLuaCallback : public cPluginManager::cCommandEnumCallback
{
public:
- cLuaCallback(lua_State * a_LuaState, int a_FuncRef)
- : LuaState( a_LuaState)
- , FuncRef( a_FuncRef)
- {}
+ cLuaCallback(lua_State * a_LuaState, int a_FuncRef):
+ LuaState(a_LuaState),
+ FuncRef(a_FuncRef)
+ {
+ }
private:
virtual bool Command(const AString & a_Command, const cPlugin * a_Plugin, const AString & a_Permission, const AString & a_HelpString) override
@@ -1717,7 +1719,7 @@ static int tolua_cPluginManager_ForEachCommand(lua_State * tolua_S)
if (lua_isboolean(LuaState, -1))
{
- return (tolua_toboolean( LuaState, -1, 0) > 0);
+ return (tolua_toboolean(LuaState, -1, 0) > 0);
}
return false; /* Continue enumeration */
}
@@ -1771,10 +1773,11 @@ static int tolua_cPluginManager_ForEachConsoleCommand(lua_State * tolua_S)
class cLuaCallback : public cPluginManager::cCommandEnumCallback
{
public:
- cLuaCallback(lua_State * a_LuaState, int a_FuncRef)
- : LuaState( a_LuaState)
- , FuncRef( a_FuncRef)
- {}
+ cLuaCallback(lua_State * a_LuaState, int a_FuncRef):
+ LuaState(a_LuaState),
+ FuncRef(a_FuncRef)
+ {
+ }
private:
virtual bool Command(const AString & a_Command, const cPlugin * a_Plugin, const AString & a_Permission, const AString & a_HelpString) override
@@ -1794,7 +1797,7 @@ static int tolua_cPluginManager_ForEachConsoleCommand(lua_State * tolua_S)
if (lua_isboolean(LuaState, -1))
{
- return (tolua_toboolean( LuaState, -1, 0) > 0);
+ return (tolua_toboolean(LuaState, -1, 0) > 0);
}
return false; /* Continue enumeration */
}
@@ -2011,7 +2014,11 @@ static int tolua_cPluginManager_CallPlugin(lua_State * tolua_S)
virtual bool Item(cPlugin * a_Plugin) override
{
- m_NumReturns = ((cPluginLua *)a_Plugin)->CallFunctionFromForeignState(
+ if (!a_Plugin->IsLoaded())
+ {
+ return false;
+ }
+ m_NumReturns = static_cast<cPluginLua *>(a_Plugin)->CallFunctionFromForeignState(
m_FunctionName, m_SrcLuaState, 4, lua_gettop(m_SrcLuaState)
);
return true;
@@ -2019,9 +2026,6 @@ static int tolua_cPluginManager_CallPlugin(lua_State * tolua_S)
} 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;
@@ -2031,6 +2035,21 @@ static int tolua_cPluginManager_CallPlugin(lua_State * tolua_S)
+static int tolua_cPluginManager_FindPlugins(lua_State * tolua_S)
+{
+ // API function no longer exists:
+ LOGWARNING("cPluginManager:FindPlugins() is obsolete, use cPluginManager:RefreshPluginList() instead!");
+ cLuaState::LogStackTrace(tolua_S);
+
+ // Still, do the actual work performed by the API function when it existed:
+ cPluginManager::Get()->RefreshPluginList();
+ return 0;
+}
+
+
+
+
+
static int tolua_cWorld_ChunkStay(lua_State * tolua_S)
{
/* Function signature:
@@ -2337,40 +2356,40 @@ static int tolua_cPluginLua_AddWebTab(lua_State * tolua_S)
-static int tolua_cPluginLua_AddTab(lua_State* tolua_S)
+static int tolua_cPlugin_GetDirectory(lua_State * tolua_S)
{
- cPluginLua * self = (cPluginLua *) tolua_tousertype(tolua_S, 1, nullptr);
- LOGWARN("WARNING: Using deprecated function AddTab()! Use AddWebTab() instead. (plugin \"%s\" in folder \"%s\")",
- self->GetName().c_str(), self->GetDirectory().c_str()
- );
- return tolua_cPluginLua_AddWebTab( tolua_S);
+ cLuaState L(tolua_S);
+
+ // Log the obsoletion warning:
+ LOGWARNING("cPlugin:GetDirectory() is obsolete, use cPlugin:GetFolderName() instead.");
+ L.LogStackTrace();
+
+ // Retrieve the params:
+ cPlugin * Plugin = static_cast<cPluginLua *>(tolua_tousertype(tolua_S, 1, nullptr));
+
+ // Get the folder name:
+ L.Push(Plugin->GetFolderName());
+ return 1;
}
-static int tolua_cPlugin_Call(lua_State * tolua_S)
+static int tolua_cPlugin_GetLocalDirectory(lua_State * tolua_S)
{
cLuaState L(tolua_S);
// Log the obsoletion warning:
- LOGWARNING("cPlugin:Call() is obsolete and unsafe, use cPluginManager:CallPlugin() instead.");
+ LOGWARNING("cPlugin:GetLocalDirectory() is obsolete, use cPlugin:GetLocalFolder() instead.");
L.LogStackTrace();
- // Retrieve the params: plugin and the function name to call
- cPluginLua * TargetPlugin = (cPluginLua *) tolua_tousertype(tolua_S, 1, nullptr);
- AString FunctionName = tolua_tostring(tolua_S, 2, "");
+ // Retrieve the params:
+ cPlugin * Plugin = static_cast<cPluginLua *>(tolua_tousertype(tolua_S, 1, nullptr));
- // Call the function:
- int NumReturns = TargetPlugin->CallFunctionFromForeignState(FunctionName, L, 3, lua_gettop(L));
- if (NumReturns < 0)
- {
- LOGWARNING("cPlugin::Call() failed to call destination function");
- L.LogStackTrace();
- return 0;
- }
- return NumReturns;
+ // Get the folder:
+ L.Push(Plugin->GetLocalFolder());
+ return 1;
}
@@ -2628,22 +2647,16 @@ static int tolua_AllToLua_cWebAdmin_GetURLEncodedString(lua_State * tolua_S)
static int tolua_cWebPlugin_GetTabNames(lua_State * tolua_S)
{
- cWebPlugin* self = (cWebPlugin*) tolua_tousertype(tolua_S, 1, nullptr);
-
- const cWebPlugin::TabNameList & TabNames = self->GetTabNames();
-
+ // Returns a map of (SafeTitle -> Title) for the plugin's web tabs.
+ auto self = reinterpret_cast<cWebPlugin *>(tolua_tousertype(tolua_S, 1, nullptr));
+ auto TabNames = self->GetTabNames();
lua_newtable(tolua_S);
int index = 1;
- cWebPlugin::TabNameList::const_iterator iter = TabNames.begin();
- while (iter != TabNames.end())
- {
- const AString & FancyName = iter->first;
- const AString & WebName = iter->second;
- tolua_pushstring( tolua_S, WebName.c_str()); // Because the WebName is supposed to be unique, use it as key
- tolua_pushstring( tolua_S, FancyName.c_str());
- //
+ for (auto itr = TabNames.cbegin(), end = TabNames.cend(); itr != end; ++itr)
+ {
+ tolua_pushstring(tolua_S, itr->second.c_str()); // Because the SafeTitle is supposed to be unique, use it as key
+ tolua_pushstring(tolua_S, itr->first.c_str());
lua_rawset(tolua_S, -3);
- ++iter;
++index;
}
return 1;
@@ -3792,7 +3805,8 @@ void ManualBindings::Bind(lua_State * tolua_S)
tolua_endmodule(tolua_S);
tolua_beginmodule(tolua_S, "cPlugin");
- tolua_function(tolua_S, "Call", tolua_cPlugin_Call);
+ tolua_function(tolua_S, "GetDirectory", tolua_cPlugin_GetDirectory);
+ tolua_function(tolua_S, "GetLocalDirectory", tolua_cPlugin_GetLocalDirectory);
tolua_endmodule(tolua_S);
tolua_beginmodule(tolua_S, "cPluginManager");
@@ -3800,10 +3814,13 @@ void ManualBindings::Bind(lua_State * tolua_S)
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, "FindPlugins", tolua_cPluginManager_FindPlugins);
tolua_function(tolua_S, "ForEachCommand", tolua_cPluginManager_ForEachCommand);
tolua_function(tolua_S, "ForEachConsoleCommand", tolua_cPluginManager_ForEachConsoleCommand);
+ tolua_function(tolua_S, "ForEachPlugin", tolua_ForEach<cPluginManager, cPlugin, &cPluginManager::ForEachPlugin>);
tolua_function(tolua_S, "GetAllPlugins", tolua_cPluginManager_GetAllPlugins);
tolua_function(tolua_S, "GetCurrentPlugin", tolua_cPluginManager_GetCurrentPlugin);
+ tolua_function(tolua_S, "GetPlugin", tolua_cPluginManager_GetPlugin);
tolua_function(tolua_S, "LogStackTrace", tolua_cPluginManager_LogStackTrace);
tolua_endmodule(tolua_S);
@@ -3819,7 +3836,6 @@ void ManualBindings::Bind(lua_State * tolua_S)
tolua_endmodule(tolua_S);
tolua_beginmodule(tolua_S, "cPluginLua");
- tolua_function(tolua_S, "AddTab", tolua_cPluginLua_AddTab);
tolua_function(tolua_S, "AddWebTab", tolua_cPluginLua_AddWebTab);
tolua_endmodule(tolua_S);
diff --git a/src/Bindings/Plugin.cpp b/src/Bindings/Plugin.cpp
index 98ccfb88c..2f2771e38 100644
--- a/src/Bindings/Plugin.cpp
+++ b/src/Bindings/Plugin.cpp
@@ -7,11 +7,11 @@
-cPlugin::cPlugin(const AString & a_PluginDirectory) :
- m_Language(E_CPP),
- m_Name(a_PluginDirectory),
+cPlugin::cPlugin(const AString & a_FolderName) :
+ m_Status(cPluginManager::psDisabled),
+ m_Name(a_FolderName),
m_Version(0),
- m_Directory(a_PluginDirectory)
+ m_FolderName(a_FolderName)
{
}
@@ -28,9 +28,33 @@ cPlugin::~cPlugin()
+void cPlugin::Unload(void)
+{
+ auto pm = cPluginManager::Get();
+ pm->RemovePluginCommands(this);
+ pm->RemovePluginConsoleCommands(this);
+ pm->RemoveHooks(this);
+ OnDisable();
+ m_Status = cPluginManager::psUnloaded;
+ m_LoadError.clear();
+}
+
+
+
+
+
AString cPlugin::GetLocalFolder(void) const
{
- return std::string("Plugins/") + m_Directory;
+ return std::string("Plugins/") + m_FolderName;
+}
+
+
+
+
+void cPlugin::SetLoadError(const AString & a_LoadError)
+{
+ m_Status = cPluginManager::psError;
+ m_LoadError = a_LoadError;
}
diff --git a/src/Bindings/Plugin.h b/src/Bindings/Plugin.h
index 3f9fa7655..5c43f9042 100644
--- a/src/Bindings/Plugin.h
+++ b/src/Bindings/Plugin.h
@@ -1,30 +1,16 @@
-#pragma once
-
-#include "Defines.h"
+// Plugin.h
-class cCommandOutputCallback;
-class cItems;
-class cHopperEntity;
+// Declares the cPlugin class representing an interface that a plugin implementation needs to expose, with some helping functions
-class cBlockEntityWithItems;
-class cClientHandle;
-class cPickup;
-class cPlayer;
-class cProjectileEntity;
-class cEntity;
-class cMonster;
-class cWorld;
-class cChunkDesc;
-struct TakeDamageInfo;
-// fwd: CraftingRecipes.h
-class cCraftingGrid;
-class cCraftingRecipe;
+#pragma once
+#include "Defines.h"
+#include "PluginManager.h"
@@ -35,11 +21,23 @@ class cPlugin
public:
// tolua_end
- cPlugin( const AString & a_PluginDirectory);
+ /** Creates a new instance.
+ a_FolderName is the name of the folder (in the Plugins folder) from which the plugin is loaded.
+ The plugin's name defaults to the folder name. */
+ cPlugin(const AString & a_FolderName);
+
virtual ~cPlugin();
+ /** Called as the last call into the plugin before it is unloaded. */
virtual void OnDisable(void) {}
- virtual bool Initialize(void) = 0;
+
+ /** Loads and initializes the plugin. Sets m_Status to psLoaded or psError accordingly.
+ Returns true if the initialization succeeded, false otherwise. */
+ virtual bool Load(void) = 0;
+
+ /** Unloads the plugin. Sets m_Status to psDisabled.
+ The default implementation removes the plugin's associations with cPluginManager, descendants should call it as well. */
+ virtual void Unload(void);
// Called each tick
virtual void Tick(float a_Dt) = 0;
@@ -109,19 +107,17 @@ public:
/** Handles the command split into a_Split, issued by player a_Player.
Command permissions have already been checked.
- Returns true if command handled successfully
- */
+ Returns true if command handled successfully. */
virtual bool HandleCommand(const AStringVector & a_Split, cPlayer & a_Player, const AString & a_FullCommand) = 0;
/** Handles the console command split into a_Split.
- Returns true if command handled successfully. Output is to be sent to the a_Output callback.
- */
+ Returns true if command handled successfully. Output is to be sent to the a_Output callback. */
virtual bool HandleConsoleCommand(const AStringVector & a_Split, cCommandOutputCallback & a_Output, const AString & a_FullCommand) = 0;
- /// All bound commands are to be removed, do any language-dependent cleanup here
+ /** All bound commands are to be removed, do any language-dependent cleanup here */
virtual void ClearCommands(void) {}
- /// All bound console commands are to be removed, do any language-dependent cleanup here
+ /** All bound console commands are to be removed, do any language-dependent cleanup here */
virtual void ClearConsoleCommands(void) {}
// tolua_begin
@@ -131,28 +127,43 @@ public:
int GetVersion(void) const { return m_Version; }
void SetVersion(int a_Version) { m_Version = a_Version; }
- const AString & GetDirectory(void) const {return m_Directory; }
- AString GetLocalDirectory(void) const {return GetLocalFolder(); } // OBSOLETE, use GetLocalFolder() instead
+ /** Returns the name of the folder (in the Plugins folder) from which the plugin is loaded. */
+ const AString & GetFolderName(void) const {return m_FolderName; }
+
+ /** Returns the folder relative to the MCS Executable, from which the plugin is loaded. */
AString GetLocalFolder(void) const;
+
+ /** Returns the error encountered while loading the plugin. Only valid if m_Status == psError. */
+ const AString & GetLoadError(void) const { return m_LoadError; }
+
+ cPluginManager::ePluginStatus GetStatus(void) const { return m_Status; }
+
+ bool IsLoaded(void) const { return (m_Status == cPluginManager::psLoaded); }
// tolua_end
+ // Needed for ManualBindings' tolua_ForEach<>
+ static const char * GetClassStatic(void) { return "cPlugin"; }
+
+protected:
+ friend class cPluginManager;
+
+ cPluginManager::ePluginStatus m_Status;
- /* This should not be exposed to scripting languages */
- enum PluginLanguage
- {
- E_CPP,
- E_LUA,
- E_SQUIRREL, // OBSOLETE, but kept in place to remind us of the horrors lurking in the history
- };
- PluginLanguage GetLanguage() { return m_Language; }
- void SetLanguage( PluginLanguage a_Language) { m_Language = a_Language; }
-
-private:
- PluginLanguage m_Language;
+ /** The name of the plugin, used to identify the plugin in the system and for inter-plugin calls. */
AString m_Name;
+
int m_Version;
- AString m_Directory;
+ /** Name of the folder (in the Plugins folder) from which the plugin is loaded. */
+ AString m_FolderName;
+
+ /** The error encountered while loading the plugin.
+ Only valid if m_Status == psError. */
+ AString m_LoadError;
+
+
+ /** Sets m_LoadError to the specified string and m_Status to psError. */
+ void SetLoadError(const AString & a_LoadError);
}; // tolua_export
diff --git a/src/Bindings/PluginLua.cpp b/src/Bindings/PluginLua.cpp
index 9bbac92b0..ddd3398a5 100644
--- a/src/Bindings/PluginLua.cpp
+++ b/src/Bindings/PluginLua.cpp
@@ -93,7 +93,7 @@ void cPluginLua::Close(void)
-bool cPluginLua::Initialize(void)
+bool cPluginLua::Load(void)
{
cCSLock Lock(m_CriticalSection);
if (!m_LuaState.IsValid())
@@ -144,6 +144,7 @@ bool cPluginLua::Initialize(void)
// Warn if there are no Lua files in the plugin folder:
if (LuaFiles.empty())
{
+ SetLoadError("No lua files found, plugin is probably missing.");
LOGWARNING("No lua files found: plugin %s is missing.", GetName().c_str());
Close();
return false;
@@ -155,6 +156,7 @@ bool cPluginLua::Initialize(void)
AString Path = PluginPath + *itr;
if (!m_LuaState.LoadFile(Path))
{
+ SetLoadError(Printf("Failed to load file %s.", itr->c_str()));
Close();
return false;
}
@@ -164,6 +166,8 @@ bool cPluginLua::Initialize(void)
AString Path = PluginPath + "Info.lua";
if (!m_LuaState.LoadFile(Path))
{
+ SetLoadError("Failed to load file Info.lua.");
+ m_Status = cPluginManager::psError;
Close();
return false;
}
@@ -173,17 +177,20 @@ bool cPluginLua::Initialize(void)
bool res = false;
if (!m_LuaState.Call("Initialize", this, cLuaState::Return, res))
{
+ SetLoadError("Cannot call the Initialize() function.");
LOGWARNING("Error in plugin %s: Cannot call the Initialize() function. Plugin is temporarily disabled.", GetName().c_str());
Close();
return false;
}
if (!res)
{
+ SetLoadError("The Initialize() function failed.");
LOGINFO("Plugin %s: Initialize() call failed, plugin is temporarily disabled.", GetName().c_str());
Close();
return false;
}
+ m_Status = cPluginManager::psLoaded;
return true;
}
@@ -191,6 +198,17 @@ bool cPluginLua::Initialize(void)
+void cPluginLua::Unload(void)
+{
+ ClearTabs();
+ super::Unload();
+ Close();
+}
+
+
+
+
+
void cPluginLua::OnDisable(void)
{
cCSLock Lock(m_CriticalSection);
@@ -1983,40 +2001,29 @@ void cPluginLua::AddResettable(cPluginLua::cResettablePtr a_Resettable)
-AString cPluginLua::HandleWebRequest(const HTTPRequest * a_Request)
+AString cPluginLua::HandleWebRequest(const HTTPRequest & a_Request)
{
- cCSLock Lock(m_CriticalSection);
- std::string RetVal = "";
-
- std::pair< std::string, std::string > TabName = GetTabNameForRequest(a_Request);
- std::string SafeTabName = TabName.second;
- if (SafeTabName.empty())
+ // Find the tab to use for the request:
+ auto TabName = GetTabNameForRequest(a_Request);
+ AString SafeTabTitle = TabName.second;
+ if (SafeTabTitle.empty())
{
return "";
}
-
- sWebPluginTab * Tab = 0;
- for (TabList::iterator itr = GetTabs().begin(); itr != GetTabs().end(); ++itr)
+ auto Tab = GetTabBySafeTitle(SafeTabTitle);
+ if (Tab == nullptr)
{
- if ((*itr)->SafeTitle.compare(SafeTabName) == 0) // This is the one! Rawr
- {
- Tab = *itr;
- break;
- }
+ return "";
}
- if (Tab != nullptr)
+ // Get the page content from the plugin:
+ cCSLock Lock(m_CriticalSection);
+ AString Contents = Printf("WARNING: WebPlugin tab '%s' did not return a string!", Tab->m_Title.c_str());
+ if (!m_LuaState.Call(Tab->m_UserData, &a_Request, cLuaState::Return, Contents))
{
- AString Contents = Printf("WARNING: WebPlugin tab '%s' did not return a string!", Tab->Title.c_str());
- if (!m_LuaState.Call(Tab->UserData, a_Request, cLuaState::Return, Contents))
- {
- return "Lua encountered error while processing the page request";
- }
-
- RetVal += Contents;
+ return "Lua encountered error while processing the page request";
}
-
- return RetVal;
+ return Contents;
}
@@ -2031,13 +2038,7 @@ bool cPluginLua::AddWebTab(const AString & a_Title, lua_State * a_LuaState, int
LOGERROR("Only allowed to add a tab to a WebPlugin of your own Plugin!");
return false;
}
- sWebPluginTab * Tab = new sWebPluginTab();
- Tab->Title = a_Title;
- Tab->SafeTitle = SafeString(a_Title);
-
- Tab->UserData = a_FunctionReference;
-
- GetTabs().push_back(Tab);
+ AddNewWebTab(a_Title, a_FunctionReference);
return true;
}
diff --git a/src/Bindings/PluginLua.h b/src/Bindings/PluginLua.h
index c14b02687..393737b34 100644
--- a/src/Bindings/PluginLua.h
+++ b/src/Bindings/PluginLua.h
@@ -32,6 +32,8 @@ class cPluginLua :
public cPlugin,
public cWebPlugin
{
+ typedef cPlugin super;
+
public:
// tolua_end
@@ -96,7 +98,8 @@ public:
~cPluginLua();
virtual void OnDisable(void) override;
- virtual bool Initialize(void) override;
+ virtual bool Load(void) override;
+ virtual void Unload(void) override;
virtual void Tick(float a_Dt) override;
@@ -173,12 +176,13 @@ public:
/** 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
+ // cWebPlugin overrides
virtual const AString GetWebTitle(void) const {return GetName(); }
+ virtual AString HandleWebRequest(const HTTPRequest & a_Request) override;
- // cWebPlugin and WebAdmin stuff
- virtual AString HandleWebRequest(const HTTPRequest * a_Request) override;
- bool AddWebTab(const AString & a_Title, lua_State * a_LuaState, int a_FunctionReference); // >> EXPORTED IN MANUALBINDINGS <<
+ /** Adds a new web tab to webadmin.
+ Displaying the tab calls the referenced function. */
+ bool AddWebTab(const AString & a_Title, lua_State * a_LuaState, int a_FunctionReference); // Exported in ManualBindings.cpp
/** 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);
diff --git a/src/Bindings/PluginManager.cpp b/src/Bindings/PluginManager.cpp
index 8935f7dd3..003996802 100644
--- a/src/Bindings/PluginManager.cpp
+++ b/src/Bindings/PluginManager.cpp
@@ -59,39 +59,48 @@ void cPluginManager::ReloadPlugins(void)
-void cPluginManager::FindPlugins(void)
+void cPluginManager::RefreshPluginList(void)
{
+ // Get a list of currently available folders:
AString PluginsPath = GetPluginsPath() + "/";
-
- // First get a clean list of only the currently running plugins, we don't want to mess those up
- for (PluginMap::iterator itr = m_Plugins.begin(); itr != m_Plugins.end();)
+ AStringVector Contents = cFile::GetFolderContents(PluginsPath.c_str());
+ AStringVector Folders;
+ for (auto & item: Contents)
{
- if (itr->second == nullptr)
+ if ((item == ".") || (item == "..") || (!cFile::IsFolder(PluginsPath + item)))
{
- PluginMap::iterator thiz = itr;
- ++thiz;
- m_Plugins.erase( itr);
- itr = thiz;
+ // We only want folders, and don't want "." or ".."
continue;
}
- ++itr;
- }
+ Folders.push_back(item);
+ } // for item - Contents[]
- AStringVector Files = cFile::GetFolderContents(PluginsPath.c_str());
- for (AStringVector::const_iterator itr = Files.begin(); itr != Files.end(); ++itr)
+ // Set all plugins with invalid folders as psNotFound:
+ for (auto & plugin: m_Plugins)
{
- if ((*itr == ".") || (*itr == "..") || (!cFile::IsFolder(PluginsPath + *itr)))
+ if (std::find(Folders.cbegin(), Folders.cend(), plugin->GetFolderName()) == Folders.end())
{
- // We only want folders, and don't want "." or ".."
- continue;
+ plugin->m_Status = psNotFound;
}
+ } // for plugin - m_Plugins[]
- // Add plugin name/directory to the list
- if (m_Plugins.find(*itr) == m_Plugins.end())
+ // Add all newly discovered plugins:
+ for (auto & folder: Folders)
+ {
+ bool hasFound = false;
+ for (auto & plugin: m_Plugins)
{
- m_Plugins[*itr] = nullptr;
+ if (plugin->GetFolderName() == folder)
+ {
+ hasFound = true;
+ break;
+ }
+ } // for plugin - m_Plugins[]
+ if (!hasFound)
+ {
+ m_Plugins.push_back(std::make_shared<cPluginLua>(folder));
}
- }
+ } // for folder - Folders[]
}
@@ -112,57 +121,23 @@ void cPluginManager::ReloadPluginsNow(void)
void cPluginManager::ReloadPluginsNow(cIniFile & a_SettingsIni)
{
LOG("-- Loading Plugins --");
+
+ // Unload any existing plugins:
m_bReloadPlugins = false;
UnloadPluginsNow();
- FindPlugins();
+ // Refresh the list of plugins to load new ones from disk / remove the deleted ones:
+ RefreshPluginList();
- cServer::BindBuiltInConsoleCommands();
-
- // Check if the Plugins section exists.
- int KeyNum = a_SettingsIni.FindKey("Plugins");
-
- if (KeyNum == -1)
+ // Load the plugins:
+ AStringVector ToLoad = GetFoldersToLoad(a_SettingsIni);
+ for (auto & pluginFolder: ToLoad)
{
- InsertDefaultPlugins(a_SettingsIni);
- KeyNum = a_SettingsIni.FindKey("Plugins");
- }
+ LoadPlugin(pluginFolder);
+ } // for pluginFolder - ToLoad[]
- // How many plugins are there?
- int NumPlugins = a_SettingsIni.GetNumValues(KeyNum);
-
- for (int i = 0; i < NumPlugins; i++)
- {
- AString ValueName = a_SettingsIni.GetValueName(KeyNum, i);
- if (ValueName.compare("Plugin") == 0)
- {
- AString PluginFile = a_SettingsIni.GetValue(KeyNum, i);
- if (!PluginFile.empty())
- {
- if (m_Plugins.find(PluginFile) != m_Plugins.end())
- {
- LoadPlugin(PluginFile);
- }
- }
- }
- }
-
-
- // Remove invalid plugins from the PluginMap.
- for (PluginMap::iterator itr = m_Plugins.begin(); itr != m_Plugins.end();)
- {
- if (itr->second == nullptr)
- {
- PluginMap::iterator thiz = itr;
- ++thiz;
- m_Plugins.erase(itr);
- itr = thiz;
- continue;
- }
- ++itr;
- }
-
- size_t NumLoadedPlugins = GetNumPlugins();
+ // Log a report of the loading process
+ size_t NumLoadedPlugins = GetNumLoadedPlugins();
if (NumLoadedPlugins == 0)
{
LOG("-- No Plugins Loaded --");
@@ -173,7 +148,7 @@ void cPluginManager::ReloadPluginsNow(cIniFile & a_SettingsIni)
}
else
{
- LOG("-- Loaded %i Plugins --", (int)NumLoadedPlugins);
+ LOG("-- Loaded %u Plugins --", static_cast<unsigned>(NumLoadedPlugins));
}
CallHookPluginsLoaded();
}
@@ -200,12 +175,39 @@ void cPluginManager::InsertDefaultPlugins(cIniFile & a_SettingsIni)
void cPluginManager::Tick(float a_Dt)
{
- while (!m_DisablePluginList.empty())
+ // Unload plugins that have been scheduled for unloading:
+ AStringVector PluginsToUnload;
{
- RemovePlugin(m_DisablePluginList.front());
- m_DisablePluginList.pop_front();
+ cCSLock Lock(m_CSPluginsToUnload);
+ std::swap(m_PluginsToUnload, PluginsToUnload);
}
+ for (auto & folder: PluginsToUnload)
+ {
+ bool HasUnloaded = false;
+ bool HasFound = false;
+ for (auto & plugin: m_Plugins)
+ {
+ if (plugin->GetFolderName() == folder)
+ {
+ HasFound = true;
+ if (plugin->IsLoaded())
+ {
+ plugin->Unload();
+ HasUnloaded = true;
+ }
+ }
+ }
+ if (!HasFound)
+ {
+ LOG("Cannot unload plugin in folder \"%s\", there's no such plugin folder", folder.c_str());
+ }
+ else if (!HasUnloaded)
+ {
+ LOG("Cannot unload plugin in folder \"%s\", it has not been loaded.", folder.c_str());
+ }
+ } // for plugin - m_Plugins[]
+ // If a plugin reload has been scheduled, reload now:
if (m_bReloadPlugins)
{
ReloadPluginsNow();
@@ -1477,68 +1479,56 @@ cPluginManager::CommandResult cPluginManager::HandleCommand(cPlayer & a_Player,
-cPlugin * cPluginManager::GetPlugin(const AString & a_Plugin) const
+void cPluginManager::UnloadPluginsNow()
{
- for (PluginMap::const_iterator itr = m_Plugins.begin(); itr != m_Plugins.end(); ++itr)
- {
- if (itr->second == nullptr)
- {
- // The plugin is currently unloaded
- continue;
- }
+ // Remove all bindings:
+ m_Hooks.clear();
+ m_Commands.clear();
+ m_ConsoleCommands.clear();
- if (itr->second->GetName().compare(a_Plugin) == 0)
+ // Re-bind built-in console commands:
+ cServer::BindBuiltInConsoleCommands();
+
+ // Unload all loaded plugins:
+ for (auto & plugin: m_Plugins)
+ {
+ if (plugin->IsLoaded())
{
- return itr->second;
+ plugin->Unload();
}
}
- return 0;
}
-const cPluginManager::PluginMap & cPluginManager::GetAllPlugins() const
+void cPluginManager::UnloadPlugin(const AString & a_PluginFolder)
{
- return m_Plugins;
+ cCSLock Lock(m_CSPluginsToUnload);
+ m_PluginsToUnload.push_back(a_PluginFolder);
}
-void cPluginManager::UnloadPluginsNow()
+bool cPluginManager::LoadPlugin(const AString & a_FolderName)
{
- m_Hooks.clear();
-
- while (!m_Plugins.empty())
+ for (auto & plugin: m_Plugins)
{
- RemovePlugin(m_Plugins.begin()->second);
- }
-
- m_Commands.clear();
- m_ConsoleCommands.clear();
-}
-
-
-
-
-
-bool cPluginManager::DisablePlugin(const AString & a_PluginName)
-{
- PluginMap::iterator itr = m_Plugins.find(a_PluginName);
- if (itr == m_Plugins.end())
- {
- return false;
- }
+ if (plugin->GetFolderName() == a_FolderName)
+ {
+ if (!plugin->IsLoaded())
+ {
+ return plugin->Load();
+ }
+ return true;
+ }
+ } // for plugin - m_Plugins[]
- if (itr->first.compare(a_PluginName) == 0) // _X 2013_02_01: wtf? Isn't this supposed to be what find() does?
- {
- m_DisablePluginList.push_back(itr->second);
- itr->second = nullptr; // Get rid of this thing right away
- return true;
- }
+ // Plugin not found
+ LOG("Cannot load plugin, folder \"%s\" not found.", a_FolderName.c_str());
return false;
}
@@ -1546,15 +1536,6 @@ bool cPluginManager::DisablePlugin(const AString & a_PluginName)
-bool cPluginManager::LoadPlugin(const AString & a_PluginName)
-{
- return AddPlugin(new cPluginLua(a_PluginName.c_str()));
-}
-
-
-
-
-
void cPluginManager::RemoveHooks(cPlugin * a_Plugin)
{
for (HookMap::iterator itr = m_Hooks.begin(), end = m_Hooks.end(); itr != end; ++itr)
@@ -1567,32 +1548,6 @@ void cPluginManager::RemoveHooks(cPlugin * a_Plugin)
-void cPluginManager::RemovePlugin(cPlugin * a_Plugin)
-{
- for (PluginMap::iterator itr = m_Plugins.begin(); itr != m_Plugins.end(); ++itr)
- {
- if (itr->second == a_Plugin)
- {
- m_Plugins.erase(itr);
- break;
- }
- }
-
- RemovePluginCommands(a_Plugin);
- RemovePluginConsoleCommands(a_Plugin);
- RemoveHooks(a_Plugin);
- if (a_Plugin != nullptr)
- {
- a_Plugin->OnDisable();
- }
- delete a_Plugin;
- a_Plugin = nullptr;
-}
-
-
-
-
-
void cPluginManager::RemovePluginCommands(cPlugin * a_Plugin)
{
if (a_Plugin != nullptr)
@@ -1619,6 +1574,22 @@ void cPluginManager::RemovePluginCommands(cPlugin * a_Plugin)
+bool cPluginManager::IsPluginLoaded(const AString & a_PluginName)
+{
+ for (auto & plugin: m_Plugins)
+ {
+ if (plugin->GetName() == a_PluginName)
+ {
+ return true;
+ }
+ }
+ return false;
+}
+
+
+
+
+
bool cPluginManager::BindCommand(const AString & a_Command, cPlugin * a_Plugin, const AString & a_Permission, const AString & a_HelpString)
{
CommandMap::iterator cmd = m_Commands.find(a_Command);
@@ -1836,31 +1807,31 @@ bool cPluginManager::IsValidHookType(int a_HookType)
bool cPluginManager::DoWithPlugin(const AString & a_PluginName, cPluginCallback & a_Callback)
{
// TODO: Implement locking for plugins
- PluginMap::iterator itr = m_Plugins.find(a_PluginName);
- if ((itr == m_Plugins.end()) || (itr->second == nullptr))
+ for (auto & plugin: m_Plugins)
{
- return false;
+ if (plugin->GetName() == a_PluginName)
+ {
+ return a_Callback.Item(plugin.get());
+ }
}
- return a_Callback.Item(itr->second);
+ return false;
}
-bool cPluginManager::AddPlugin(cPlugin * a_Plugin)
+bool cPluginManager::ForEachPlugin(cPluginCallback & a_Callback)
{
- m_Plugins[a_Plugin->GetDirectory()] = a_Plugin;
-
- if (a_Plugin->Initialize())
+ // TODO: Implement locking for plugins
+ for (auto & plugin: m_Plugins)
{
- // Initialization OK
- return true;
+ if (a_Callback.Item(plugin.get()))
+ {
+ return false;
+ }
}
-
- // Initialization failed
- RemovePlugin(a_Plugin); // Also undoes any registrations that Initialize() might have made
- return false;
+ return true;
}
@@ -1869,21 +1840,23 @@ bool cPluginManager::AddPlugin(cPlugin * a_Plugin)
void cPluginManager::AddHook(cPlugin * a_Plugin, int a_Hook)
{
- if (!a_Plugin)
+ if (a_Plugin == nullptr)
{
LOGWARN("Called cPluginManager::AddHook() with a_Plugin == nullptr");
return;
}
PluginList & Plugins = m_Hooks[a_Hook];
- Plugins.remove(a_Plugin);
- Plugins.push_back(a_Plugin);
+ if (std::find(Plugins.cbegin(), Plugins.cend(), a_Plugin) == Plugins.cend())
+ {
+ Plugins.push_back(a_Plugin);
+ }
}
-size_t cPluginManager::GetNumPlugins() const
+size_t cPluginManager::GetNumPlugins(void) const
{
return m_Plugins.size();
}
@@ -1891,3 +1864,53 @@ size_t cPluginManager::GetNumPlugins() const
+
+size_t cPluginManager::GetNumLoadedPlugins(void) const
+{
+ size_t res = 0;
+ for (auto & plugin: m_Plugins)
+ {
+ if (plugin->IsLoaded())
+ {
+ res += 1;
+ }
+ }
+ return res;
+}
+
+
+
+
+
+AStringVector cPluginManager::GetFoldersToLoad(cIniFile & a_SettingsIni)
+{
+ // Check if the Plugins section exists.
+ int KeyNum = a_SettingsIni.FindKey("Plugins");
+ if (KeyNum == -1)
+ {
+ InsertDefaultPlugins(a_SettingsIni);
+ KeyNum = a_SettingsIni.FindKey("Plugins");
+ }
+
+ // Get the list of plugins to load:
+ AStringVector res;
+ int NumPlugins = a_SettingsIni.GetNumValues(KeyNum);
+ for (int i = 0; i < NumPlugins; i++)
+ {
+ AString ValueName = a_SettingsIni.GetValueName(KeyNum, i);
+ if (ValueName.compare("Plugin") == 0)
+ {
+ AString PluginFile = a_SettingsIni.GetValue(KeyNum, i);
+ if (!PluginFile.empty())
+ {
+ res.push_back(PluginFile);
+ }
+ }
+ } // for i - ini values
+
+ return res;
+}
+
+
+
+
diff --git a/src/Bindings/PluginManager.h b/src/Bindings/PluginManager.h
index be6ff021a..994f19943 100644
--- a/src/Bindings/PluginManager.h
+++ b/src/Bindings/PluginManager.h
@@ -6,48 +6,30 @@
-class cPlugin;
-// fwd: World.h
-class cWorld;
-// fwd: ChunkDesc.h
+// fwd:
+class cBlockEntityWithItems;
class cChunkDesc;
-
-// fwd: Entities/Entity.h
-class cEntity;
-
-// fwd: Entities/ProjectileEntity.h
-class cProjectileEntity;
-
-// fwd: Mobs/Monster.h
-class cMonster;
-
-// fwd: Player.h
-class cPlayer;
-
-// fwd: CraftingRecipes.h
+class cClientHandle;
+class cCommandOutputCallback;
class cCraftingGrid;
class cCraftingRecipe;
-
-// fwd: Pickup.h
+class cEntity;
+class cHopperEntity;
+class cItems;
+class cMonster;
class cPickup;
-
-// fwd: Pawn.h
+class cPlayer;
+class cPlugin;
+class cProjectileEntity;
+class cWorld;
struct TakeDamageInfo;
-// fwd: CommandOutput.h
-class cCommandOutputCallback;
-
-// fwd: BlockEntities/HopperEntity.h
-class cHopperEntity;
-
-// fwd: BlockEntities/BlockEntityWithItems.h
-class cBlockEntityWithItems;
-
+typedef SharedPtr<cPlugin> cPluginPtr;
+typedef std::vector<cPluginPtr> cPluginPtrs;
-class cItems;
@@ -55,12 +37,7 @@ class cItems;
class cPluginManager
{
public:
- // tolua_end
-
- // Called each tick
- virtual void Tick(float a_Dt);
-
- // tolua_begin
+
enum CommandResult
{
crExecuted,
@@ -70,6 +47,29 @@ public:
crNoPermission,
} ;
+
+ /** Defines the status of a single plugin - whether it is loaded, disabled or errored. */
+ enum ePluginStatus
+ {
+ /** The plugin has been loaded successfully. */
+ psLoaded,
+
+ /** The plugin is disabled in settings.ini. */
+ psDisabled,
+
+ /** The plugin is enabled in settings.ini but has been unloaded (by a command). */
+ psUnloaded,
+
+ /** The plugin is enabled in settings.ini but has failed to load.
+ m_LoadError is the description of the error. */
+ psError,
+
+ /** The plugin has been loaded before, but after a folder refresh it is no longer present.
+ The plugin will be unloaded in the next call to ReloadPlugins(). */
+ psNotFound,
+ };
+
+
enum PluginHook
{
HOOK_BLOCK_SPREAD,
@@ -160,24 +160,31 @@ public:
/** The interface used for enumerating and extern-calling plugins */
typedef cItemCallback<cPlugin> cPluginCallback;
+ typedef std::list<cPlugin *> PluginList;
+
+
+ /** Called each tick, calls the plugins' OnTick hook, as well as processes plugin events (addition, removal) */
+ void Tick(float a_Dt);
/** Returns the instance of the Plugin Manager (there is only ever one) */
static cPluginManager * Get(void); // tolua_export
- typedef std::map< AString, cPlugin * > PluginMap;
- typedef std::list< cPlugin * > PluginList;
- cPlugin * GetPlugin( const AString & a_Plugin) const; // tolua_export
- const PluginMap & GetAllPlugins() const; // >> EXPORTED IN MANUALBINDINGS <<
+ /** Refreshes the m_Plugins list based on the current contents of the Plugins folder.
+ If an active plugin's folder is not found anymore, the plugin is set as psNotFound, but not yet unloaded. */
+ void RefreshPluginList(); // tolua_export
- // tolua_begin
- void FindPlugins();
- void ReloadPlugins();
- // tolua_end
+ /** Schedules a reload of the plugins to happen within the next call to Tick(). */
+ 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.
+ If a plugin adds multiple handlers for a single hook, it is added only once (ignore-duplicates). */
void AddHook(cPlugin * a_Plugin, int a_HookType);
+ /** Returns the number of all plugins in m_Plugins (includes disabled, unloaded and errored plugins). */
size_t GetNumPlugins() const; // tolua_export
+
+ /** Returns the number of plugins that are psLoaded. */
+ size_t GetNumLoadedPlugins(void) const; // tolua_export
// Calls for individual hooks. Each returns false if the action is to continue or true if the plugin wants to abort
bool CallHookBlockSpread (cWorld & a_World, int a_BlockX, int a_BlockY, int a_BlockZ, eSpreadSource a_Source);
@@ -242,18 +249,26 @@ public:
bool CallHookWorldStarted (cWorld & a_World);
bool CallHookWorldTick (cWorld & a_World, std::chrono::milliseconds a_Dt, std::chrono::milliseconds a_LastTickDurationMSec);
- bool DisablePlugin(const AString & a_PluginName); // tolua_export
- bool LoadPlugin (const AString & a_PluginName); // tolua_export
+ /** Queues the specified plugin to be unloaded in the next call to Tick().
+ Note that this function returns before the plugin is unloaded, to avoid deadlocks. */
+ void UnloadPlugin(const AString & a_PluginFolder); // tolua_export
+
+ /** Loads the plugin from the specified plugin folder.
+ Returns true if the plugin was loaded successfully or was already loaded before, false otherwise. */
+ bool LoadPlugin(const AString & a_PluginFolder); // tolua_export
/** Removes all hooks the specified plugin has registered */
void RemoveHooks(cPlugin * a_Plugin);
- /** Removes the plugin from the internal structures and deletes its object. */
- void RemovePlugin(cPlugin * a_Plugin);
+ /** Removes the plugin of the specified name from the internal structures and deletes its object. */
+ void RemovePlugin(const AString & a_PluginName);
/** Removes all command bindings that the specified plugin has made */
void RemovePluginCommands(cPlugin * a_Plugin);
-
+
+ /** Returns true if the specified plugin is loaded. */
+ bool IsPluginLoaded(const AString & a_PluginName); // tolua_export
+
/** 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
@@ -296,8 +311,12 @@ public:
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. */
+ Returns false if plugin not found, otherwise returns the value that the callback has returned. */
bool DoWithPlugin(const AString & a_PluginName, cPluginCallback & a_Callback);
+
+ /** Calls the specified callback for each plugin in m_Plugins.
+ Returns true if all plugins have been reported, false if the callback has aborted the enumeration by returning true. */
+ bool ForEachPlugin(cPluginCallback & a_Callback);
/** Returns the path where individual plugins' folders are expected.
The path doesn't end in a slash. */
@@ -317,14 +336,26 @@ private:
typedef std::map<int, cPluginManager::PluginList> HookMap;
typedef std::map<AString, cCommandReg> CommandMap;
- PluginList m_DisablePluginList;
- PluginMap m_Plugins;
+
+ /** FolderNames of plugins that should be unloaded.
+ The plugins will be unloaded within the next call to Tick(), to avoid multithreading issues.
+ Protected against multithreaded access by m_CSPluginsToUnload. */
+ AStringVector m_PluginsToUnload;
+
+ /** Protects m_PluginsToUnload against multithreaded access. */
+ mutable cCriticalSection m_CSPluginsToUnload;
+
+ /** All plugins that have been found in the Plugins folder. */
+ cPluginPtrs m_Plugins;
+
HookMap m_Hooks;
CommandMap m_Commands;
CommandMap m_ConsoleCommands;
+ /** If set to true, all the plugins will be reloaded within the next call to Tick(). */
bool m_bReloadPlugins;
+
cPluginManager();
virtual ~cPluginManager();
@@ -340,11 +371,11 @@ private:
/** 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. */
- 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 crExecuted if the command is executed. */
CommandResult HandleCommand(cPlayer & a_Player, const AString & a_Command, bool a_ShouldCheckPermissions);
+
+ /** Returns the folders that are specified in the settings ini to load plugins from. */
+ AStringVector GetFoldersToLoad(cIniFile & a_SettingsIni);
} ; // tolua_export
diff --git a/src/Bindings/WebPlugin.cpp b/src/Bindings/WebPlugin.cpp
index 5759b20e7..1eca7de93 100644
--- a/src/Bindings/WebPlugin.cpp
+++ b/src/Bindings/WebPlugin.cpp
@@ -24,75 +24,82 @@ cWebPlugin::cWebPlugin()
cWebPlugin::~cWebPlugin()
{
+ ASSERT(m_Tabs.empty()); // Has ClearTabs() been called?
+
+ // Remove from WebAdmin:
cWebAdmin * WebAdmin = cRoot::Get()->GetWebAdmin();
if (WebAdmin != nullptr)
{
WebAdmin->RemovePlugin(this);
}
+}
+
+
+
- for (TabList::iterator itr = m_Tabs.begin(); itr != m_Tabs.end(); ++itr)
+
+cWebPlugin::cTabNames cWebPlugin::GetTabNames(void) const
+{
+ std::list< std::pair<AString, AString>> NameList;
+ for (auto itr = m_Tabs.cbegin(), end = m_Tabs.cend(); itr != end; ++itr)
{
- delete *itr;
+ NameList.push_back(std::make_pair((*itr)->m_Title, (*itr)->m_SafeTitle));
}
- m_Tabs.clear();
+ return NameList;
}
-std::list<std::pair<AString, AString> > cWebPlugin::GetTabNames(void)
+cWebPlugin::cTabPtr cWebPlugin::GetTabBySafeTitle(const AString & a_SafeTitle) const
{
- std::list< std::pair< AString, AString > > NameList;
- for (TabList::iterator itr = GetTabs().begin(); itr != GetTabs().end(); ++itr)
+ cCSLock Lock(m_CSTabs);
+ for (auto itr = m_Tabs.cbegin(), end = m_Tabs.cend(); itr != end; ++itr)
{
- std::pair< AString, AString > StringPair;
- StringPair.first = (*itr)->Title;
- StringPair.second = (*itr)->SafeTitle;
- NameList.push_back( StringPair);
+ if ((*itr)->m_SafeTitle == a_SafeTitle)
+ {
+ return *itr;
+ }
}
- return NameList;
+ return nullptr;
}
-std::pair< AString, AString > cWebPlugin::GetTabNameForRequest(const HTTPRequest * a_Request)
+std::pair<AString, AString> cWebPlugin::GetTabNameForRequest(const HTTPRequest & a_Request)
{
- std::pair< AString, AString > Names;
- AStringVector Split = StringSplit(a_Request->Path, "/");
+ AStringVector Split = StringSplit(a_Request.Path, "/");
+ if (Split.empty())
+ {
+ return std::make_pair(AString(), AString());
+ }
- if (Split.size() > 1)
+ cCSLock Lock(m_CSTabs);
+ cTabPtr Tab;
+ if (Split.size() > 2) // If we got the tab name, show that page
{
- sWebPluginTab * Tab = nullptr;
- if (Split.size() > 2) // If we got the tab name, show that page
+ for (auto itr = m_Tabs.cbegin(), end = m_Tabs.cend(); itr != end; ++itr)
{
- for (TabList::iterator itr = GetTabs().begin(); itr != GetTabs().end(); ++itr)
+ if ((*itr)->m_SafeTitle.compare(Split[2]) == 0) // This is the one!
{
- if ((*itr)->SafeTitle.compare(Split[2]) == 0) // This is the one!
- {
- Tab = *itr;
- break;
- }
- }
- }
- else // Otherwise show the first tab
- {
- if (GetTabs().size() > 0)
- {
- Tab = *GetTabs().begin();
+ return std::make_pair((*itr)->m_Title, (*itr)->m_SafeTitle);
}
}
+ // Tab name not found, display an "empty" page:
+ return std::make_pair(AString(), AString());
+ }
- if (Tab != nullptr)
- {
- Names.first = Tab->Title;
- Names.second = Tab->SafeTitle;
- }
+ // Show the first tab:
+ if (!m_Tabs.empty())
+ {
+ return std::make_pair(m_Tabs.front()->m_SafeTitle, m_Tabs.front()->m_SafeTitle);
}
- return Names;
+ // No tabs at all:
+ return std::make_pair(AString(), AString());
}
@@ -101,14 +108,16 @@ std::pair< AString, AString > cWebPlugin::GetTabNameForRequest(const HTTPRequest
AString cWebPlugin::SafeString(const AString & a_String)
{
AString RetVal;
- for (unsigned int i = 0; i < a_String.size(); ++i)
+ auto len = a_String.size();
+ RetVal.reserve(len);
+ for (size_t i = 0; i < len; ++i)
{
char c = a_String[i];
if (c == ' ')
{
c = '_';
}
- RetVal.push_back( c);
+ RetVal.push_back(c);
}
return RetVal;
}
@@ -116,3 +125,28 @@ AString cWebPlugin::SafeString(const AString & a_String)
+
+void cWebPlugin::AddNewWebTab(const AString & a_Title, int a_UserData)
+{
+ auto Tab = std::make_shared<cTab>(a_Title, a_UserData);
+ cCSLock Lock(m_CSTabs);
+ m_Tabs.push_back(Tab);
+}
+
+
+
+
+
+void cWebPlugin::ClearTabs(void)
+{
+ // Remove the webadmin tabs:
+ cTabPtrs Tabs;
+ {
+ cCSLock Lock(m_CSTabs);
+ std::swap(Tabs, m_Tabs);
+ }
+}
+
+
+
+
diff --git a/src/Bindings/WebPlugin.h b/src/Bindings/WebPlugin.h
index 9b825b918..ade4f4359 100644
--- a/src/Bindings/WebPlugin.h
+++ b/src/Bindings/WebPlugin.h
@@ -12,34 +12,67 @@ class cWebPlugin
{
public:
// tolua_end
+
+ struct cTab
+ {
+ AString m_Title;
+ AString m_SafeTitle;
+ int m_UserData;
+
+ cTab(const AString & a_Title, int a_UserData):
+ m_Title(a_Title),
+ m_SafeTitle(cWebPlugin::SafeString(a_Title)),
+ m_UserData(a_UserData)
+ {
+ }
+ };
+
+ typedef SharedPtr<cTab> cTabPtr;
+ typedef std::list<cTabPtr> cTabPtrs;
+ typedef std::list<std::pair<AString, AString>> cTabNames;
+
+
cWebPlugin();
+
virtual ~cWebPlugin();
// tolua_begin
+
+ /** Returns the title of the plugin, as it should be presented in the webadmin's pages tree. */
virtual const AString GetWebTitle(void) const = 0;
- virtual AString HandleWebRequest(const HTTPRequest * a_Request) = 0;
+ /** Sanitizes the input string, replacing spaces with underscores. */
+ static AString SafeString(const AString & a_String);
- static AString SafeString( const AString & a_String);
// tolua_end
- struct sWebPluginTab
- {
- std::string Title;
- std::string SafeTitle;
+ virtual AString HandleWebRequest(const HTTPRequest & a_Request) = 0;
- int UserData;
- };
+ /** Adds a new web tab with the specified contents. */
+ void AddNewWebTab(const AString & a_Title, int a_UserData);
+
+ /** Removes all the tabs. */
+ void ClearTabs(void);
- typedef std::list< sWebPluginTab* > TabList;
- TabList & GetTabs() { return m_Tabs; }
+ /** Returns all the tabs that this plugin has registered. */
+ const cTabPtrs & GetTabs(void) const { return m_Tabs; }
- typedef std::list< std::pair<AString, AString> > TabNameList;
- TabNameList GetTabNames(); // >> EXPORTED IN MANUALBINDINGS <<
- std::pair< AString, AString > GetTabNameForRequest(const HTTPRequest* a_Request);
+ /** Returns all of the tabs that this plugin has registered. */
+ cTabNames GetTabNames(void) const; // Exported in ManualBindings.cpp
+
+ /** Returns the tab that has the specified SafeTitle.
+ Returns nullptr if no such tab. */
+ cTabPtr GetTabBySafeTitle(const AString & a_SafeTitle) const;
+
+ std::pair<AString, AString> GetTabNameForRequest(const HTTPRequest & a_Request);
private:
- TabList m_Tabs;
+ /** All tabs that this plugin has registered.
+ Protected against multithreaded access by m_CSTabs. */
+ cTabPtrs m_Tabs;
+
+ /** Protects m_Tabs against multithreaded access. */
+ mutable cCriticalSection m_CSTabs;
}; // tolua_export
diff --git a/src/Server.cpp b/src/Server.cpp
index df2c7deef..8b6a2e769 100644
--- a/src/Server.cpp
+++ b/src/Server.cpp
@@ -464,22 +464,12 @@ void cServer::ExecuteConsoleCommand(const AString & a_Cmd, cCommandOutputCallbac
{
if (split.size() > 1)
{
- cPluginManager::PluginMap map = cPluginManager::Get()->GetAllPlugins();
-
- for (auto plugin_entry : map)
- {
- if (plugin_entry.first == split[1])
- {
- a_Output.Out("Error! Plugin is already loaded!");
- a_Output.Finished();
- return;
- }
- }
+ cPluginManager::Get()->RefreshPluginList(); // Refresh the plugin list, so that if the plugin was added just now, it is loadable
a_Output.Out(cPluginManager::Get()->LoadPlugin(split[1]) ? "Plugin loaded" : "Error occurred loading plugin");
}
else
{
- a_Output.Out("Usage: load <pluginname>");
+ a_Output.Out("Usage: load <PluginFolder>");
}
a_Output.Finished();
return;
@@ -488,12 +478,12 @@ void cServer::ExecuteConsoleCommand(const AString & a_Cmd, cCommandOutputCallbac
{
if (split.size() > 1)
{
- cPluginManager::Get()->RemovePlugin(cPluginManager::Get()->GetPlugin(split[1]));
- a_Output.Out("Plugin unloaded");
+ cPluginManager::Get()->UnloadPlugin(split[1]);
+ a_Output.Out("Plugin unload scheduled");
}
else
{
- a_Output.Out("Usage: unload <pluginname>");
+ a_Output.Out("Usage: unload <PluginFolder>");
}
a_Output.Finished();
return;
diff --git a/src/WebAdmin.cpp b/src/WebAdmin.cpp
index 13cf3cc41..aff3b4710 100644
--- a/src/WebAdmin.cpp
+++ b/src/WebAdmin.cpp
@@ -464,10 +464,10 @@ sWebAdminPage cWebAdmin::GetPage(const HTTPRequest & a_Request)
{
if ((*itr)->GetWebTitle() == Split[1])
{
- Page.Content = (*itr)->HandleWebRequest(&a_Request);
+ Page.Content = (*itr)->HandleWebRequest(a_Request);
cWebPlugin * WebPlugin = *itr;
FoundPlugin = WebPlugin->GetWebTitle();
- AString TabName = WebPlugin->GetTabNameForRequest(&a_Request).first;
+ AString TabName = WebPlugin->GetTabNameForRequest(a_Request).first;
Page.PluginName = FoundPlugin;
Page.TabName = TabName;
break;
@@ -489,20 +489,32 @@ AString cWebAdmin::GetDefaultPage(void)
Content += "<h4>Server Name:</h4>";
Content += "<p>" + AString( cRoot::Get()->GetServer()->GetServerID()) + "</p>";
+ // Display a list of all plugins:
Content += "<h4>Plugins:</h4><ul>";
- cPluginManager * PM = cPluginManager::Get();
- const cPluginManager::PluginMap & List = PM->GetAllPlugins();
- for (cPluginManager::PluginMap::const_iterator itr = List.begin(); itr != List.end(); ++itr)
+ struct cPluginCallback:
+ public cPluginManager::cPluginCallback
{
- if (itr->second == nullptr)
+ AString & m_Content;
+
+ cPluginCallback(AString & a_Content):
+ m_Content(a_Content)
{
- continue;
}
- AppendPrintf(Content, "<li>%s V.%i</li>", itr->second->GetName().c_str(), itr->second->GetVersion());
- }
+
+ virtual bool Item(cPlugin * a_Plugin) override
+ {
+ if (a_Plugin->IsLoaded())
+ {
+ AppendPrintf(m_Content, "<li>%s V.%i</li>", a_Plugin->GetName().c_str(), a_Plugin->GetVersion());
+ }
+ return false;
+ }
+ } Callback(Content);
+ cPluginManager::Get()->ForEachPlugin(Callback);
Content += "</ul>";
- Content += "<h4>Players:</h4><ul>";
+ // Display a list of all players:
+ Content += "<h4>Players:</h4><ul>";
cPlayerAccum PlayerAccum;
cWorld * World = cRoot::Get()->GetDefaultWorld(); // TODO - Create a list of worlds and players
if (World != nullptr)