diff options
82 files changed, 1951 insertions, 1827 deletions
diff --git a/MCServer/Plugins/APIDump/APIDesc.lua b/MCServer/Plugins/APIDump/APIDesc.lua index a892adbcd..2224c549d 100644 --- a/MCServer/Plugins/APIDump/APIDesc.lua +++ b/MCServer/Plugins/APIDump/APIDesc.lua @@ -973,12 +973,15 @@ cFile:Delete("/usr/bin/virus.exe"); ]], Functions = { - ChangeFileExt = { Params = "FileName, NewExt", Return = "string", Notes = "(STATIC) Returns FileName with its extension changed to NewExt. FileName may contain path elements, extension is recognized as the last dot in the string." }, + ChangeFileExt = { Params = "FileName, NewExt", Return = "string", Notes = "(STATIC) Returns FileName with its extension changed to NewExt. NewExt may begin with a dot, but needn't, the result is the same in both cases (the first dot, if present, is ignored). FileName may contain path elements, extension is recognized as the last dot after the last path separator in the string." }, Copy = { Params = "SrcFileName, DstFileName", Return = "bool", Notes = "(STATIC) Copies a single file to a new destination. Returns true if successful. Fails if the destination already exists." }, CreateFolder = { Params = "FolderName", Return = "bool", Notes = "(STATIC) Creates a new folder. Returns true if successful." }, Delete = { Params = "FileName", Return = "bool", Notes = "(STATIC) Deletes the specified file. Returns true if successful." }, Exists = { Params = "FileName", Return = "bool", Notes = "(STATIC) Returns true if the specified file exists." }, + GetExecutableExt = { Params = "", Return = "string", Notes = "(STATIC) Returns the customary executable extension (including the dot) used by the current platform (\".exe\" on Windows, empty string on Linux). " }, GetFolderContents = { Params = "FolderName", Return = "array table of strings", Notes = "(STATIC) Returns the contents of the specified folder, as an array table of strings. Each filesystem object is listed. Use the IsFile() and IsFolder() functions to determine the object type." }, + GetLastModificationTime = { Params = "Path", Return = "number", Notes = "(STATIC) Returns the last modification time (in current timezone) of the specified file or folder. Returns zero if file not found / not accessible. The returned value is in the same units as values returned by os.time()." }, + GetPathSeparator = { Params = "", Return = "string", Notes = "(STATIC) Returns the primary path separator used by the current platform. Returns \"\\\" on Windows and \"/\" on Linux. Note that the platform or CRT may support additional path separators, those are not reported." }, GetSize = { Params = "FileName", Return = "number", Notes = "(STATIC) Returns the size of the file, or -1 on failure." }, IsFile = { Params = "Path", Return = "bool", Notes = "(STATIC) Returns true if the specified path points to an existing file." }, IsFolder = { Params = "Path", Return = "bool", Notes = "(STATIC) Returns true if the specified path points to an existing folder." }, @@ -1914,159 +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_MAX = { Notes = "The maximum TypeID of a hook. Used internally by MCS to check hook type for validity." }, - HOOK_NUM_HOOKS = { Notes = "Total number of hook types MCS supports. Used internally by MCS to check hook type for validity." }, - 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 = [[ @@ -2321,15 +2171,6 @@ local CompressedString = cStringCompression.CompressStringGZIP("DataToCompress") Inherits = "cEntity", }, - cWebAdmin = - { - Desc = "", - Functions = - { - GetHTMLEscapedString = { Params = "string", Return = "string", Notes = "Gets the HTML escaped representation of a requested string. This is useful for user input and game data that is not guaranteed to be escaped already." }, - }, - }, -- cWebAdmin - cWebPlugin = { Desc = "", @@ -2652,39 +2493,6 @@ World:ForEachEntity( }, -- AdditionalInfo }, -- cWorld - HTTPFormData = - { - Desc = "This class stores data for one form element for a {{HTTPRequest|HTTP request}}.", - Variables = - { - Name = { Type = "string", Notes = "Name of the form element" }, - Type = { Type = "string", Notes = "Type of the data (usually empty)" }, - Value = { Type = "string", Notes = "Value of the form element. Contains the raw data as sent by the browser." }, - }, - }, -- HTTPFormData - - HTTPRequest = - { - Desc = [[ - This class encapsulates all the data that is sent to the WebAdmin through one HTTP request. Plugins - receive this class as a parameter to the function handling the web requests, as registered in the - FIXME: {{cPluginLua}}:AddWebPage(). - ]], - Constants = - { - FormData = { Notes = "Array-table of {{HTTPFormData}}, contains the values of individual form elements submitted by the client" }, - Params = { Notes = "Map-table of parameters given to the request in the URL (?param=value); if a form uses GET method, this is the same as FormData. For each parameter given as \"param=value\", there is an entry in the table with \"param\" as its key and \"value\" as its value." }, - PostParams = { Notes = "Map-table of data posted through a FORM - either a GET or POST method. Logically the same as FormData, but in a map-table format (for each parameter given as \"param=value\", there is an entry in the table with \"param\" as its key and \"value\" as its value)." }, - }, - - Variables = - { - Method = { Type = "string", Notes = "The HTTP method used to make the request. Usually GET or POST." }, - Path = { Type = "string", Notes = "The Path part of the URL (excluding the parameters)" }, - Username = { Type = "string", Notes = "Name of the logged-in user." }, - }, - }, -- HTTPRequest - ItemCategory = { 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/Plugins/APIDump/Classes/WebAdmin.lua b/MCServer/Plugins/APIDump/Classes/WebAdmin.lua new file mode 100644 index 000000000..808335aea --- /dev/null +++ b/MCServer/Plugins/APIDump/Classes/WebAdmin.lua @@ -0,0 +1,51 @@ +return +{ + cWebAdmin = + { + Desc = "", + Functions = + { + GetHTMLEscapedString = { Params = "string", Return = "string", Notes = "(STATIC) Gets the HTML-escaped representation of a requested string. This is useful for user input and game data that is not guaranteed to be escaped already." }, + }, + }, -- cWebAdmin + + + HTTPFormData = + { + Desc = "This class stores data for one form element for a {{HTTPRequest|HTTP request}}.", + Variables = + { + Name = { Type = "string", Notes = "Name of the form element" }, + Type = { Type = "string", Notes = "Type of the data (usually empty)" }, + Value = { Type = "string", Notes = "Value of the form element. Contains the raw data as sent by the browser." }, + }, + }, -- HTTPFormData + + + HTTPRequest = + { + Desc = [[ + This class encapsulates all the data that is sent to the WebAdmin through one HTTP request. Plugins + receive this class as a parameter to the function handling the web requests, as registered in the + {{cPluginLua}}:AddWebPage(). + ]], + Constants = + { + FormData = { Notes = "Array-table of {{HTTPFormData}}, contains the values of individual form elements submitted by the client" }, + Params = { Notes = "Map-table of parameters given to the request in the URL (?param=value); if a form uses GET method, this is the same as FormData. For each parameter given as \"param=value\", there is an entry in the table with \"param\" as its key and \"value\" as its value." }, + PostParams = { Notes = "Map-table of data posted through a FORM - either a GET or POST method. Logically the same as FormData, but in a map-table format (for each parameter given as \"param=value\", there is an entry in the table with \"param\" as its key and \"value\" as its value)." }, + }, + + Variables = + { + Method = { Type = "string", Notes = "The HTTP method used to make the request. Usually GET or POST." }, + Path = { Type = "string", Notes = "The Path part of the URL (excluding the parameters)" }, + URL = { Type = "string", Notes = "The entire URL used for the request." }, + Username = { Type = "string", Notes = "Name of the logged-in user." }, + }, + }, -- HTTPRequest +} + + + + diff --git a/MCServer/Plugins/Core b/MCServer/Plugins/Core -Subproject ee3cd9ba917baa94d6b9bfe7c9205609e0722fa +Subproject 5f3aca002af6b77c1c67ddc356c63479131dfde diff --git a/MCServer/Plugins/Debuggers/Debuggers.lua b/MCServer/Plugins/Debuggers/Debuggers.lua index d0c362ab4..01a5de81e 100644 --- a/MCServer/Plugins/Debuggers/Debuggers.lua +++ b/MCServer/Plugins/Debuggers/Debuggers.lua @@ -9,7 +9,7 @@ g_ShowFoodStats = false; -- When true, each player's food stats are sent to the -function Initialize(Plugin) +function Initialize(a_Plugin) --[[ -- Test multiple hook handlers: cPluginManager.AddHook(cPluginManager.HOOK_TICK, OnTick1); @@ -45,14 +45,12 @@ function Initialize(Plugin) -- Bind all the console commands: RegisterPluginInfoConsoleCommands(); - Plugin:AddWebTab("Debuggers", HandleRequest_Debuggers) - Plugin:AddWebTab("StressTest", HandleRequest_StressTest) + a_Plugin:AddWebTab("Debuggers", HandleRequest_Debuggers) + a_Plugin:AddWebTab("StressTest", HandleRequest_StressTest) -- Enable the following line for BlockArea / Generator interface testing: -- PluginManager:AddHook(Plugin, cPluginManager.HOOK_CHUNK_GENERATED); - LOG("Initialized " .. Plugin:GetName() .. " v." .. Plugin:GetVersion()) - -- TestBlockAreas() -- TestSQLiteBindings() -- TestExpatBindings() @@ -62,6 +60,11 @@ function Initialize(Plugin) TestStringBase64() -- TestUUIDFromName() -- TestRankMgr() + TestFileExt() + TestFileLastMod() + + local LastSelfMod = cFile:GetLastModificationTime(a_Plugin:GetLocalFolder() .. "/Debuggers.lua") + LOG("Debuggers.lua last modified on " .. os.date("%Y-%m-%dT%H:%M:%S", LastSelfMod)) --[[ -- Test cCompositeChat usage in console-logging: @@ -79,6 +82,40 @@ end; +function TestFileExt() + assert(cFile:ChangeFileExt("fileless_dir/", "new") == "fileless_dir/") + assert(cFile:ChangeFileExt("fileless_dir/", ".new") == "fileless_dir/") + assert(cFile:ChangeFileExt("pathless_file.ext", "new") == "pathless_file.new") + assert(cFile:ChangeFileExt("pathless_file.ext", ".new") == "pathless_file.new") + assert(cFile:ChangeFileExt("path/to/file.ext", "new") == "path/to/file.new") + assert(cFile:ChangeFileExt("path/to/file.ext", ".new") == "path/to/file.new") + assert(cFile:ChangeFileExt("path/to.dir/file", "new") == "path/to.dir/file.new") + assert(cFile:ChangeFileExt("path/to.dir/file", ".new") == "path/to.dir/file.new") + assert(cFile:ChangeFileExt("path/to.dir/file.ext", "new") == "path/to.dir/file.new") + assert(cFile:ChangeFileExt("path/to.dir/file.ext", ".new") == "path/to.dir/file.new") + assert(cFile:ChangeFileExt("path/to.dir/file.longext", "new") == "path/to.dir/file.new") + assert(cFile:ChangeFileExt("path/to.dir/file.longext", ".new") == "path/to.dir/file.new") + assert(cFile:ChangeFileExt("path/to.dir/file.", "new") == "path/to.dir/file.new") + assert(cFile:ChangeFileExt("path/to.dir/file.", ".new") == "path/to.dir/file.new") +end + + + + + +function TestFileLastMod() + local f = assert(io.open("test.txt", "w")) + f:write("test") + f:close() + local filetime = cFile:GetLastModificationTime("test.txt") + local ostime = os.time() + LOG("file time: " .. filetime .. ", OS time: " .. ostime .. ", difference: " .. ostime - filetime) +end + + + + + function TestPluginCalls() -- In order to test the inter-plugin communication, we're going to call Core's ReturnColorFromChar() function -- It is a rather simple function that doesn't need any tables as its params and returns a value, too diff --git a/MCServer/Plugins/Debuggers/Info.lua b/MCServer/Plugins/Debuggers/Info.lua index 0370145df..2e170487b 100644 --- a/MCServer/Plugins/Debuggers/Info.lua +++ b/MCServer/Plugins/Debuggers/Info.lua @@ -28,7 +28,7 @@ g_PluginInfo = Handler = HandleCompo, HelpString = "Tests the cCompositeChat bindings" }, - ["/cs"] = + ["/cstay"] = { Permission = "debuggers", Handler = HandleChunkStay, diff --git a/MCServer/Plugins/HookNotify/HookNotify.lua b/MCServer/Plugins/HookNotify/HookNotify.lua index 1d3d5088e..411dbebbd 100644 --- a/MCServer/Plugins/HookNotify/HookNotify.lua +++ b/MCServer/Plugins/HookNotify/HookNotify.lua @@ -1,456 +1,64 @@ --- Global variables -PLUGIN = {} -- Reference to own plugin object +-- HookNotify.lua +--[[ +Implements the entire plugin +NOTE: This plugin is not meant for production servers. It is used mainly by developers to verify that things +are working properly when implementing MCServer features. Do not enable this plugin on production servers! +This plugin logs a notification for each hook that is being called by the server. +The TICK and WORLD_TICK hooks are disabled because they produce too much output. +--]] -function Initialize(Plugin) - PLUGIN = Plugin - - Plugin:SetName("HookNotify"); - Plugin:SetVersion(1); - - PluginManager = cPluginManager:Get(); - cPluginManager.AddHook(cPluginManager.HOOK_BLOCK_TO_PICKUPS, OnBlockToPickups); - cPluginManager.AddHook(cPluginManager.HOOK_CHAT, OnChat); - cPluginManager.AddHook(cPluginManager.HOOK_CHUNK_AVAILABLE, OnChunkAvailable); - cPluginManager.AddHook(cPluginManager.HOOK_CHUNK_GENERATED, OnChunkGenerated); - cPluginManager.AddHook(cPluginManager.HOOK_CHUNK_GENERATING, OnChunkGenerating); - cPluginManager.AddHook(cPluginManager.HOOK_CHUNK_UNLOADED, OnChunkUnloaded); - cPluginManager.AddHook(cPluginManager.HOOK_CHUNK_UNLOADING, OnChunkUnloading); - cPluginManager.AddHook(cPluginManager.HOOK_COLLECTING_PICKUP, OnCollectingPickup); - cPluginManager.AddHook(cPluginManager.HOOK_CRAFTING_NO_RECIPE, OnCraftingNoRecipe); - cPluginManager.AddHook(cPluginManager.HOOK_DISCONNECT, OnDisconnect); - cPluginManager.AddHook(cPluginManager.HOOK_ENTITY_TELEPORT, OnEntityTeleport); - cPluginManager.AddHook(cPluginManager.HOOK_EXECUTE_COMMAND, OnExecuteCommand); - cPluginManager.AddHook(cPluginManager.HOOK_HANDSHAKE, OnHandshake); - cPluginManager.AddHook(cPluginManager.HOOK_KILLING, OnKilling); - cPluginManager.AddHook(cPluginManager.HOOK_LOGIN, OnLogin); - cPluginManager.AddHook(cPluginManager.HOOK_PLAYER_BREAKING_BLOCK, OnPlayerBreakingBlock); - cPluginManager.AddHook(cPluginManager.HOOK_PLAYER_BROKEN_BLOCK, OnPlayerBrokenBlock); - cPluginManager.AddHook(cPluginManager.HOOK_PLAYER_EATING, OnPlayerEating); - cPluginManager.AddHook(cPluginManager.HOOK_PLAYER_JOINED, OnPlayerJoined); - cPluginManager.AddHook(cPluginManager.HOOK_PLAYER_LEFT_CLICK, OnPlayerLeftClick); - cPluginManager.AddHook(cPluginManager.HOOK_PLAYER_MOVING, OnPlayerMoving); - cPluginManager.AddHook(cPluginManager.HOOK_PLAYER_PLACED_BLOCK, OnPlayerPlacedBlock); - cPluginManager.AddHook(cPluginManager.HOOK_PLAYER_PLACING_BLOCK, OnPlayerPlacingBlock); - cPluginManager.AddHook(cPluginManager.HOOK_PLAYER_RIGHT_CLICK, OnPlayerRightClick); - cPluginManager.AddHook(cPluginManager.HOOK_PLAYER_SHOOTING, OnPlayerShooting); - cPluginManager.AddHook(cPluginManager.HOOK_PLAYER_SPAWNED, OnPlayerSpawned); - cPluginManager.AddHook(cPluginManager.HOOK_PLAYER_TOSSING_ITEM, OnPlayerTossingItem); - cPluginManager.AddHook(cPluginManager.HOOK_PLAYER_USED_BLOCK, OnPlayerUsedBlock); - cPluginManager.AddHook(cPluginManager.HOOK_PLAYER_USED_ITEM, OnPlayerUsedItem); - cPluginManager.AddHook(cPluginManager.HOOK_PLAYER_USING_BLOCK, OnPlayerUsingBlock); - cPluginManager.AddHook(cPluginManager.HOOK_PLAYER_USING_ITEM, OnPlayerUsingItem); - cPluginManager.AddHook(cPluginManager.HOOK_POST_CRAFTING, OnPostCrafting); - cPluginManager.AddHook(cPluginManager.HOOK_PRE_CRAFTING, OnPreCrafting); - cPluginManager.AddHook(cPluginManager.HOOK_SPAWNED_ENTITY, OnSpawnedEntity); - cPluginManager.AddHook(cPluginManager.HOOK_SPAWNED_MONSTER, OnSpawnedMonster); - cPluginManager.AddHook(cPluginManager.HOOK_SPAWNING_ENTITY, OnSpawningEntity); - cPluginManager.AddHook(cPluginManager.HOOK_SPAWNING_MONSTER, OnSpawningMonster); - cPluginManager.AddHook(cPluginManager.HOOK_TAKE_DAMAGE, OnTakeDamage); - cPluginManager.AddHook(cPluginManager.HOOK_UPDATED_SIGN, OnUpdatedSign); - cPluginManager.AddHook(cPluginManager.HOOK_UPDATING_SIGN, OnUpdatingSign); - cPluginManager.AddHook(cPluginManager.HOOK_WEATHER_CHANGED, OnWeatherChanged); - cPluginManager.AddHook(cPluginManager.HOOK_WEATHER_CHANGING, OnWeatherChanging); - - LOGINFO("HookNotify plugin is installed, beware, the log output may be quite large!"); - LOGINFO("You want this plugin enabled only when developing another plugin, not for regular gameplay."); - - return true -end - - - - - -function LogHook(FnName, ...) - LOG(FnName .. "("); - for i, v in ipairs(arg) do - local vt = tostring(v); - local TypeString = type(v); - if (type(v) == "userdata") then - TypeString = tolua.type(v); - end; - LOG(" " .. tostring(i) .. ": " .. TypeString .. ": " .. tostring(v)); - end - LOG(")"); -end +function Initialize(a_Plugin) + -- Notify the admin that HookNotify is installed, this is not meant for production servers + LOGINFO("HookNotify plugin is installed, beware, the log output may be quite large!"); + LOGINFO("You want this plugin enabled only when developing another plugin, not for regular gameplay."); + -- These hooks will not be notified: + local hooksToIgnore = + { + ["HOOK_TICK"] = true, -- Too much spam + ["HOOK_WORLD_TICK"] = true, -- Too much spam + ["HOOK_TAKE_DAMAGE"] = true, -- Has a separate handler with more info logged + ["HOOK_MAX"] = true, -- No such hook, placeholder only + ["HOOK_NUM_HOOKS"] = true, -- No such hook, placeholder only + } + + -- Add all hooks: + for n, v in pairs(cPluginManager) do + if (n:match("HOOK_.*")) then + if not(hooksToIgnore[n]) then + LOG("Adding notification for hook " .. n .. " (" .. v .. ").") + cPluginManager.AddHook(v, + function (...) + LOG(n .. "(") + for i, param in ipairs(arg) do + LOG(" " .. i .. ": " .. tolua.type(param) .. ": " .. tostring(param)) + end + LOG(")"); + end -- hook handler + ) -- AddHook + end -- not (ignore) + end -- n matches "HOOK" + end -- for cPluginManager{} + + -- OnTakeDamage has a special handler listing the details of the damage dealt: + cPluginManager.AddHook(cPluginManager.HOOK_TAKE_DAMAGE, + function (a_Receiver, a_TDI) + -- a_Receiver is cPawn + -- a_TDI is TakeDamageInfo -function OnBlockToPickups(...) - LogHook("OnBlockToPickups", unpack(arg)); - local World, Digger, BlockX, BlockY, BlockZ, BlockType, BlockMeta, Pickups = unpack(arg); - if (Pickups ~= nil) then - local Name = "NULL"; - if (Digger ~= nil) then - Name = Digger:GetName() + LOG("OnTakeDamage(): " .. a_Receiver:GetClass() .. " was dealt RawDamage " .. a_TDI.RawDamage .. ", FinalDamage " .. a_TDI.FinalDamage .. " (that is, " .. (a_TDI.RawDamage - a_TDI.FinalDamage) .. " HPs covered by armor)"); end - LOG("Got cItems from " .. Name .. ", trying to manipulate them."); - Pickups:Add(cItem:new(E_ITEM_DIAMOND_SHOVEL, 1)); - LOG("Current size: " .. Pickups:Size()); - end; -end; - - - - - -function OnChat(...) - LogHook("OnChat", unpack(arg)); -end - - - - - -function OnChunkAvailable(...) - LogHook("OnChunkAvailable", unpack(arg)); -end - - - - - -function OnChunkGenerated(...) - LogHook("OnChunkGenerated", unpack(arg)); -end - - - - - -function OnChunkGenerating(...) - LogHook("OnChunkGenerating", unpack(arg)); -end - - - - - -function OnChunkUnloaded(...) - LogHook("OnChunkUnloaded", unpack(arg)); -end - - - - - -function OnChunkUnloading(...) - LogHook("OnChunkUnloading", unpack(arg)); -end - - - - - -function OnPlayerUsingItem(...) - LogHook("OnPlayerUsingItem", unpack(arg)); -end - - - - - -function OnCollectingPickup(...) - LogHook("OnCollectingPickup", unpack(arg)); -end - - - - -function OnCraftingNoRecipe(...) - LogHook("OnCraftingNoRecipe", unpack(arg)); -end - - - - - -function OnDisconnect(...) - LogHook("OnDisconnect", unpack(arg)); -end - - - - - -function OnEntityTeleport(arg1,arg2,arg3) - if arg1.IsPlayer() then - -- if it's a player, get his name - LOG("OnEntityTeleport: Player: " .. arg1.GetName()); - else - -- if it's a entity, get its type - LOG("OnEntityTeleport: EntityType: " .. arg1.GetEntityType()); - end - LOG("OldPos: " .. arg2.x .. " / " .. arg2.y .. " / " .. arg2.z); - LOG("NewPos: " .. arg3.x .. " / " .. arg3.y .. " / " .. arg3.z); -end - - - - - -function OnExecuteCommand(...) - LogHook("OnExecuteCommand", unpack(arg)); + ) - -- For some reason logging doesn't work for this callback, so list some stuff manually to verify: - LOG("arg1 type: " .. type(arg[1])); - if (arg[1] ~= nil) then - LOG("Player name: " .. arg[1]:GetName()); - end - LOG("Command: " .. arg[2][1]); -end - - - - - -function OnHandshake(...) - LogHook("OnHandshake", unpack(arg)); -end - - - - - -function OnKilling(...) - LogHook("OnKilling", unpack(arg)); -end - - - - - -function OnLogin(...) - LogHook("OnLogin", unpack(arg)); -end - - - - - -function OnPlayerBreakingBlock(...) - LogHook("OnPlayerBreakingBlock", unpack(arg)); -end - - - - - -function OnPlayerBrokenBlock(...) - LogHook("OnPlayerBrokenBlock", unpack(arg)); -end - - - - - -function OnPlayerEating(...) - LogHook("OnPlayerEating", unpack(arg)); -end - - - - - -function OnPlayerJoined(...) - LogHook("OnPlayerJoined", unpack(arg)); -end - - - - - -function OnPlayerLeftClick(...) - LogHook("OnPlayerLeftClick", unpack(arg)); -end - - - - - -function OnPlayerMoving(...) - LogHook("OnPlayerMoving", unpack(arg)); -end - - - - - -function OnPlayerPlacedBlock(...) - LogHook("OnPlayerPlacedBlock", unpack(arg)); -end - - - - - -function OnPlayerPlacingBlock(...) - LogHook("OnPlayerPlacingBlock", unpack(arg)); -end - - - - - -function OnPlayerRightClick(...) - LogHook("OnPlayerRightClick", unpack(arg)); -end - - - - - -function OnPlayerShooting(...) - LogHook("OnPlayerShooting", unpack(arg)); -end - - - - - -function OnPlayerSpawned(...) - LogHook("OnPlayerSpawned", unpack(arg)); -end - - - - - -function OnPlayerTossingItem(...) - LogHook("OnPlayerTossingItem", unpack(arg)); -end - - - - - -function OnPlayerUsedBlock(...) - LogHook("OnPlayerUsedBlock", unpack(arg)); -end - - - - - -function OnPlayerUsedItem(...) - LogHook("OnPlayerUsedItem", unpack(arg)); -end - - - - - -function OnPlayerUsingBlock(...) - LogHook("OnPlayerUsingBlock", unpack(arg)); -end - - - - - -function OnPlayerUsingItem(...) - LogHook("OnPlayerUsingItem", unpack(arg)); -end - - - - - -function OnPostCrafting(...) - LogHook("OnPostCrafting", unpack(arg)); -end - - - - - -function OnPreCrafting(...) - LogHook("OnPreCrafting", unpack(arg)); -end - - - - - -function OnSpawnedEntity(...) - LogHook("OnSpawnedEntity", unpack(arg)); -end - - - - - -function OnSpawnedMonster(...) - LogHook("OnSpawnedMonster", unpack(arg)); -end - - - - - -function OnSpawningEntity(...) - LogHook("OnSpawningEntity", unpack(arg)); -end - - - - - -function OnSpawningMonster(...) - LogHook("OnSpawningMonster", unpack(arg)); -end - - - - - -function OnUpdatedSign(...) - LogHook("OnUpdatedSign", unpack(arg)); -end - - - - - -function OnUpdatingSign(...) - LogHook("OnUpdatingSign", unpack(arg)); -end - - - - - -function OnWeatherChanged(...) - LogHook("OnWeatherChanged", unpack(arg)); -end - - - - - -function OnWeatherChanging(...) - LogHook("OnWeatherChanging", unpack(arg)); -end - - - - - ------------------------------------------------------------------- --- Special handling for OnTakeDamage to print the contents of TDI: - -function OnTakeDamage(Receiver, TDI) - -- Receiver is cPawn - -- TDI is TakeDamageInfo - - LOG("OnTakeDamage(): " .. Receiver:GetClass() .. " was dealt RawDamage " .. TDI.RawDamage .. ", FinalDamage " .. TDI.FinalDamage .. " (that is, " .. (TDI.RawDamage - TDI.FinalDamage) .. " HPs covered by armor)"); + return true end diff --git a/MCServer/crafting.txt b/MCServer/crafting.txt index a24fdee0c..0b6f357c8 100644 --- a/MCServer/crafting.txt +++ b/MCServer/crafting.txt @@ -14,7 +14,10 @@ # # <ItemType> can be either a number, or an item name (checked against items.ini) # -# ^<DamageValue> is optional, if not present, any damage value is matched for ingredients and zero is produced for the result +# ^<DamageValue> is optional, if not present, the default damage for the given item is used +# +# If the DamageValue in the ingredients list is set to -1, the ingredient matches the specified item with any DamageValue. +# This is used e. g. for "any planks -> sticks", or beds using any color wool etc. # # Ingredients with an asterisk for a coord will not match already matched crafting grid items. This enables simplifying some of the recipes, # e. g. hoe: "Iron, 2:1, *:1" @@ -46,10 +49,10 @@ BirchPlanks, 4 = BirchLog, * JunglePlanks, 4 = JungleLog, * AcaciaPlanks, 4 = AcaciaLog, * DarkOakPlanks, 4 = DarkOakLog, * -Stick, 4 = Planks, 2:2, 2:3 +Stick, 4 = Planks^-1, 2:2, 2:3 Torch, 4 = Stick, 1:2 | Coal, 1:1 -Workbench = Planks, 1:1, 1:2, 2:1, 2:2 -Chest = Planks, 1:1, 1:2, 1:3, 2:1, 2:3, 3:1, 3:2, 3:3 +Workbench = Planks^-1, 1:1, 1:2, 2:1, 2:2 +Chest = Planks^-1, 1:1, 1:2, 1:3, 2:1, 2:3, 3:1, 3:2, 3:3 TrappedChest = TripWireHook, 1:1 | Chest, 2:1 EnderChest = EyeOfEnder, 2:2 | Obsidian, 1:1, 1:2, 1:3, 2:1, 2:3, 3:1, 3:2, 3:3 Furnace = Cobblestone, 1:1, 1:2, 1:3, 2:1, 2:3, 3:1, 3:2, 3:3 @@ -79,15 +82,15 @@ HayBale = Wheat, 1:1, 1:2, 1:3, 2:1, 2:2, 2:3, 3:1, 3:2, 3:3 SnowBlock = SnowBall, 1:1, 1:2, 2:1, 2:2 ClayBlock = Clay, 1:1, 1:2, 2:1, 2:2 BrickBlock = Brick, 1:1, 1:2, 2:1, 2:2 +PolishedGranite, 4 = Granite, 1:1, 1:2, 2:1, 2:2 +PolishedDiorite, 4 = Diorite, 1:1, 1:2, 2:1, 2:2 +PolishedAndesite, 4 = Andesite, 1:1, 1:2, 2:1, 2:2 StoneBrick, 4 = Stone, 1:1, 1:2, 2:1, 2:2 -BookShelf = Planks, 1:1, 2:1, 3:1, 1:3, 2:3, 3:3 | Book, 1:2, 2:2, 3:2 +BookShelf = Planks^-1, 1:1, 2:1, 3:1, 1:3, 2:3, 3:3 | Book, 1:2, 2:2, 3:2 Sandstone, 4 = Sand, 1:1, 1:2, 2:1, 2:2 SmoothSandstone, 4 = Sandstone, 1:1, 1:2, 2:1, 2:2 OrnamentSandstone = SandstoneSlab, 1:1, 1:2 JackOLantern = Pumpkin, 1:1 | Torch, 1:2 -PolishedGranite, 4 = Granite, 1:1, 1:2, 2:1, 2:2 -PolishedDiorite, 4 = Diorite, 1:1, 1:2, 2:1, 2:2 -PolishedAndesite, 4 = Andesite, 1:1, 1:2, 2:1, 2:2 CoarsedDirt, 4 = Dirt, 1:1, 2:2 | Gravel, 1:2, 2:1 CoarsedDirt, 4 = Gravel, 1:1, 2:2 | Dirt, 1:2, 2:1 SlimeBlock = Slimeball, 1:1, 1:2, 1:3, 2:1, 2:2, 2:3, 3:1, 3:2, 3:3 @@ -154,8 +157,8 @@ RedSandstoneStairs, 4 = RedSandstone, 3:1, 2:2, 3:2, 1:3, 2:3, 3:3 # # Axes: -WoodenAxe = Stick, 2:2, 2:3 | Planks, 2:1, 1:1, 1:2 -WoodenAxe = Stick, 2:2, 2:3 | Planks, 2:1, 3:1, 3:2 +WoodenAxe = Stick, 2:2, 2:3 | Planks^-1, 2:1, 1:1, 1:2 +WoodenAxe = Stick, 2:2, 2:3 | Planks^-1, 2:1, 3:1, 3:2 StoneAxe = Stick, 2:2, 2:3 | Cobblestone, 2:1, 1:1, 1:2 StoneAxe = Stick, 2:2, 2:3 | Cobblestone, 2:1, 3:1, 3:2 GoldenAxe = Stick, 2:2, 2:3 | GoldIngot, 2:1, 1:1, 1:2 @@ -166,21 +169,21 @@ DiamondAxe = Stick, 2:2, 2:3 | Diamond, 2:1, 1:1, 1:2 DiamondAxe = Stick, 2:2, 2:3 | Diamond, 2:1, 3:1, 3:2 # Pickaxes: -WoodenPickaxe = Stick, 2:2, 2:3 | Planks, 1:1, 2:1, 3:1 +WoodenPickaxe = Stick, 2:2, 2:3 | Planks^-1, 1:1, 2:1, 3:1 StonePickaxe = Stick, 2:2, 2:3 | Cobblestone, 1:1, 2:1, 3:1 GoldenPickaxe = Stick, 2:2, 2:3 | GoldIngot, 1:1, 2:1, 3:1 IronPickaxe = Stick, 2:2, 2:3 | IronIngot, 1:1, 2:1, 3:1 DiamondPickaxe = Stick, 2:2, 2:3 | Diamond, 1:1, 2:1, 3:1 # Shovels: -WoodenShovel = Stick, 2:2, 2:3 | Planks, 2:1 +WoodenShovel = Stick, 2:2, 2:3 | Planks^-1, 2:1 StoneShovel = Stick, 2:2, 2:3 | Cobblestone, 2:1 GoldenShovel = Stick, 2:2, 2:3 | GoldIngot, 2:1 IronShovel = Stick, 2:2, 2:3 | IronIngot, 2:1 DiamondShovel = Stick, 2:2, 2:3 | Diamond, 2:1 # Hoes: -WoodenHoe = Stick, 2:2, 2:3 | Planks, 2:1, *:1 +WoodenHoe = Stick, 2:2, 2:3 | Planks^-1, 2:1, *:1 StoneHoe = Stick, 2:2, 2:3 | Cobblestone, 2:1, *:1 GoldenHoe = Stick, 2:2, 2:3 | GoldIngot, 2:1, *:1 IronHoe = Stick, 2:2, 2:3 | IronIngot, 2:1, *:1 @@ -206,7 +209,7 @@ Lead = String, 1:1, 1:2, 2:1, 3:3 | Slimeball, 2:2 #******************************************************# # Weapons # -WoodenSword = Stick, 2:3 | Planks, 2:1, 2:2 +WoodenSword = Stick, 2:3 | Planks^-1, 2:1, 2:2 StoneSword = Stick, 2:3 | Cobblestone, 2:1, 2:2 GoldenSword = Stick, 2:3 | GoldIngot, 2:1, 2:2 IronSword = Stick, 2:3 | IronIngot, 2:1, 2:2 @@ -268,7 +271,7 @@ hopperminecart = Minecart, * | Hopper, * Rails, 16 = IronIngot, 1:1, 3:1, 1:2, 3:2, 1:3, 3:3 | Stick, 2:2 PoweredRail, 6 = GoldIngot, 1:1, 3:1, 1:2, 3:2, 1:3, 3:3 | Stick, 2:2 | RedstoneDust, 2:3 DetectorRail, 6 = IronIngot, 1:1, 3:1, 1:2, 3:2, 1:3, 3:3 | StonePlate, 2:2 | RedstoneDust, 2:3 -Boat = Planks, 1:1, 3:1, 1:2, 2:2, 3:2 +Boat = Planks^-1, 1:1, 3:1, 1:2, 2:2, 3:2 ActivatorRail, 6 = IronIngot, 1:1, 1:2, 1:3, 3:1, 3:2, 3:3 | Stick, 2:1, 2:3 | RedstoneTorchon, 2:2 @@ -277,35 +280,35 @@ ActivatorRail, 6 = IronIngot, 1:1, 1:2, 1:3, 3:1, 3:2, 3:3 | Stick, 2:1, 2:3 | R #******************************************************# # Mechanisms # -SpruceDoor, 3 = SprucePlanks, 1:1, 1:2, 1:3, 2:1, 2:2, 2:3 -BirchDoor, 3 = BirchPlanks, 1:1, 1:2, 1:3, 2:1, 2:2, 2:3 -JungleDoor, 3 = JunglePlanks, 1:1, 1:2, 1:3, 2:1, 2:2, 2:3 -AcaciaDoor, 3 = AcaciaPlanks, 1:1, 1:2, 1:3, 2:1, 2:2, 2:3 +SpruceDoor, 3 = SprucePlanks, 1:1, 1:2, 1:3, 2:1, 2:2, 2:3 +BirchDoor, 3 = BirchPlanks, 1:1, 1:2, 1:3, 2:1, 2:2, 2:3 +JungleDoor, 3 = JunglePlanks, 1:1, 1:2, 1:3, 2:1, 2:2, 2:3 +AcaciaDoor, 3 = AcaciaPlanks, 1:1, 1:2, 1:3, 2:1, 2:2, 2:3 DarkOakDoor, 3 = DarkOakPlanks, 1:1, 1:2, 1:3, 2:1, 2:2, 2:3 -WoodenDoor, 3 = OakPlanks, 1:1, 1:2, 1:3, 2:1, 2:2, 2:3 +WoodenDoor, 3 = OakPlanks, 1:1, 1:2, 1:3, 2:1, 2:2, 2:3 IronDoor, 3 = IronIngot, 1:1, 1:2, 1:3, 2:1, 2:2, 2:3 -TrapDoor, 2 = Planks, 1:1, 2:1, 3:1, 1:2, 2:2, 3:2 +TrapDoor, 2 = Planks^-1, 1:1, 2:1, 3:1, 1:2, 2:2, 3:2 IronTrapDoor = IronIngot, 1:1, 1:2, 2:1, 2:2 -WoodPlate = Planks, 1:1, 2:1 -StonePlate = Stone, 1:1, 2:1 +WoodPlate = Planks^-1, 1:1, 2:1 +StonePlate = Stone, 1:1, 2:1 lightweightedpressureplate = IronIngot, 1:1, 2:1 heavyweightedpressureplate = GoldIngot, 1:1, 2:1 -StoneButton = Stone, 1:1 -WoodenButton = Planks, 1:1 +StoneButton = Stone, 1:1 +WoodenButton = Planks^-1, 1:1 RedstoneTorchOn = Stick, 1:2 | RedstoneDust, 1:1 Lever = Cobblestone, 1:2 | Stick, 1:1 -NoteBlock = Planks, 1:1, 1:2, 1:3, 2:1, 2:3, 3:1, 3:2, 3:3 | RedstoneDust, 2:2 -Jukebox = Planks, 1:1, 1:2, 1:3, 2:1, 2:3, 3:1, 3:2, 3:3 | Diamond, 2:2 +NoteBlock = Planks^-1, 1:1, 1:2, 1:3, 2:1, 2:3, 3:1, 3:2, 3:3 | RedstoneDust, 2:2 +Jukebox = Planks^-1, 1:1, 1:2, 1:3, 2:1, 2:3, 3:1, 3:2, 3:3 | Diamond, 2:2 Dispenser = Cobblestone, 1:1, 1:2, 1:3, 2:1, 3:1, 3:2, 3:3 | RedstoneDust, 2:3 | Bow, 2:2 Dropper = Cobblestone, 1:1, 2:1, 3:1, 1:2, 1:3, 3:2, 3:3 | Hopper, 2:2 | RedstoneDust, 2:3 Repeater = Stone, 1:2, 2:2, 3:2 | RedstoneTorchOn, 1:1, 3:1 | RedstoneDust, 2:1 Comparator = RedstoneTorchOn, 2:1, 1:2, 3:2 | NetherQuartz, 2:2 | Stone, 1:3, 2:3, 3:3 DaylightSensor = Glass, 1:1, 2:1, 3:1 | NetherQuartz, 1:2, 2:2, 3:2 | Woodslab, 1:3, 2:3, 3:3 Hopper = Ironbars, 1:1, 3:1, 1:2, 3:2, 2:3 | Chest, 2:2 -Piston = Planks, 1:1, 2:1, 3:1 | RedstoneDust, 2:3 | Cobblestone, 1:2, 3:2, 1:3, 3:3 | IronIngot, 2:2 -StickyPiston = Piston, * | SlimeBall, * +Piston = Planks^-1, 1:1, 2:1, 3:1 | RedstoneDust, 2:3 | Cobblestone, 1:2, 3:2, 1:3, 3:3 | IronIngot, 2:2 +StickyPiston = Piston, * | SlimeBall, * RedstoneLamp = RedstoneDust, 2:1, 1:2, 3:2, 2:3 | Glowstone, 2:2 -tripwirehook, 2 = planks, 2:3 | stick, 2:2 | ironbar, 2:1 +TripwireHook, 2 = Planks^-1, 2:3 | stick, 2:2 | IronIngot, 2:1 @@ -314,7 +317,7 @@ tripwirehook, 2 = planks, 2:3 | stick, 2:2 | ironbar, 2:1 #******************************************************# # Food # -Bowl, 4 = Planks, 1:1, 2:2, 3:1 +Bowl, 4 = Planks^-1, 1:1, 2:2, 3:1 MushroomStew = Bowl, * | BrownMushroom, * | RedMushroom, * Bread = Wheat, 1:1, 2:1, 3:1 Sugar = Sugarcane, * @@ -349,9 +352,9 @@ Coal, 9 = CoalBlock, * Clay, 4 = ClayBlock, * SlimeBall, 9 = SlimeBlock, * -Painting = Stick, 1:1, 1:2, 1:3, 2:1, 2:3, 3:1, 3:2, 3:3 | Wool, 2:2 +Painting = Stick, 1:1, 1:2, 1:3, 2:1, 2:3, 3:1, 3:2, 3:3 | Wool^-1, 2:2 ItemFrame = Stick, 1:1, 1:2, 1:3, 2:1, 2:3, 3:1, 3:2, 3:3 | Leather, 2:2 -Sign, 3 = Planks, 1:1, 2:1, 3:1, 1:2, 2:2, 3:2 | Stick, 2:3 +Sign, 3 = Planks^-1, 1:1, 2:1, 3:1, 1:2, 2:2, 3:2 | Stick, 2:3 Ladder, 3 = Stick, 1:1, 3:1, 1:2, 2:2, 3:2, 1:3, 3:3 GlassPane, 16 = Glass, 1:1, 2:1, 3:1, 1:2, 2:2, 3:2 IronBars, 16 = IronIngot, 1:1, 2:1, 3:1, 1:2, 2:2, 3:2 @@ -373,7 +376,7 @@ JungleFenceGate = Stick, 1:1, 1:2, 3:1, 3:2 | JunglePlanks, 2:1, 2:2 DarkOakFenceGate = Stick, 1:1, 1:2, 3:1, 3:2 | DarkOakPlanks, 2:1, 2:2 AcaciaFenceGate = Stick, 1:1, 1:2, 3:1, 3:2 | AcaciaPlanks, 2:1, 2:2 FenceGate = Stick, 1:1, 1:2, 3:1, 3:2 | OakPlanks, 2:1, 2:2 -Bed = Planks, 1:2, 2:2, 3:2 | Wool, 1:1, 2:1, 3:1 +Bed = Planks^-1, 1:2, 2:2, 3:2 | Wool^-1, 1:1, 2:1, 3:1 GoldIngot = GoldNugget, 1:1, 1:2, 1:3, 2:1, 2:2, 2:3, 3:1, 3:2, 3:3 EyeOfEnder = EnderPearl, * | BlazePowder, * Beacon = Glass, 1:1, 1:2, 2:1, 3:1, 3:2 | Obsidian, 1:3, 2:3, 3:3 | NetherStar, 2:2 @@ -412,7 +415,7 @@ BlackBanner = Stick, 2:3 | BlackWool, 1:1, 1:2, 2:1, 2:2, 2:1, 2:2 WhiteDye, 3 = Bone, * RedDye, 2 = Rose, * -YellowDye, 2 = Flower, * +YellowDye, 2 = Dandelion, * # Color mixing, duals: OrangeDye, 2 = YellowDye, * | RedDye, * @@ -439,24 +442,23 @@ MagentaDye, 4 = BlueDye, * | WhiteDye, * | RedDye, *, * #******************************************************# # Colored wool: # -WhiteWool = Wool, * | BoneMeal, * -OrangeWool = Wool, * | OrangeDye, * -MagentaWool = Wool, * | MagentaDye, * -LightBlueWool = Wool, * | LightBlueDye, * -YellowWool = Wool, * | YellowDye, * -LimeWool = Wool, * | LimeDye, * -PinkWool = Wool, * | PinkDye, * -GrayWool = Wool, * | GrayDye, * -LightGrayWool = Wool, * | LightGrayDye, * -CyanWool = Wool, * | CyanDye, * -PurpleWool = Wool, * | PurpleDye, * -BlueWool = Wool, * | BlueDye, * -BrownWool = Wool, * | BrownDye, * -GreenWool = Wool, * | GreenDye, * -RedWool = Wool, * | RedDye, * -BlackWool = Wool, * | BlackDye, * +WhiteWool = Wool^-1, * | BoneMeal, * +OrangeWool = WhiteWool, * | OrangeDye, * +MagentaWool = WhiteWool, * | MagentaDye, * +LightBlueWool = WhiteWool, * | LightBlueDye, * +YellowWool = WhiteWool, * | YellowDye, * +LimeWool = WhiteWool, * | LimeDye, * +PinkWool = WhiteWool, * | PinkDye, * +GrayWool = WhiteWool, * | GrayDye, * +LightGrayWool = WhiteWool, * | LightGrayDye, * +CyanWool = WhiteWool, * | CyanDye, * +PurpleWool = WhiteWool, * | PurpleDye, * +BlueWool = WhiteWool, * | BlueDye, * +BrownWool = WhiteWool, * | BrownDye, * +GreenWool = WhiteWool, * | GreenDye, * +RedWool = WhiteWool, * | RedDye, * +BlackWool = WhiteWool, * | BlackDye, * -WhiteCarpet, 3 = WhiteWool, 1:1, 2:1 OrangeCarpet, 3 = OrangeWool, 1:1, 2:1 MagentaCarpet, 3 = MagentaWool, 1:1, 2:1 LightBlueCarpet, 3 = LightBlueWool, 1:1, 2:1 @@ -472,11 +474,11 @@ BrownCarpet, 3 = BrownWool, 1:1, 2:1 GreenCarpet, 3 = GreenWool, 1:1, 2:1 RedCarpet, 3 = RedWool, 1:1, 2:2 BlackCarpet, 3 = BlackWool, 1:1, 2:1 +WhiteCarpet, 3 = WhiteWool, 1:1, 2:1 #******************************************************# # Stained Glass: # -WhiteStainedGlass, 8 = Glass, 1:1, 1:2, 1:3, 2:1, 2:3, 3:1, 3:2, 3:3 | BoneMeal, 2:2 OrangeStainedGlass, 8 = Glass, 1:1, 1:2, 1:3, 2:1, 2:3, 3:1, 3:2, 3:3 | OrangeDye, 2:2 MagentaStainedGlass, 8 = Glass, 1:1, 1:2, 1:3, 2:1, 2:3, 3:1, 3:2, 3:3 | MagentaDye, 2:2 LightBlueStainedGlass, 8 = Glass, 1:1, 1:2, 1:3, 2:1, 2:3, 3:1, 3:2, 3:3 | LightBlueDye, 2:2 @@ -492,11 +494,11 @@ BrownStainedGlass, 8 = Glass, 1:1, 1:2, 1:3, 2:1, 2:3, 3:1, 3:2, 3:3 | Brown GreenStainedGlass, 8 = Glass, 1:1, 1:2, 1:3, 2:1, 2:3, 3:1, 3:2, 3:3 | GreenDye, 2:2 RedStainedGlass, 8 = Glass, 1:1, 1:2, 1:3, 2:1, 2:3, 3:1, 3:2, 3:3 | RedDye, 2:2 BlackStainedGlass, 8 = Glass, 1:1, 1:2, 1:3, 2:1, 2:3, 3:1, 3:2, 3:3 | BlackDye, 2:2 +WhiteStainedGlass, 8 = Glass, 1:1, 1:2, 1:3, 2:1, 2:3, 3:1, 3:2, 3:3 | BoneMeal, 2:2 #******************************************************# # Stained Glass Pane: # -WhiteStainedGlassPane, 16 = WhiteStainedGlass, 1:2, 1:3, 2:2, 2:3, 3:2, 3:3 OrangeStainedGlassPane, 16 = OrangeStainedGlass, 1:2, 1:3, 2:2, 2:3, 3:2, 3:3 MagentaStainedGlassPane, 16 = MagentaStainedGlass, 1:2, 1:3, 2:2, 2:3, 3:2, 3:3 LightBlueStainedGlassPane, 16 = LightBlueStainedGlass, 1:2, 1:3, 2:2, 2:3, 3:2, 3:3 @@ -512,7 +514,27 @@ BrownStainedGlassPane, 16 = BrownStainedGlass, 1:2, 1:3, 2:2, 2:3, 3:2, 3:3 GreenStainedGlassPane, 16 = GreenStainedGlass, 1:2, 1:3, 2:2, 2:3, 3:2, 3:3 RedStainedGlassPane, 16 = RedStainedGlass, 1:2, 1:3, 2:2, 2:3, 3:2, 3:3 BlackStainedGlassPane , 16 = BlackStainedGlass, 1:2, 1:3, 2:2, 2:3, 3:2, 3:3 +WhiteStainedGlassPane, 16 = WhiteStainedGlass, 1:2, 1:3, 2:2, 2:3, 3:2, 3:3 +#******************************************************# +# Stained Clay: +# +WhiteStainedClay, 8 = HardenedClay, 1:1, 1:2, 1:3, 2:1, 2:3, 3:1, 3:2, 3:3 | BoneMeal, 2:2 +OrangeStainedClay, 8 = HardenedClay, 1:1, 1:2, 1:3, 2:1, 2:3, 3:1, 3:2, 3:3 | OrangeDye, 2:2 +MagentaStainedClay, 8 = HardenedClay, 1:1, 1:2, 1:3, 2:1, 2:3, 3:1, 3:2, 3:3 | MagentaDye, 2:2 +LightBlueStainedClay, 8 = HardenedClay, 1:1, 1:2, 1:3, 2:1, 2:3, 3:1, 3:2, 3:3 | LightBlueDye, 2:2 +YellowStainedClay, 8 = HardenedClay, 1:1, 1:2, 1:3, 2:1, 2:3, 3:1, 3:2, 3:3 | YellowDye, 2:2 +LimeStainedClay, 8 = HardenedClay, 1:1, 1:2, 1:3, 2:1, 2:3, 3:1, 3:2, 3:3 | LimeDye, 2:2 +PinkStainedClay, 8 = HardenedClay, 1:1, 1:2, 1:3, 2:1, 2:3, 3:1, 3:2, 3:3 | PinkDye, 2:2 +GrayStainedClay, 8 = HardenedClay, 1:1, 1:2, 1:3, 2:1, 2:3, 3:1, 3:2, 3:3 | GrayDye, 2:2 +LightGrayStainedClay, 8 = HardenedClay, 1:1, 1:2, 1:3, 2:1, 2:3, 3:1, 3:2, 3:3 | LightGrayDye, 2:2 +CyanStainedClay, 8 = HardenedClay, 1:1, 1:2, 1:3, 2:1, 2:3, 3:1, 3:2, 3:3 | CyanDye, 2:2 +VioletStainedClay, 8 = HardenedClay, 1:1, 1:2, 1:3, 2:1, 2:3, 3:1, 3:2, 3:3 | VioletDye, 2:2 +BlueStainedClay, 8 = HardenedClay, 1:1, 1:2, 1:3, 2:1, 2:3, 3:1, 3:2, 3:3 | BlueDye, 2:2 +BrownStainedClay, 8 = HardenedClay, 1:1, 1:2, 1:3, 2:1, 2:3, 3:1, 3:2, 3:3 | BrownDye, 2:2 +GreenStainedClay, 8 = HardenedClay, 1:1, 1:2, 1:3, 2:1, 2:3, 3:1, 3:2, 3:3 | GreenDye, 2:2 +RedStainedClay, 8 = HardenedClay, 1:1, 1:2, 1:3, 2:1, 2:3, 3:1, 3:2, 3:3 | RedDye, 2:2 +BlackStainedClay, 8 = HardenedClay, 1:1, 1:2, 1:3, 2:1, 2:3, 3:1, 3:2, 3:3 | BlackDye, 2:2 #******************************************************# # Enchantment & Brewing 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..2e89836af 100644 --- a/MCServer/webadmin/template.lua +++ b/MCServer/webadmin/template.lua @@ -26,24 +26,23 @@ function GetDefaultPage() 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 .. " (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>"; @@ -102,9 +101,8 @@ function ShowPage(WebAdmin, TemplateRequest) <div class="upper"> <div class="wrapper"> <ul class="menu top_links"> - <li><a>Server Name: <strong>]] .. cRoot:Get():GetServer():GetServerID() .. [[</strong></a></li> <li><a>Players online: <strong>]] .. NumPlayers .. [[</strong></a></li> - <li><a>Memory: <strong>]] .. MemoryUsageKiB / 1024 .. [[MB</strong></a></li> + <li><a>Memory: <strong>]] .. string.format("%.2f", MemoryUsageKiB / 1024) .. [[MB</strong></a></li> <li><a>Chunks: <strong>]] .. NumChunks .. [[</strong></a></li> </ul> <div class="welcome"><strong>Welcome back, ]] .. TemplateRequest.Request.Username .. [[</strong> <a href=".././"><img src="/log_out.png" style="vertical-align:bottom;"> Log Out</a></div> 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 0a2a8411d..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); @@ -208,6 +226,10 @@ void cPluginLua::OnDisable(void) void cPluginLua::Tick(float a_Dt) { cCSLock Lock(m_CriticalSection); + if (!m_LuaState.IsValid()) + { + return; + } cLuaRefs & Refs = m_HookMap[cPluginManager::HOOK_TICK]; for (cLuaRefs::iterator itr = Refs.begin(), end = Refs.end(); itr != end; ++itr) { @@ -222,6 +244,10 @@ void cPluginLua::Tick(float a_Dt) bool cPluginLua::OnBlockSpread(cWorld & a_World, int a_BlockX, int a_BlockY, int a_BlockZ, eSpreadSource a_Source) { cCSLock Lock(m_CriticalSection); + if (!m_LuaState.IsValid()) + { + return false; + } bool res = false; cLuaRefs & Refs = m_HookMap[cPluginManager::HOOK_BLOCK_SPREAD]; for (cLuaRefs::iterator itr = Refs.begin(), end = Refs.end(); itr != end; ++itr) @@ -242,6 +268,10 @@ bool cPluginLua::OnBlockSpread(cWorld & a_World, int a_BlockX, int a_BlockY, int bool cPluginLua::OnBlockToPickups(cWorld & a_World, cEntity * a_Digger, int a_BlockX, int a_BlockY, int a_BlockZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta, cItems & a_Pickups) { cCSLock Lock(m_CriticalSection); + if (!m_LuaState.IsValid()) + { + return false; + } bool res = false; cLuaRefs & Refs = m_HookMap[cPluginManager::HOOK_BLOCK_TO_PICKUPS]; for (cLuaRefs::iterator itr = Refs.begin(), end = Refs.end(); itr != end; ++itr) @@ -262,6 +292,10 @@ bool cPluginLua::OnBlockToPickups(cWorld & a_World, cEntity * a_Digger, int a_Bl bool cPluginLua::OnChat(cPlayer & a_Player, AString & a_Message) { cCSLock Lock(m_CriticalSection); + if (!m_LuaState.IsValid()) + { + return false; + } bool res = false; cLuaRefs & Refs = m_HookMap[cPluginManager::HOOK_CHAT]; for (cLuaRefs::iterator itr = Refs.begin(), end = Refs.end(); itr != end; ++itr) @@ -282,6 +316,10 @@ bool cPluginLua::OnChat(cPlayer & a_Player, AString & a_Message) bool cPluginLua::OnChunkAvailable(cWorld & a_World, int a_ChunkX, int a_ChunkZ) { cCSLock Lock(m_CriticalSection); + if (!m_LuaState.IsValid()) + { + return false; + } bool res = false; cLuaRefs & Refs = m_HookMap[cPluginManager::HOOK_CHUNK_AVAILABLE]; for (cLuaRefs::iterator itr = Refs.begin(), end = Refs.end(); itr != end; ++itr) @@ -302,6 +340,10 @@ bool cPluginLua::OnChunkAvailable(cWorld & a_World, int a_ChunkX, int a_ChunkZ) bool cPluginLua::OnChunkGenerated(cWorld & a_World, int a_ChunkX, int a_ChunkZ, cChunkDesc * a_ChunkDesc) { cCSLock Lock(m_CriticalSection); + if (!m_LuaState.IsValid()) + { + return false; + } bool res = false; cLuaRefs & Refs = m_HookMap[cPluginManager::HOOK_CHUNK_GENERATED]; for (cLuaRefs::iterator itr = Refs.begin(), end = Refs.end(); itr != end; ++itr) @@ -322,6 +364,10 @@ bool cPluginLua::OnChunkGenerated(cWorld & a_World, int a_ChunkX, int a_ChunkZ, bool cPluginLua::OnChunkGenerating(cWorld & a_World, int a_ChunkX, int a_ChunkZ, cChunkDesc * a_ChunkDesc) { cCSLock Lock(m_CriticalSection); + if (!m_LuaState.IsValid()) + { + return false; + } bool res = false; cLuaRefs & Refs = m_HookMap[cPluginManager::HOOK_CHUNK_GENERATING]; for (cLuaRefs::iterator itr = Refs.begin(), end = Refs.end(); itr != end; ++itr) @@ -342,6 +388,10 @@ bool cPluginLua::OnChunkGenerating(cWorld & a_World, int a_ChunkX, int a_ChunkZ, bool cPluginLua::OnChunkUnloaded(cWorld & a_World, int a_ChunkX, int a_ChunkZ) { cCSLock Lock(m_CriticalSection); + if (!m_LuaState.IsValid()) + { + return false; + } bool res = false; cLuaRefs & Refs = m_HookMap[cPluginManager::HOOK_CHUNK_UNLOADED]; for (cLuaRefs::iterator itr = Refs.begin(), end = Refs.end(); itr != end; ++itr) @@ -362,6 +412,10 @@ bool cPluginLua::OnChunkUnloaded(cWorld & a_World, int a_ChunkX, int a_ChunkZ) bool cPluginLua::OnChunkUnloading(cWorld & a_World, int a_ChunkX, int a_ChunkZ) { cCSLock Lock(m_CriticalSection); + if (!m_LuaState.IsValid()) + { + return false; + } bool res = false; cLuaRefs & Refs = m_HookMap[cPluginManager::HOOK_CHUNK_UNLOADING]; for (cLuaRefs::iterator itr = Refs.begin(), end = Refs.end(); itr != end; ++itr) @@ -382,6 +436,10 @@ bool cPluginLua::OnChunkUnloading(cWorld & a_World, int a_ChunkX, int a_ChunkZ) bool cPluginLua::OnCollectingPickup(cPlayer & a_Player, cPickup & a_Pickup) { cCSLock Lock(m_CriticalSection); + if (!m_LuaState.IsValid()) + { + return false; + } bool res = false; cLuaRefs & Refs = m_HookMap[cPluginManager::HOOK_COLLECTING_PICKUP]; for (cLuaRefs::iterator itr = Refs.begin(), end = Refs.end(); itr != end; ++itr) @@ -402,6 +460,10 @@ bool cPluginLua::OnCollectingPickup(cPlayer & a_Player, cPickup & a_Pickup) bool cPluginLua::OnCraftingNoRecipe(cPlayer & a_Player, cCraftingGrid & a_Grid, cCraftingRecipe & a_Recipe) { cCSLock Lock(m_CriticalSection); + if (!m_LuaState.IsValid()) + { + return false; + } bool res = false; cLuaRefs & Refs = m_HookMap[cPluginManager::HOOK_CRAFTING_NO_RECIPE]; for (cLuaRefs::iterator itr = Refs.begin(), end = Refs.end(); itr != end; ++itr) @@ -422,6 +484,10 @@ bool cPluginLua::OnCraftingNoRecipe(cPlayer & a_Player, cCraftingGrid & a_Grid, bool cPluginLua::OnDisconnect(cClientHandle & a_Client, const AString & a_Reason) { cCSLock Lock(m_CriticalSection); + if (!m_LuaState.IsValid()) + { + return false; + } bool res = false; cLuaRefs & Refs = m_HookMap[cPluginManager::HOOK_DISCONNECT]; for (cLuaRefs::iterator itr = Refs.begin(), end = Refs.end(); itr != end; ++itr) @@ -442,6 +508,10 @@ bool cPluginLua::OnDisconnect(cClientHandle & a_Client, const AString & a_Reason bool cPluginLua::OnEntityAddEffect(cEntity & a_Entity, int a_EffectType, int a_EffectDurationTicks, int a_EffectIntensity, double a_DistanceModifier) { cCSLock Lock(m_CriticalSection); + if (!m_LuaState.IsValid()) + { + return false; + } bool res = false; cLuaRefs & Refs = m_HookMap[cPluginManager::HOOK_ENTITY_ADD_EFFECT]; for (cLuaRefs::iterator itr = Refs.begin(), end = Refs.end(); itr != end; ++itr) @@ -462,6 +532,10 @@ bool cPluginLua::OnEntityAddEffect(cEntity & a_Entity, int a_EffectType, int a_E bool cPluginLua::OnExecuteCommand(cPlayer * a_Player, const AStringVector & a_Split) { cCSLock Lock(m_CriticalSection); + if (!m_LuaState.IsValid()) + { + return false; + } bool res = false; cLuaRefs & Refs = m_HookMap[cPluginManager::HOOK_EXECUTE_COMMAND]; for (cLuaRefs::iterator itr = Refs.begin(), end = Refs.end(); itr != end; ++itr) @@ -482,6 +556,10 @@ bool cPluginLua::OnExecuteCommand(cPlayer * a_Player, const AStringVector & a_Sp bool cPluginLua::OnExploded(cWorld & a_World, double a_ExplosionSize, bool a_CanCauseFire, double a_X, double a_Y, double a_Z, eExplosionSource a_Source, void * a_SourceData) { cCSLock Lock(m_CriticalSection); + if (!m_LuaState.IsValid()) + { + return false; + } bool res = false; cLuaRefs & Refs = m_HookMap[cPluginManager::HOOK_EXPLODED]; for (cLuaRefs::iterator itr = Refs.begin(), end = Refs.end(); itr != end; ++itr) @@ -519,6 +597,10 @@ bool cPluginLua::OnExploded(cWorld & a_World, double a_ExplosionSize, bool a_Can bool cPluginLua::OnExploding(cWorld & a_World, double & a_ExplosionSize, bool & a_CanCauseFire, double a_X, double a_Y, double a_Z, eExplosionSource a_Source, void * a_SourceData) { cCSLock Lock(m_CriticalSection); + if (!m_LuaState.IsValid()) + { + return false; + } bool res = false; cLuaRefs & Refs = m_HookMap[cPluginManager::HOOK_EXPLODING]; for (cLuaRefs::iterator itr = Refs.begin(), end = Refs.end(); itr != end; ++itr) @@ -556,6 +638,10 @@ bool cPluginLua::OnExploding(cWorld & a_World, double & a_ExplosionSize, bool & bool cPluginLua::OnHandshake(cClientHandle & a_Client, const AString & a_Username) { cCSLock Lock(m_CriticalSection); + if (!m_LuaState.IsValid()) + { + return false; + } bool res = false; cLuaRefs & Refs = m_HookMap[cPluginManager::HOOK_HANDSHAKE]; for (cLuaRefs::iterator itr = Refs.begin(), end = Refs.end(); itr != end; ++itr) @@ -576,8 +662,11 @@ bool cPluginLua::OnHandshake(cClientHandle & a_Client, const AString & a_Usernam bool cPluginLua::OnHopperPullingItem(cWorld & a_World, cHopperEntity & a_Hopper, int a_DstSlotNum, cBlockEntityWithItems & a_SrcEntity, int a_SrcSlotNum) { cCSLock Lock(m_CriticalSection); + if (!m_LuaState.IsValid()) + { + return false; + } bool res = false; - cLuaRefs & Refs = m_HookMap[cPluginManager::HOOK_HOPPER_PULLING_ITEM]; for (cLuaRefs::iterator itr = Refs.begin(), end = Refs.end(); itr != end; ++itr) { @@ -597,6 +686,10 @@ bool cPluginLua::OnHopperPullingItem(cWorld & a_World, cHopperEntity & a_Hopper, bool cPluginLua::OnHopperPushingItem(cWorld & a_World, cHopperEntity & a_Hopper, int a_SrcSlotNum, cBlockEntityWithItems & a_DstEntity, int a_DstSlotNum) { cCSLock Lock(m_CriticalSection); + if (!m_LuaState.IsValid()) + { + return false; + } bool res = false; cLuaRefs & Refs = m_HookMap[cPluginManager::HOOK_HOPPER_PUSHING_ITEM]; for (cLuaRefs::iterator itr = Refs.begin(), end = Refs.end(); itr != end; ++itr) @@ -617,6 +710,10 @@ bool cPluginLua::OnHopperPushingItem(cWorld & a_World, cHopperEntity & a_Hopper, bool cPluginLua::OnKilling(cEntity & a_Victim, cEntity * a_Killer, TakeDamageInfo & a_TDI) { cCSLock Lock(m_CriticalSection); + if (!m_LuaState.IsValid()) + { + return false; + } bool res = false; cLuaRefs & Refs = m_HookMap[cPluginManager::HOOK_KILLING]; for (cLuaRefs::iterator itr = Refs.begin(), end = Refs.end(); itr != end; ++itr) @@ -637,6 +734,10 @@ bool cPluginLua::OnKilling(cEntity & a_Victim, cEntity * a_Killer, TakeDamageInf bool cPluginLua::OnLogin(cClientHandle & a_Client, int a_ProtocolVersion, const AString & a_Username) { cCSLock Lock(m_CriticalSection); + if (!m_LuaState.IsValid()) + { + return false; + } bool res = false; cLuaRefs & Refs = m_HookMap[cPluginManager::HOOK_LOGIN]; for (cLuaRefs::iterator itr = Refs.begin(), end = Refs.end(); itr != end; ++itr) @@ -657,6 +758,10 @@ bool cPluginLua::OnLogin(cClientHandle & a_Client, int a_ProtocolVersion, const bool cPluginLua::OnPlayerAnimation(cPlayer & a_Player, int a_Animation) { cCSLock Lock(m_CriticalSection); + if (!m_LuaState.IsValid()) + { + return false; + } bool res = false; cLuaRefs & Refs = m_HookMap[cPluginManager::HOOK_PLAYER_ANIMATION]; for (cLuaRefs::iterator itr = Refs.begin(), end = Refs.end(); itr != end; ++itr) @@ -677,6 +782,10 @@ bool cPluginLua::OnPlayerAnimation(cPlayer & a_Player, int a_Animation) bool cPluginLua::OnPlayerBreakingBlock(cPlayer & a_Player, int a_BlockX, int a_BlockY, int a_BlockZ, char a_BlockFace, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta) { cCSLock Lock(m_CriticalSection); + if (!m_LuaState.IsValid()) + { + return false; + } bool res = false; cLuaRefs & Refs = m_HookMap[cPluginManager::HOOK_PLAYER_BREAKING_BLOCK]; for (cLuaRefs::iterator itr = Refs.begin(), end = Refs.end(); itr != end; ++itr) @@ -697,6 +806,10 @@ bool cPluginLua::OnPlayerBreakingBlock(cPlayer & a_Player, int a_BlockX, int a_B bool cPluginLua::OnPlayerBrokenBlock(cPlayer & a_Player, int a_BlockX, int a_BlockY, int a_BlockZ, char a_BlockFace, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta) { cCSLock Lock(m_CriticalSection); + if (!m_LuaState.IsValid()) + { + return false; + } bool res = false; cLuaRefs & Refs = m_HookMap[cPluginManager::HOOK_PLAYER_BROKEN_BLOCK]; for (cLuaRefs::iterator itr = Refs.begin(), end = Refs.end(); itr != end; ++itr) @@ -717,6 +830,10 @@ bool cPluginLua::OnPlayerBrokenBlock(cPlayer & a_Player, int a_BlockX, int a_Blo bool cPluginLua::OnPlayerDestroyed(cPlayer & a_Player) { cCSLock Lock(m_CriticalSection); + if (!m_LuaState.IsValid()) + { + return false; + } bool res = false; cLuaRefs & Refs = m_HookMap[cPluginManager::HOOK_PLAYER_DESTROYED]; for (cLuaRefs::iterator itr = Refs.begin(), end = Refs.end(); itr != end; ++itr) @@ -737,6 +854,10 @@ bool cPluginLua::OnPlayerDestroyed(cPlayer & a_Player) bool cPluginLua::OnPlayerEating(cPlayer & a_Player) { cCSLock Lock(m_CriticalSection); + if (!m_LuaState.IsValid()) + { + return false; + } bool res = false; cLuaRefs & Refs = m_HookMap[cPluginManager::HOOK_PLAYER_EATING]; for (cLuaRefs::iterator itr = Refs.begin(), end = Refs.end(); itr != end; ++itr) @@ -757,6 +878,10 @@ bool cPluginLua::OnPlayerEating(cPlayer & a_Player) bool cPluginLua::OnPlayerFoodLevelChange(cPlayer & a_Player, int a_NewFoodLevel) { cCSLock Lock(m_CriticalSection); + if (!m_LuaState.IsValid()) + { + return false; + } bool res = false; cLuaRefs & Refs = m_HookMap[cPluginManager::HOOK_PLAYER_FOOD_LEVEL_CHANGE]; for (cLuaRefs::iterator itr = Refs.begin(), end = Refs.end(); itr != end; ++itr) @@ -777,6 +902,10 @@ bool cPluginLua::OnPlayerFoodLevelChange(cPlayer & a_Player, int a_NewFoodLevel) bool cPluginLua::OnPlayerFished(cPlayer & a_Player, const cItems & a_Reward) { cCSLock Lock(m_CriticalSection); + if (!m_LuaState.IsValid()) + { + return false; + } bool res = false; cLuaRefs & Refs = m_HookMap[cPluginManager::HOOK_PLAYER_FISHED]; for (cLuaRefs::iterator itr = Refs.begin(), end = Refs.end(); itr != end; ++itr) @@ -797,6 +926,10 @@ bool cPluginLua::OnPlayerFished(cPlayer & a_Player, const cItems & a_Reward) bool cPluginLua::OnPlayerFishing(cPlayer & a_Player, cItems & a_Reward) { cCSLock Lock(m_CriticalSection); + if (!m_LuaState.IsValid()) + { + return false; + } bool res = false; cLuaRefs & Refs = m_HookMap[cPluginManager::HOOK_PLAYER_FISHING]; for (cLuaRefs::iterator itr = Refs.begin(), end = Refs.end(); itr != end; ++itr) @@ -817,6 +950,10 @@ bool cPluginLua::OnPlayerFishing(cPlayer & a_Player, cItems & a_Reward) bool cPluginLua::OnPlayerJoined(cPlayer & a_Player) { cCSLock Lock(m_CriticalSection); + if (!m_LuaState.IsValid()) + { + return false; + } bool res = false; cLuaRefs & Refs = m_HookMap[cPluginManager::HOOK_PLAYER_JOINED]; for (cLuaRefs::iterator itr = Refs.begin(), end = Refs.end(); itr != end; ++itr) @@ -837,6 +974,10 @@ bool cPluginLua::OnPlayerJoined(cPlayer & a_Player) bool cPluginLua::OnPlayerLeftClick(cPlayer & a_Player, int a_BlockX, int a_BlockY, int a_BlockZ, char a_BlockFace, char a_Status) { cCSLock Lock(m_CriticalSection); + if (!m_LuaState.IsValid()) + { + return false; + } bool res = false; cLuaRefs & Refs = m_HookMap[cPluginManager::HOOK_PLAYER_LEFT_CLICK]; for (cLuaRefs::iterator itr = Refs.begin(), end = Refs.end(); itr != end; ++itr) @@ -857,6 +998,10 @@ bool cPluginLua::OnPlayerLeftClick(cPlayer & a_Player, int a_BlockX, int a_Block bool cPluginLua::OnPlayerMoving(cPlayer & a_Player, const Vector3d & a_OldPosition, const Vector3d & a_NewPosition) { cCSLock Lock(m_CriticalSection); + if (!m_LuaState.IsValid()) + { + return false; + } bool res = false; cLuaRefs & Refs = m_HookMap[cPluginManager::HOOK_PLAYER_MOVING]; for (cLuaRefs::iterator itr = Refs.begin(), end = Refs.end(); itr != end; ++itr) @@ -877,6 +1022,10 @@ bool cPluginLua::OnPlayerMoving(cPlayer & a_Player, const Vector3d & a_OldPositi bool cPluginLua::OnEntityTeleport(cEntity & a_Entity, const Vector3d & a_OldPosition, const Vector3d & a_NewPosition) { cCSLock Lock(m_CriticalSection); + if (!m_LuaState.IsValid()) + { + return false; + } bool res = false; cLuaRefs & Refs = m_HookMap[cPluginManager::HOOK_ENTITY_TELEPORT]; for (cLuaRefs::iterator itr = Refs.begin(), end = Refs.end(); itr != end; ++itr) @@ -897,6 +1046,10 @@ bool cPluginLua::OnEntityTeleport(cEntity & a_Entity, const Vector3d & a_OldPosi bool cPluginLua::OnPlayerPlacedBlock(cPlayer & a_Player, const sSetBlock & a_BlockChange) { cCSLock Lock(m_CriticalSection); + if (!m_LuaState.IsValid()) + { + return false; + } bool res = false; cLuaRefs & Refs = m_HookMap[cPluginManager::HOOK_PLAYER_PLACED_BLOCK]; for (cLuaRefs::iterator itr = Refs.begin(), end = Refs.end(); itr != end; ++itr) @@ -922,6 +1075,10 @@ bool cPluginLua::OnPlayerPlacedBlock(cPlayer & a_Player, const sSetBlock & a_Blo bool cPluginLua::OnPlayerPlacingBlock(cPlayer & a_Player, const sSetBlock & a_BlockChange) { cCSLock Lock(m_CriticalSection); + if (!m_LuaState.IsValid()) + { + return false; + } bool res = false; cLuaRefs & Refs = m_HookMap[cPluginManager::HOOK_PLAYER_PLACING_BLOCK]; for (cLuaRefs::iterator itr = Refs.begin(), end = Refs.end(); itr != end; ++itr) @@ -947,6 +1104,10 @@ bool cPluginLua::OnPlayerPlacingBlock(cPlayer & a_Player, const sSetBlock & a_Bl bool cPluginLua::OnPlayerRightClick(cPlayer & a_Player, int a_BlockX, int a_BlockY, int a_BlockZ, char a_BlockFace, int a_CursorX, int a_CursorY, int a_CursorZ) { cCSLock Lock(m_CriticalSection); + if (!m_LuaState.IsValid()) + { + return false; + } bool res = false; cLuaRefs & Refs = m_HookMap[cPluginManager::HOOK_PLAYER_RIGHT_CLICK]; for (cLuaRefs::iterator itr = Refs.begin(), end = Refs.end(); itr != end; ++itr) @@ -967,6 +1128,10 @@ bool cPluginLua::OnPlayerRightClick(cPlayer & a_Player, int a_BlockX, int a_Bloc bool cPluginLua::OnPlayerRightClickingEntity(cPlayer & a_Player, cEntity & a_Entity) { cCSLock Lock(m_CriticalSection); + if (!m_LuaState.IsValid()) + { + return false; + } bool res = false; cLuaRefs & Refs = m_HookMap[cPluginManager::HOOK_PLAYER_RIGHT_CLICKING_ENTITY]; for (cLuaRefs::iterator itr = Refs.begin(), end = Refs.end(); itr != end; ++itr) @@ -987,6 +1152,10 @@ bool cPluginLua::OnPlayerRightClickingEntity(cPlayer & a_Player, cEntity & a_Ent bool cPluginLua::OnPlayerShooting(cPlayer & a_Player) { cCSLock Lock(m_CriticalSection); + if (!m_LuaState.IsValid()) + { + return false; + } bool res = false; cLuaRefs & Refs = m_HookMap[cPluginManager::HOOK_PLAYER_SHOOTING]; for (cLuaRefs::iterator itr = Refs.begin(), end = Refs.end(); itr != end; ++itr) @@ -1007,6 +1176,10 @@ bool cPluginLua::OnPlayerShooting(cPlayer & a_Player) bool cPluginLua::OnPlayerSpawned(cPlayer & a_Player) { cCSLock Lock(m_CriticalSection); + if (!m_LuaState.IsValid()) + { + return false; + } bool res = false; cLuaRefs & Refs = m_HookMap[cPluginManager::HOOK_PLAYER_SPAWNED]; for (cLuaRefs::iterator itr = Refs.begin(), end = Refs.end(); itr != end; ++itr) @@ -1027,6 +1200,10 @@ bool cPluginLua::OnPlayerSpawned(cPlayer & a_Player) bool cPluginLua::OnPlayerTossingItem(cPlayer & a_Player) { cCSLock Lock(m_CriticalSection); + if (!m_LuaState.IsValid()) + { + return false; + } bool res = false; cLuaRefs & Refs = m_HookMap[cPluginManager::HOOK_PLAYER_TOSSING_ITEM]; for (cLuaRefs::iterator itr = Refs.begin(), end = Refs.end(); itr != end; ++itr) @@ -1047,6 +1224,10 @@ bool cPluginLua::OnPlayerTossingItem(cPlayer & a_Player) bool cPluginLua::OnPlayerUsedBlock(cPlayer & a_Player, int a_BlockX, int a_BlockY, int a_BlockZ, char a_BlockFace, int a_CursorX, int a_CursorY, int a_CursorZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta) { cCSLock Lock(m_CriticalSection); + if (!m_LuaState.IsValid()) + { + return false; + } bool res = false; cLuaRefs & Refs = m_HookMap[cPluginManager::HOOK_PLAYER_USED_BLOCK]; for (cLuaRefs::iterator itr = Refs.begin(), end = Refs.end(); itr != end; ++itr) @@ -1067,6 +1248,10 @@ bool cPluginLua::OnPlayerUsedBlock(cPlayer & a_Player, int a_BlockX, int a_Block bool cPluginLua::OnPlayerUsedItem(cPlayer & a_Player, int a_BlockX, int a_BlockY, int a_BlockZ, char a_BlockFace, int a_CursorX, int a_CursorY, int a_CursorZ) { cCSLock Lock(m_CriticalSection); + if (!m_LuaState.IsValid()) + { + return false; + } bool res = false; cLuaRefs & Refs = m_HookMap[cPluginManager::HOOK_PLAYER_USED_ITEM]; for (cLuaRefs::iterator itr = Refs.begin(), end = Refs.end(); itr != end; ++itr) @@ -1087,6 +1272,10 @@ bool cPluginLua::OnPlayerUsedItem(cPlayer & a_Player, int a_BlockX, int a_BlockY bool cPluginLua::OnPlayerUsingBlock(cPlayer & a_Player, int a_BlockX, int a_BlockY, int a_BlockZ, char a_BlockFace, int a_CursorX, int a_CursorY, int a_CursorZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta) { cCSLock Lock(m_CriticalSection); + if (!m_LuaState.IsValid()) + { + return false; + } bool res = false; cLuaRefs & Refs = m_HookMap[cPluginManager::HOOK_PLAYER_USING_BLOCK]; for (cLuaRefs::iterator itr = Refs.begin(), end = Refs.end(); itr != end; ++itr) @@ -1107,6 +1296,10 @@ bool cPluginLua::OnPlayerUsingBlock(cPlayer & a_Player, int a_BlockX, int a_Bloc bool cPluginLua::OnPlayerUsingItem(cPlayer & a_Player, int a_BlockX, int a_BlockY, int a_BlockZ, char a_BlockFace, int a_CursorX, int a_CursorY, int a_CursorZ) { cCSLock Lock(m_CriticalSection); + if (!m_LuaState.IsValid()) + { + return false; + } bool res = false; cLuaRefs & Refs = m_HookMap[cPluginManager::HOOK_PLAYER_USING_ITEM]; for (cLuaRefs::iterator itr = Refs.begin(), end = Refs.end(); itr != end; ++itr) @@ -1127,6 +1320,10 @@ bool cPluginLua::OnPlayerUsingItem(cPlayer & a_Player, int a_BlockX, int a_Block bool cPluginLua::OnPluginMessage(cClientHandle & a_Client, const AString & a_Channel, const AString & a_Message) { cCSLock Lock(m_CriticalSection); + if (!m_LuaState.IsValid()) + { + return false; + } bool res = false; cLuaRefs & Refs = m_HookMap[cPluginManager::HOOK_PLUGIN_MESSAGE]; for (cLuaRefs::iterator itr = Refs.begin(), end = Refs.end(); itr != end; ++itr) @@ -1147,6 +1344,10 @@ bool cPluginLua::OnPluginMessage(cClientHandle & a_Client, const AString & a_Cha bool cPluginLua::OnPluginsLoaded(void) { cCSLock Lock(m_CriticalSection); + if (!m_LuaState.IsValid()) + { + return false; + } bool res = false; cLuaRefs & Refs = m_HookMap[cPluginManager::HOOK_PLUGINS_LOADED]; for (cLuaRefs::iterator itr = Refs.begin(), end = Refs.end(); itr != end; ++itr) @@ -1165,6 +1366,10 @@ bool cPluginLua::OnPluginsLoaded(void) bool cPluginLua::OnPostCrafting(cPlayer & a_Player, cCraftingGrid & a_Grid, cCraftingRecipe & a_Recipe) { cCSLock Lock(m_CriticalSection); + if (!m_LuaState.IsValid()) + { + return false; + } bool res = false; cLuaRefs & Refs = m_HookMap[cPluginManager::HOOK_POST_CRAFTING]; for (cLuaRefs::iterator itr = Refs.begin(), end = Refs.end(); itr != end; ++itr) @@ -1185,6 +1390,10 @@ bool cPluginLua::OnPostCrafting(cPlayer & a_Player, cCraftingGrid & a_Grid, cCra bool cPluginLua::OnPreCrafting(cPlayer & a_Player, cCraftingGrid & a_Grid, cCraftingRecipe & a_Recipe) { cCSLock Lock(m_CriticalSection); + if (!m_LuaState.IsValid()) + { + return false; + } bool res = false; cLuaRefs & Refs = m_HookMap[cPluginManager::HOOK_PRE_CRAFTING]; for (cLuaRefs::iterator itr = Refs.begin(), end = Refs.end(); itr != end; ++itr) @@ -1205,6 +1414,10 @@ bool cPluginLua::OnPreCrafting(cPlayer & a_Player, cCraftingGrid & a_Grid, cCraf bool cPluginLua::OnProjectileHitBlock(cProjectileEntity & a_Projectile, int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_Face, const Vector3d & a_BlockHitPos) { cCSLock Lock(m_CriticalSection); + if (!m_LuaState.IsValid()) + { + return false; + } bool res = false; cLuaRefs & Refs = m_HookMap[cPluginManager::HOOK_PROJECTILE_HIT_BLOCK]; for (cLuaRefs::iterator itr = Refs.begin(), end = Refs.end(); itr != end; ++itr) @@ -1225,6 +1438,10 @@ bool cPluginLua::OnProjectileHitBlock(cProjectileEntity & a_Projectile, int a_Bl bool cPluginLua::OnProjectileHitEntity(cProjectileEntity & a_Projectile, cEntity & a_HitEntity) { cCSLock Lock(m_CriticalSection); + if (!m_LuaState.IsValid()) + { + return false; + } bool res = false; cLuaRefs & Refs = m_HookMap[cPluginManager::HOOK_PROJECTILE_HIT_ENTITY]; for (cLuaRefs::iterator itr = Refs.begin(), end = Refs.end(); itr != end; ++itr) @@ -1245,6 +1462,10 @@ bool cPluginLua::OnProjectileHitEntity(cProjectileEntity & a_Projectile, cEntity bool cPluginLua::OnServerPing(cClientHandle & a_ClientHandle, AString & a_ServerDescription, int & a_OnlinePlayersCount, int & a_MaxPlayersCount, AString & a_Favicon) { cCSLock Lock(m_CriticalSection); + if (!m_LuaState.IsValid()) + { + return false; + } bool res = false; cLuaRefs & Refs = m_HookMap[cPluginManager::HOOK_SERVER_PING]; for (cLuaRefs::iterator itr = Refs.begin(), end = Refs.end(); itr != end; ++itr) @@ -1265,6 +1486,10 @@ bool cPluginLua::OnServerPing(cClientHandle & a_ClientHandle, AString & a_Server bool cPluginLua::OnSpawnedEntity(cWorld & a_World, cEntity & a_Entity) { cCSLock Lock(m_CriticalSection); + if (!m_LuaState.IsValid()) + { + return false; + } bool res = false; cLuaRefs & Refs = m_HookMap[cPluginManager::HOOK_SPAWNED_ENTITY]; for (cLuaRefs::iterator itr = Refs.begin(), end = Refs.end(); itr != end; ++itr) @@ -1285,6 +1510,10 @@ bool cPluginLua::OnSpawnedEntity(cWorld & a_World, cEntity & a_Entity) bool cPluginLua::OnSpawnedMonster(cWorld & a_World, cMonster & a_Monster) { cCSLock Lock(m_CriticalSection); + if (!m_LuaState.IsValid()) + { + return false; + } bool res = false; cLuaRefs & Refs = m_HookMap[cPluginManager::HOOK_SPAWNED_MONSTER]; for (cLuaRefs::iterator itr = Refs.begin(), end = Refs.end(); itr != end; ++itr) @@ -1305,6 +1534,10 @@ bool cPluginLua::OnSpawnedMonster(cWorld & a_World, cMonster & a_Monster) bool cPluginLua::OnSpawningEntity(cWorld & a_World, cEntity & a_Entity) { cCSLock Lock(m_CriticalSection); + if (!m_LuaState.IsValid()) + { + return false; + } bool res = false; cLuaRefs & Refs = m_HookMap[cPluginManager::HOOK_SPAWNING_ENTITY]; for (cLuaRefs::iterator itr = Refs.begin(), end = Refs.end(); itr != end; ++itr) @@ -1325,6 +1558,10 @@ bool cPluginLua::OnSpawningEntity(cWorld & a_World, cEntity & a_Entity) bool cPluginLua::OnSpawningMonster(cWorld & a_World, cMonster & a_Monster) { cCSLock Lock(m_CriticalSection); + if (!m_LuaState.IsValid()) + { + return false; + } bool res = false; cLuaRefs & Refs = m_HookMap[cPluginManager::HOOK_SPAWNING_MONSTER]; for (cLuaRefs::iterator itr = Refs.begin(), end = Refs.end(); itr != end; ++itr) @@ -1345,6 +1582,10 @@ bool cPluginLua::OnSpawningMonster(cWorld & a_World, cMonster & a_Monster) bool cPluginLua::OnTakeDamage(cEntity & a_Receiver, TakeDamageInfo & a_TDI) { cCSLock Lock(m_CriticalSection); + if (!m_LuaState.IsValid()) + { + return false; + } bool res = false; cLuaRefs & Refs = m_HookMap[cPluginManager::HOOK_TAKE_DAMAGE]; for (cLuaRefs::iterator itr = Refs.begin(), end = Refs.end(); itr != end; ++itr) @@ -1370,6 +1611,10 @@ bool cPluginLua::OnUpdatedSign( ) { cCSLock Lock(m_CriticalSection); + if (!m_LuaState.IsValid()) + { + return false; + } bool res = false; cLuaRefs & Refs = m_HookMap[cPluginManager::HOOK_UPDATED_SIGN]; for (cLuaRefs::iterator itr = Refs.begin(), end = Refs.end(); itr != end; ++itr) @@ -1395,6 +1640,10 @@ bool cPluginLua::OnUpdatingSign( ) { cCSLock Lock(m_CriticalSection); + if (!m_LuaState.IsValid()) + { + return false; + } bool res = false; cLuaRefs & Refs = m_HookMap[cPluginManager::HOOK_UPDATING_SIGN]; for (cLuaRefs::iterator itr = Refs.begin(), end = Refs.end(); itr != end; ++itr) @@ -1415,6 +1664,10 @@ bool cPluginLua::OnUpdatingSign( bool cPluginLua::OnWeatherChanged(cWorld & a_World) { cCSLock Lock(m_CriticalSection); + if (!m_LuaState.IsValid()) + { + return false; + } bool res = false; cLuaRefs & Refs = m_HookMap[cPluginManager::HOOK_WEATHER_CHANGED]; for (cLuaRefs::iterator itr = Refs.begin(), end = Refs.end(); itr != end; ++itr) @@ -1435,6 +1688,10 @@ bool cPluginLua::OnWeatherChanged(cWorld & a_World) bool cPluginLua::OnWeatherChanging(cWorld & a_World, eWeather & a_NewWeather) { cCSLock Lock(m_CriticalSection); + if (!m_LuaState.IsValid()) + { + return false; + } bool res = false; cLuaRefs & Refs = m_HookMap[cPluginManager::HOOK_WEATHER_CHANGING]; for (cLuaRefs::iterator itr = Refs.begin(), end = Refs.end(); itr != end; ++itr) @@ -1455,6 +1712,10 @@ bool cPluginLua::OnWeatherChanging(cWorld & a_World, eWeather & a_NewWeather) bool cPluginLua::OnWorldStarted(cWorld & a_World) { cCSLock Lock(m_CriticalSection); + if (!m_LuaState.IsValid()) + { + return false; + } cLuaRefs & Refs = m_HookMap[cPluginManager::HOOK_WORLD_STARTED]; for (cLuaRefs::iterator itr = Refs.begin(), end = Refs.end(); itr != end; ++itr) { @@ -1470,6 +1731,10 @@ bool cPluginLua::OnWorldStarted(cWorld & a_World) bool cPluginLua::OnWorldTick(cWorld & a_World, std::chrono::milliseconds a_Dt, std::chrono::milliseconds a_LastTickDurationMSec) { cCSLock Lock(m_CriticalSection); + if (!m_LuaState.IsValid()) + { + return false; + } cLuaRefs & Refs = m_HookMap[cPluginManager::HOOK_WORLD_TICK]; for (cLuaRefs::iterator itr = Refs.begin(), end = Refs.end(); itr != end; ++itr) { @@ -1736,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; } @@ -1784,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 4efcbb6f3..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, @@ -134,6 +134,8 @@ public: HOOK_WEATHER_CHANGING, HOOK_WORLD_STARTED, HOOK_WORLD_TICK, + + // tolua_end // Note that if a hook type is added, it may need processing in cPlugin::CanAddHook() descendants, // and it definitely needs adding in cPluginLua::GetHookFnName() ! @@ -141,8 +143,7 @@ public: // Keep these two as the last items, they are used for validity checking and get their values automagically HOOK_NUM_HOOKS, HOOK_MAX = HOOK_NUM_HOOKS - 1, - } ; - // tolua_end + } ; // tolua_export /** Used as a callback for enumerating bound commands */ class cCommandEnumCallback @@ -159,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); @@ -241,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 @@ -295,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. */ @@ -316,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(); @@ -339,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/BlockInServerPluginInterface.h b/src/BlockInServerPluginInterface.h index 70c9944a8..d4759ce83 100644 --- a/src/BlockInServerPluginInterface.h +++ b/src/BlockInServerPluginInterface.h @@ -1,4 +1,12 @@ +// BlockInServerPluginInterface.h + +// Defines the cBlockInServerPluginInterface class that implements the cBlockPluginInterface for blocks, using the plugin manager + + + + + #pragma once #include "Blocks/BlockPluginInterface.h" @@ -16,7 +24,7 @@ class cBlockInServerPluginInterface : public: cBlockInServerPluginInterface(cWorld & a_World) : m_World(a_World) {} - virtual bool CallHookBlockSpread(int a_BlockX, int a_BlockY, int a_BlockZ, eSpreadSource a_Source) + virtual bool CallHookBlockSpread(int a_BlockX, int a_BlockY, int a_BlockZ, eSpreadSource a_Source) override { return cPluginManager::Get()->CallHookBlockSpread(m_World, a_BlockX, a_BlockY, a_BlockZ, a_Source); } @@ -26,6 +34,16 @@ public: return cPluginManager::Get()->CallHookBlockToPickups(m_World, a_Digger, a_BlockX, a_BlockY, a_BlockZ, a_BlockType, a_BlockMeta, a_Pickups); } + virtual bool CallHookPlayerBreakingBlock(cPlayer & a_Player, int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta) override + { + return cPluginManager::Get()->CallHookPlayerBreakingBlock(a_Player, a_BlockX, a_BlockY, a_BlockZ, a_BlockFace, a_BlockType, a_BlockMeta); + } + + virtual bool CallHookPlayerBrokenBlock(cPlayer & a_Player, int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta) override + { + return cPluginManager::Get()->CallHookPlayerBrokenBlock(a_Player, a_BlockX, a_BlockY, a_BlockZ, a_BlockFace, a_BlockType, a_BlockMeta); + } + private: cWorld & m_World; }; diff --git a/src/Blocks/BlockHandler.cpp b/src/Blocks/BlockHandler.cpp index 2de4a3e4c..6fff4c18f 100644 --- a/src/Blocks/BlockHandler.cpp +++ b/src/Blocks/BlockHandler.cpp @@ -353,7 +353,7 @@ bool cBlockHandler::GetPlacementBlockTypeMeta( { // By default, all blocks can be placed and the meta is copied over from the item's damage value: a_BlockType = m_BlockType; - a_BlockMeta = (NIBBLETYPE)(a_Player->GetEquippedItem().m_ItemDamage & 0x0f); + a_BlockMeta = static_cast<NIBBLETYPE>(a_Player->GetEquippedItem().m_ItemDamage & 0x0f); return true; } diff --git a/src/Blocks/BlockPluginInterface.h b/src/Blocks/BlockPluginInterface.h index b769bcf3e..6d49a248d 100644 --- a/src/Blocks/BlockPluginInterface.h +++ b/src/Blocks/BlockPluginInterface.h @@ -1,10 +1,25 @@ +// BlockPluginInterface.h + +// Declares the cBlockPluginInterface class representing an interface that the blockhandlers and itemhandlers use for calling plugins + + + + + #pragma once +// fwd: +class cPlayer; + + + + + /** This interface is used to decouple block handlers from the cPluginManager dependency through cWorld. The block handlers call this interface, which is then implemented by the specific classes that the caller provides. @@ -16,6 +31,8 @@ public: virtual bool CallHookBlockSpread(int a_BlockX, int a_BlockY, int a_BlockZ, eSpreadSource a_Source) = 0; virtual bool CallHookBlockToPickups(cEntity * a_Digger, int a_BlockX, int a_BlockY, int a_BlockZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta, cItems & a_Pickups) = 0; + virtual bool CallHookPlayerBreakingBlock(cPlayer & a_Player, int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta) = 0; + virtual bool CallHookPlayerBrokenBlock(cPlayer & a_Player, int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta) = 0; }; diff --git a/src/ChunkMap.cpp b/src/ChunkMap.cpp index 87da86131..394dc703b 100644 --- a/src/ChunkMap.cpp +++ b/src/ChunkMap.cpp @@ -2413,6 +2413,7 @@ bool cChunkMap::GenerateChunk(int a_ChunkX, int a_ChunkZ, cChunkCoordCallback * // Try loading the chunk: if ((Chunk == nullptr) || (!Chunk->IsValid())) { + Chunk->SetPresence(cChunk::cpQueued); class cPrepareLoadCallback: public cChunkCoordCallback { public: @@ -2427,6 +2428,7 @@ bool cChunkMap::GenerateChunk(int a_ChunkX, int a_ChunkZ, cChunkCoordCallback * virtual void Call(int a_CBChunkX, int a_CBChunkZ) override { // The chunk has been loaded or an error occurred, check if it's valid now: + cCSLock Lock(m_ChunkMap.m_CSLayers); cChunkPtr CBChunk = m_ChunkMap.GetChunkNoLoad(a_CBChunkX, a_CBChunkZ); if (CBChunk == nullptr) diff --git a/src/ClientHandle.cpp b/src/ClientHandle.cpp index 28fccb68e..60a2f8873 100644 --- a/src/ClientHandle.cpp +++ b/src/ClientHandle.cpp @@ -23,6 +23,7 @@ #include "Blocks/BlockSlab.h" #include "Blocks/BlockBed.h" #include "Blocks/ChunkInterface.h" +#include "BlockInServerPluginInterface.h" #include "Root.h" @@ -1340,7 +1341,14 @@ void cClientHandle::HandleRightClick(int a_BlockX, int a_BlockY, int a_BlockZ, e { AddFaceDirection(a_BlockX, a_BlockY, a_BlockZ, a_BlockFace); World->SendBlockTo(a_BlockX, a_BlockY, a_BlockZ, m_Player); - World->SendBlockTo(a_BlockX, a_BlockY + 1, a_BlockZ, m_Player); // 2 block high things + if (a_BlockY < cChunkDef::Height - 1) + { + World->SendBlockTo(a_BlockX, a_BlockY + 1, a_BlockZ, m_Player); // 2 block high things + } + if (a_BlockY > 1) + { + World->SendBlockTo(a_BlockX, a_BlockY - 1, a_BlockZ, m_Player); // 2 block high things + } m_Player->GetInventory().SendEquippedSlot(); } } @@ -1432,7 +1440,8 @@ void cClientHandle::HandleRightClick(int a_BlockX, int a_BlockY, int a_BlockZ, e // A plugin doesn't agree with using the item, abort return; } - ItemHandler->OnItemUse(World, m_Player, Equipped, a_BlockX, a_BlockY, a_BlockZ, a_BlockFace); + cBlockInServerPluginInterface PluginInterface(*World); + ItemHandler->OnItemUse(World, m_Player, PluginInterface, Equipped, a_BlockX, a_BlockY, a_BlockZ, a_BlockFace); PlgMgr->CallHookPlayerUsedItem(*m_Player, a_BlockX, a_BlockY, a_BlockZ, a_BlockFace, a_CursorX, a_CursorY, a_CursorZ); } } diff --git a/src/CraftingRecipes.cpp b/src/CraftingRecipes.cpp index 202fb900e..472044fa3 100644 --- a/src/CraftingRecipes.cpp +++ b/src/CraftingRecipes.cpp @@ -366,6 +366,7 @@ void cCraftingRecipes::ClearRecipes(void) void cCraftingRecipes::AddRecipeLine(int a_LineNum, const AString & a_RecipeLine) { + // Remove any spaces within the line: AString RecipeLine(a_RecipeLine); RecipeLine.erase(std::remove_if(RecipeLine.begin(), RecipeLine.end(), isspace), RecipeLine.end()); @@ -672,10 +673,10 @@ cCraftingRecipes::cRecipe * cCraftingRecipes::MatchRecipe(const cItem * a_Crafti if ( (itrS->x >= a_GridWidth) || (itrS->y >= a_GridHeight) || - (Item.m_ItemType != a_CraftingGrid[GridID].m_ItemType) || // same item type? + (Item.m_ItemType != a_CraftingGrid[GridID].m_ItemType) || // same item type? (Item.m_ItemCount > a_CraftingGrid[GridID].m_ItemCount) || // not enough items ( - (Item.m_ItemDamage > 0) && // should compare damage values? + (Item.m_ItemDamage >= 0) && // should compare damage values? (Item.m_ItemDamage != a_CraftingGrid[GridID].m_ItemDamage) ) ) diff --git a/src/Entities/ArrowEntity.cpp b/src/Entities/ArrowEntity.cpp index 3c1fabb1b..32952100c 100644 --- a/src/Entities/ArrowEntity.cpp +++ b/src/Entities/ArrowEntity.cpp @@ -21,6 +21,8 @@ cArrowEntity::cArrowEntity(cEntity * a_Creator, double a_X, double a_Y, double a { SetSpeed(a_Speed); SetMass(0.1); + SetGravity(-20.0f); + SetAirDrag(0.2f); SetYawFromSpeed(); SetPitchFromSpeed(); LOGD("Created arrow %d with speed {%.02f, %.02f, %.02f} and rot {%.02f, %.02f}", @@ -48,6 +50,8 @@ cArrowEntity::cArrowEntity(cPlayer & a_Player, double a_Force) : { m_PickupState = psInCreative; } + SetGravity(-20.0f); + SetAirDrag(0.01f); } diff --git a/src/Entities/Boat.cpp b/src/Entities/Boat.cpp index 6177eb32f..4ad418be4 100644 --- a/src/Entities/Boat.cpp +++ b/src/Entities/Boat.cpp @@ -16,7 +16,9 @@ cBoat::cBoat(double a_X, double a_Y, double a_Z) : super(etBoat, a_X, a_Y, a_Z, 0.98, 0.7) { - SetMass(20.f); + SetMass(20.0f); + SetGravity(-16.0f); + SetAirDrag(0.05f); SetMaxHealth(6); SetHealth(6); } diff --git a/src/Entities/Entity.cpp b/src/Entities/Entity.cpp index c8df6b4b1..51cee248e 100644 --- a/src/Entities/Entity.cpp +++ b/src/Entities/Entity.cpp @@ -36,6 +36,7 @@ cEntity::cEntity(eEntityType a_EntityType, double a_X, double a_Y, double a_Z, d m_bHasSentNoSpeed(true), m_bOnGround(false), m_Gravity(-9.81f), + m_AirDrag(0.02f), m_LastPos(a_X, a_Y, a_Z), m_IsInitialized(false), m_WorldTravellingFrom(nullptr), @@ -943,6 +944,7 @@ void cEntity::HandlePhysics(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) { // Normal gravity fallspeed = m_Gravity * DtSec.count(); + NextSpeed -= NextSpeed * (m_AirDrag * 20.0f) * DtSec.count(); } NextSpeed.y += static_cast<float>(fallspeed); } @@ -999,7 +1001,7 @@ void cEntity::HandlePhysics(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) NextSpeed += m_WaterSpeed; - if (NextSpeed.SqrLength() > 0.f) + if (NextSpeed.SqrLength() > 0.0f) { cTracer Tracer(GetWorld()); // Distance traced is an integer, so we round up from the distance we should go (Speed * Delta), else we will encounter collision detection failurse @@ -1015,20 +1017,20 @@ void cEntity::HandlePhysics(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) // Block hit was within our projected path // Begin by stopping movement in the direction that we hit something. The Normal is the line perpendicular to a 2D face and in this case, stores what block face was hit through either -1 or 1. // For example: HitNormal.y = -1 : BLOCK_FACE_YM; HitNormal.y = 1 : BLOCK_FACE_YP - if (Tracer.HitNormal.x != 0.f) + if (Tracer.HitNormal.x != 0.0f) { - NextSpeed.x = 0.f; + NextSpeed.x = 0.0f; } - if (Tracer.HitNormal.y != 0.f) + if (Tracer.HitNormal.y != 0.0f) { - NextSpeed.y = 0.f; + NextSpeed.y = 0.0f; } - if (Tracer.HitNormal.z != 0.f) + if (Tracer.HitNormal.z != 0.0f) { - NextSpeed.z = 0.f; + NextSpeed.z = 0.0f; } - if (Tracer.HitNormal.y == 1.f) // Hit BLOCK_FACE_YP, we are on the ground + if (Tracer.HitNormal.y == 1.0f) // Hit BLOCK_FACE_YP, we are on the ground { m_bOnGround = true; } @@ -1971,7 +1973,7 @@ void cEntity::SteerVehicle(float a_Forward, float a_Sideways) { return; } - if ((a_Forward != 0.f) || (a_Sideways != 0.f)) + if ((a_Forward != 0.0f) || (a_Sideways != 0.0f)) { m_AttachedTo->HandleSpeedFromAttachee(a_Forward, a_Sideways); } diff --git a/src/Entities/Entity.h b/src/Entities/Entity.h index 9bb1837f1..dd6190ced 100644 --- a/src/Entities/Entity.h +++ b/src/Entities/Entity.h @@ -270,6 +270,10 @@ public: float GetGravity(void) const { return m_Gravity; } void SetGravity(float a_Gravity) { m_Gravity = a_Gravity; } + + float GetAirDrag(void) const { return m_AirDrag; } + + void SetAirDrag(float a_AirDrag) { m_AirDrag = a_AirDrag; } /// Sets the rotation to match the speed vector (entity goes "face-forward") void SetYawFromSpeed(void); @@ -504,6 +508,12 @@ protected: For realistic effects, this should be negative. For spaaaaaaace, this can be zero or even positive */ float m_Gravity; + /** Stores the air drag that is applied to the entity every tick, measured in speed ratio per tick + Acts as air friction and slows down flight + Will be interpolated if the server tick rate varies + Data: http://minecraft.gamepedia.com/Entity#Motion_of_entities */ + float m_AirDrag; + /** Last position sent to client via the Relative Move or Teleport packets (not Velocity) Only updated if cEntity::BroadcastMovementUpdate() is called! */ Vector3d m_LastPos; diff --git a/src/Entities/FallingBlock.cpp b/src/Entities/FallingBlock.cpp index 7301a3c9d..4a165909a 100644 --- a/src/Entities/FallingBlock.cpp +++ b/src/Entities/FallingBlock.cpp @@ -16,6 +16,8 @@ cFallingBlock::cFallingBlock(const Vector3i & a_BlockPosition, BLOCKTYPE a_Block m_BlockMeta(a_BlockMeta), m_OriginalPosition(a_BlockPosition) { + SetGravity(-16.0f); + SetAirDrag(0.02f); } diff --git a/src/Entities/FireChargeEntity.cpp b/src/Entities/FireChargeEntity.cpp index aba32602f..f6c665156 100644 --- a/src/Entities/FireChargeEntity.cpp +++ b/src/Entities/FireChargeEntity.cpp @@ -12,6 +12,7 @@ cFireChargeEntity::cFireChargeEntity(cEntity * a_Creator, double a_X, double a_Y { SetSpeed(a_Speed); SetGravity(0); + SetAirDrag(0); } diff --git a/src/Entities/FireworkEntity.cpp b/src/Entities/FireworkEntity.cpp index 32eaf669a..89f69f113 100644 --- a/src/Entities/FireworkEntity.cpp +++ b/src/Entities/FireworkEntity.cpp @@ -13,6 +13,8 @@ cFireworkEntity::cFireworkEntity(cEntity * a_Creator, double a_X, double a_Y, do m_TicksToExplosion(a_Item.m_FireworkItem.m_FlightTimeInTicks), m_FireworkItem(a_Item) { + SetGravity(0); + SetAirDrag(0); } diff --git a/src/Entities/GhastFireballEntity.cpp b/src/Entities/GhastFireballEntity.cpp index 9e4cb387e..c64fb2a17 100644 --- a/src/Entities/GhastFireballEntity.cpp +++ b/src/Entities/GhastFireballEntity.cpp @@ -12,6 +12,7 @@ cGhastFireballEntity::cGhastFireballEntity(cEntity * a_Creator, double a_X, doub { SetSpeed(a_Speed); SetGravity(0); + SetAirDrag(0); } diff --git a/src/Entities/HangingEntity.h b/src/Entities/HangingEntity.h index 507502ac6..9d783006c 100644 --- a/src/Entities/HangingEntity.h +++ b/src/Entities/HangingEntity.h @@ -27,7 +27,10 @@ public: eBlockFace GetFacing() const { return cHangingEntity::ProtocolFaceToBlockFace(m_Facing); } /** Set the direction in which the entity is facing. */ - void SetFacing(eBlockFace a_Facing) { m_Facing = cHangingEntity::BlockFaceToProtocolFace(a_Facing); } + void SetFacing(eBlockFace a_Facing) + { + m_Facing = cHangingEntity::BlockFaceToProtocolFace(a_Facing); + } // tolua_end @@ -37,7 +40,7 @@ public: /** Set the direction in which the entity is facing. */ void SetProtocolFacing(Byte a_Facing) { - ASSERT((a_Facing <= 3) && (a_Facing >= 0)); + ASSERT(a_Facing <= 3); m_Facing = a_Facing; } diff --git a/src/Entities/Minecart.cpp b/src/Entities/Minecart.cpp index ee10cf6b3..3d56570ba 100644 --- a/src/Entities/Minecart.cpp +++ b/src/Entities/Minecart.cpp @@ -92,7 +92,9 @@ cMinecart::cMinecart(ePayload a_Payload, double a_X, double a_Y, double a_Z) : m_DetectorRailPosition(0, 0, 0), m_bIsOnDetectorRail(false) { - SetMass(20.f); + SetMass(20.0f); + SetGravity(-16.0f); + SetAirDrag(0.05f); SetMaxHealth(6); SetHealth(6); SetWidth(1); diff --git a/src/Entities/Pawn.cpp b/src/Entities/Pawn.cpp index baf8a2f3b..fcb686e28 100644 --- a/src/Entities/Pawn.cpp +++ b/src/Entities/Pawn.cpp @@ -13,6 +13,8 @@ cPawn::cPawn(eEntityType a_EntityType, double a_Width, double a_Height) : super(a_EntityType, 0, 0, 0, a_Width, a_Height) , m_EntityEffects(tEffectMap()) { + SetGravity(-32.0f); + SetAirDrag(0.02f); } diff --git a/src/Entities/Pickup.cpp b/src/Entities/Pickup.cpp index 9f2609894..f2f76dbf9 100644 --- a/src/Entities/Pickup.cpp +++ b/src/Entities/Pickup.cpp @@ -91,7 +91,8 @@ cPickup::cPickup(double a_PosX, double a_PosY, double a_PosZ, const cItem & a_It , m_bCollected(false) , m_bIsPlayerCreated(IsPlayerCreated) { - SetGravity(-10.5f); + SetGravity(-16.0f); + SetAirDrag(0.02f); SetMaxHealth(5); SetHealth(5); SetSpeed(a_SpeedX, a_SpeedY, a_SpeedZ); diff --git a/src/Entities/ProjectileEntity.cpp b/src/Entities/ProjectileEntity.cpp index 4684e3e09..05b7669cd 100644 --- a/src/Entities/ProjectileEntity.cpp +++ b/src/Entities/ProjectileEntity.cpp @@ -227,6 +227,8 @@ cProjectileEntity::cProjectileEntity(eKind a_Kind, cEntity * a_Creator, double a ), m_IsInGround(false) { + SetGravity(-12.0f); + SetAirDrag(0.01f); } @@ -242,6 +244,8 @@ cProjectileEntity::cProjectileEntity(eKind a_Kind, cEntity * a_Creator, const Ve SetSpeed(a_Speed); SetYawFromSpeed(); SetPitchFromSpeed(); + SetGravity(-12.0f); + SetAirDrag(0.01f); } @@ -349,9 +353,11 @@ void cProjectileEntity::HandlePhysics(std::chrono::milliseconds a_Dt, cChunk & a return; } - const Vector3d PerTickSpeed = GetSpeed() / 20; + auto DtSec = std::chrono::duration_cast<std::chrono::duration<double>>(a_Dt); + + const Vector3d DeltaSpeed = GetSpeed() * DtSec.count(); const Vector3d Pos = GetPosition(); - const Vector3d NextPos = Pos + PerTickSpeed; + const Vector3d NextPos = Pos + DeltaSpeed; // Test for entity collisions: cProjectileEntityCollisionCallback EntityCollisionCallback(this, Pos, NextPos); @@ -388,8 +394,8 @@ void cProjectileEntity::HandlePhysics(std::chrono::milliseconds a_Dt, cChunk & a // Add slowdown and gravity effect to the speed: Vector3d NewSpeed(GetSpeed()); - NewSpeed.y += m_Gravity / 20; - NewSpeed *= TracerCallback.GetSlowdownCoeff(); + NewSpeed.y += m_Gravity * DtSec.count(); + NewSpeed -= NewSpeed * (m_AirDrag * 20.0f) * DtSec.count(); SetSpeed(NewSpeed); SetYawFromSpeed(); SetPitchFromSpeed(); diff --git a/src/Entities/TNTEntity.cpp b/src/Entities/TNTEntity.cpp index a89d2f300..d849bd4c9 100644 --- a/src/Entities/TNTEntity.cpp +++ b/src/Entities/TNTEntity.cpp @@ -12,6 +12,8 @@ cTNTEntity::cTNTEntity(double a_X, double a_Y, double a_Z, int a_FuseTicks) : super(etTNT, a_X, a_Y, a_Z, 0.98, 0.98), m_FuseTicks(a_FuseTicks) { + SetGravity(-16.0f); + SetAirDrag(0.02f); } @@ -22,6 +24,8 @@ cTNTEntity::cTNTEntity(const Vector3d & a_Pos, int a_FuseTicks) : super(etTNT, a_Pos.x, a_Pos.y, a_Pos.z, 0.98, 0.98), m_FuseTicks(a_FuseTicks) { + SetGravity(-16.0f); + SetAirDrag(0.4f); } diff --git a/src/Entities/WitherSkullEntity.cpp b/src/Entities/WitherSkullEntity.cpp index a7e774bba..dc95e3edd 100644 --- a/src/Entities/WitherSkullEntity.cpp +++ b/src/Entities/WitherSkullEntity.cpp @@ -16,6 +16,8 @@ cWitherSkullEntity::cWitherSkullEntity(cEntity * a_Creator, double a_X, double a super(pkWitherSkull, a_Creator, a_X, a_Y, a_Z, 0.25, 0.25) { SetSpeed(a_Speed); + SetGravity(0); + SetAirDrag(0); } diff --git a/src/Generating/CompoGen.cpp b/src/Generating/CompoGen.cpp index cb9c04fd7..7cadc881a 100644 --- a/src/Generating/CompoGen.cpp +++ b/src/Generating/CompoGen.cpp @@ -218,7 +218,7 @@ void cCompoGenClassic::InitializeCompoGen(cIniFile & a_IniFile) cCompoGenNether::cCompoGenNether(int a_Seed) : m_Noise1(a_Seed + 10), m_Noise2(a_Seed * a_Seed * 10 + a_Seed * 1000 + 6000), - m_Threshold(0) + m_MaxThreshold(25000) { } @@ -282,17 +282,16 @@ void cCompoGenNether::ComposeTerrain(cChunkDesc & a_ChunkDesc, const cChunkDesc: // Interpolate between FloorLo and FloorHi: for (int z = 0; z < 16; z++) for (int x = 0; x < 16; x++) { + int Threshold = static_cast<int>(m_Noise1.CubicNoise2D(static_cast<float>(BaseX + x) / 75, static_cast<float>(BaseZ + z) / 75) * m_MaxThreshold); int Lo = FloorLo[x + 17 * z] / 256; int Hi = FloorHi[x + 17 * z] / 256; for (int y = 0; y < SEGMENT_HEIGHT; y++) { int Val = Lo + (Hi - Lo) * y / SEGMENT_HEIGHT; - BLOCKTYPE Block = E_BLOCK_AIR; - if (Val < m_Threshold) // Don't calculate if the block should be Netherrack or Soulsand when it's already decided that it's air. + if (Val < Threshold) // Don't calculate if the block should be Netherrack when it's already decided that it's air. { - Block = E_BLOCK_NETHERRACK; + a_ChunkDesc.SetBlockType(x, y + Segment, z, E_BLOCK_NETHERRACK); } - a_ChunkDesc.SetBlockType(x, y + Segment, z, Block); } } @@ -329,7 +328,7 @@ void cCompoGenNether::ComposeTerrain(cChunkDesc & a_ChunkDesc, const cChunkDesc: void cCompoGenNether::InitializeCompoGen(cIniFile & a_IniFile) { - m_Threshold = a_IniFile.GetValueSetI("Generator", "NetherThreshold", m_Threshold); + m_MaxThreshold = a_IniFile.GetValueSetF("Generator", "NetherMaxThreshold", m_MaxThreshold); } diff --git a/src/Generating/CompoGen.h b/src/Generating/CompoGen.h index 3847688cd..d4d38bfdd 100644 --- a/src/Generating/CompoGen.h +++ b/src/Generating/CompoGen.h @@ -99,7 +99,7 @@ protected: cNoise m_Noise1; cNoise m_Noise2; - int m_Threshold; + double m_MaxThreshold; // cTerrainCompositionGen overrides: virtual void ComposeTerrain(cChunkDesc & a_ChunkDesc, const cChunkDesc::Shape & a_Shape) override; diff --git a/src/Generating/ComposableGenerator.cpp b/src/Generating/ComposableGenerator.cpp index 4a670b064..6b7643ddb 100644 --- a/src/Generating/ComposableGenerator.cpp +++ b/src/Generating/ComposableGenerator.cpp @@ -347,6 +347,10 @@ void cComposableGenerator::InitFinishGens(cIniFile & a_IniFile) AString HeightDistrib = a_IniFile.GetValueSet ("Generator", "DungeonRoomsHeightDistrib", "0, 0; 10, 10; 11, 500; 40, 500; 60, 40; 90, 1"); m_FinishGens.push_back(cFinishGenPtr(new cDungeonRoomsFinisher(m_ShapeGen, Seed, GridSize, MaxSize, MinSize, HeightDistrib))); } + else if (NoCaseCompare(*itr, "GlowStone") == 0) + { + m_FinishGens.push_back(cFinishGenPtr(new cFinishGenGlowStone(Seed))); + } else if (NoCaseCompare(*itr, "Ice") == 0) { m_FinishGens.push_back(cFinishGenPtr(new cFinishGenIce)); @@ -447,7 +451,7 @@ void cComposableGenerator::InitFinishGens(cIniFile & a_IniFile) else if (NoCaseCompare(*itr, "NetherForts") == 0) { int GridSize = a_IniFile.GetValueSetI("Generator", "NetherFortsGridSize", 512); - int MaxOffset = a_IniFile.GetValueSetI("Generator", "NetherFortMaxOffset", 128); + int MaxOffset = a_IniFile.GetValueSetI("Generator", "NetherFortsMaxOffset", 128); int MaxDepth = a_IniFile.GetValueSetI("Generator", "NetherFortsMaxDepth", 12); m_FinishGens.push_back(cFinishGenPtr(new cNetherFortGen(Seed, GridSize, MaxOffset, MaxDepth))); } diff --git a/src/Generating/FinishGen.cpp b/src/Generating/FinishGen.cpp index 5839a4ccc..7541c8598 100644 --- a/src/Generating/FinishGen.cpp +++ b/src/Generating/FinishGen.cpp @@ -180,6 +180,118 @@ void cFinishGenNetherClumpFoliage::TryPlaceClump(cChunkDesc & a_ChunkDesc, int a //////////////////////////////////////////////////////////////////////////////// +// cFinishGenGlowStone: + +void cFinishGenGlowStone::GenFinish(cChunkDesc & a_ChunkDesc) +{ + int ChunkX = a_ChunkDesc.GetChunkX(); + int ChunkZ = a_ChunkDesc.GetChunkZ(); + + // Change the number of attempts to create a vein depending on the maximum height of the chunk. A standard Nether could have 5 veins at most. + int NumGlowStone = m_Noise.IntNoise2DInt(ChunkX, ChunkZ) % a_ChunkDesc.GetMaxHeight() / 23; + + for (int i = 1; i <= NumGlowStone; i++) + { + // The maximum size for a string of glowstone can get 3 - 5 blocks long + int Size = 3 + m_Noise.IntNoise3DInt(ChunkX, i, ChunkZ) % 3; + + // Generate X/Z coordinates. + int X = Size + (m_Noise.IntNoise2DInt(i, Size) % (cChunkDef::Width - Size * 2)); + int Z = Size + (m_Noise.IntNoise2DInt(X, i) % (cChunkDef::Width - Size * 2)); + + int Height = a_ChunkDesc.GetHeight(X, Z); + for (int y = Height; y > Size; y--) + { + if (!cBlockInfo::IsSolid(a_ChunkDesc.GetBlockType(X, y, Z))) + { + // Current block isn't solid, bail out + continue; + } + + if (a_ChunkDesc.GetBlockType(X, y - 1, Z) != E_BLOCK_AIR) + { + // The block below isn't air, bail out + continue; + } + + if ((m_Noise.IntNoise3DInt(X, y, Z) % 100) < 95) + { + // Have a 5% chance of creating the glowstone + continue; + } + + TryPlaceGlowstone(a_ChunkDesc, X, y, Z, Size, 5 + m_Noise.IntNoise3DInt(X, y, Z) % 7); + break; + } + } +} + + + + + +void cFinishGenGlowStone::TryPlaceGlowstone(cChunkDesc & a_ChunkDesc, int a_RelX, int a_RelY, int a_RelZ, int a_Size, int a_NumStrings) +{ + // The starting point of every glowstone string + Vector3i StartPoint = Vector3i(a_RelX, a_RelY, a_RelZ); + + // Array with possible directions for a string of glowstone to go to. + const Vector3i AvailableDirections[] = + { + { -1, 0, 0 }, { 1, 0, 0 }, + { 0, -1, 0 }, // Don't let the glowstone go up + { 0, 0, -1 }, { 0, 0, 1 }, + + // Diagonal direction. Only X or Z with Y. + // If all were changed the glowstone string looks awkward + { 0, -1, 1 }, { 1, -1, 0 }, + { 0, -1, -1 }, { -1, -1, 0 }, + + }; + + for (int i = 1; i <= a_NumStrings; i++) + { + // The current position of the string that is being generated + Vector3i CurrentPos = Vector3i(StartPoint); + + // A vector where the previous direction of a glowstone string is stored. + // This is used to make the strings change direction when going one block further + Vector3i PreviousDirection = Vector3i(); + + for (int j = 0; j < a_Size; j++) + { + Vector3i Direction = AvailableDirections[m_Noise.IntNoise3DInt(CurrentPos.x, CurrentPos.y * i, CurrentPos.z) % ARRAYCOUNT(AvailableDirections)]; + int Attempts = 2; // multiply by 1 would make no difference, so multiply by 2 instead + + while (Direction.Equals(PreviousDirection)) + { + // To make the glowstone branches look better we want to make the direction change every time. + Direction = AvailableDirections[m_Noise.IntNoise3DInt(CurrentPos.x, CurrentPos.y * i * Attempts, CurrentPos.z) % ARRAYCOUNT(AvailableDirections)]; + Attempts++; + } + + // Update the previous direction variable + PreviousDirection = Direction; + + // Update the position of the glowstone string + CurrentPos += Direction; + if (cBlockInfo::IsSolid(a_ChunkDesc.GetBlockType(CurrentPos.x, CurrentPos.y, CurrentPos.z)) && (a_ChunkDesc.GetBlockType(CurrentPos.x, CurrentPos.y, CurrentPos.z) != E_BLOCK_GLOWSTONE)) + { + // The glowstone hit something solid, and it wasn't glowstone. Stop the string. + break; + } + + // Place a glowstone block. + a_ChunkDesc.SetBlockType(CurrentPos.x, CurrentPos.y, CurrentPos.z, E_BLOCK_GLOWSTONE); + } + } +} + + + + + +//////////////////////////////////////////////////////////////////////////////// // cFinishGenTallGrass: void cFinishGenTallGrass::GenFinish(cChunkDesc & a_ChunkDesc) @@ -523,7 +635,7 @@ void cFinishGenSoulsandRims::GenFinish(cChunkDesc & a_ChunkDesc) { // The current block is air. Let's bail ut. BLOCKTYPE Block = a_ChunkDesc.GetBlockType(x, y, z); - if (Block == E_BLOCK_AIR) + if (Block != E_BLOCK_NETHERRACK) { continue; } diff --git a/src/Generating/FinishGen.h b/src/Generating/FinishGen.h index 70696c4f8..aa50335d4 100644 --- a/src/Generating/FinishGen.h +++ b/src/Generating/FinishGen.h @@ -70,6 +70,28 @@ protected: +class cFinishGenGlowStone : + public cFinishGen +{ +public: + cFinishGenGlowStone(int a_Seed) : + m_Noise(a_Seed), + m_Seed(a_Seed) + { + } + +protected: + cNoise m_Noise; + int m_Seed; + + void TryPlaceGlowstone(cChunkDesc & a_ChunkDesc, int a_RelX, int a_RelY, int a_RelZ, int a_Size, int a_NumStrings); + virtual void GenFinish(cChunkDesc & a_ChunkDesc) override; +} ; + + + + + class cFinishGenTallGrass : public cFinishGen { diff --git a/src/HTTPServer/HTTPConnection.cpp b/src/HTTPServer/HTTPConnection.cpp index de12b36ce..a5c6afd18 100644 --- a/src/HTTPServer/HTTPConnection.cpp +++ b/src/HTTPServer/HTTPConnection.cpp @@ -38,7 +38,9 @@ cHTTPConnection::~cHTTPConnection() void cHTTPConnection::SendStatusAndReason(int a_StatusCode, const AString & a_Response) { - SendData(Printf("%d %s\r\nContent-Length: 0\r\n\r\n", a_StatusCode, a_Response.c_str())); + SendData(Printf("HTTP/1.1 %d %s\r\n", a_StatusCode, a_Response.c_str())); + SendData(Printf("Content-Length: %u\r\n\r\n", static_cast<unsigned>(a_Response.size()))); + SendData(a_Response.data(), a_Response.size()); m_State = wcsRecvHeaders; } diff --git a/src/HTTPServer/HTTPConnection.h b/src/HTTPServer/HTTPConnection.h index 8ecc4a4d4..e1ebeb9ee 100644 --- a/src/HTTPServer/HTTPConnection.h +++ b/src/HTTPServer/HTTPConnection.h @@ -41,7 +41,8 @@ public: cHTTPConnection(cHTTPServer & a_HTTPServer); virtual ~cHTTPConnection(); - /** Sends HTTP status code together with a_Reason (used for HTTP errors) */ + /** Sends HTTP status code together with a_Reason (used for HTTP errors). + Sends the a_Reason as the body as well, so that browsers display it. */ void SendStatusAndReason(int a_StatusCode, const AString & a_Reason); /** Sends the "401 unauthorized" reply together with instructions on authorizing, using the specified realm */ diff --git a/src/Items/ItemArmor.h b/src/Items/ItemArmor.h index 2436df5bd..252b94df9 100644 --- a/src/Items/ItemArmor.h +++ b/src/Items/ItemArmor.h @@ -17,8 +17,13 @@ public: { } + + /** Move the armor to the armor slot of the player's inventory */ - virtual bool OnItemUse(cWorld * a_World, cPlayer * a_Player, const cItem & a_Item, int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_Dir) override + virtual bool OnItemUse( + cWorld * a_World, cPlayer * a_Player, cBlockPluginInterface & a_PluginInterface, const cItem & a_Item, + int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace + ) override { int SlotNum; if (ItemCategory::IsHelmet(a_Item.m_ItemType)) @@ -60,6 +65,8 @@ public: return true; } + + virtual bool CanRepairWithRawMaterial(short a_ItemType) override { switch (m_ItemType) diff --git a/src/Items/ItemBoat.h b/src/Items/ItemBoat.h index 7faac1e32..452d86775 100644 --- a/src/Items/ItemBoat.h +++ b/src/Items/ItemBoat.h @@ -28,9 +28,12 @@ public: - virtual bool OnItemUse(cWorld * a_World, cPlayer * a_Player, const cItem & a_Item, int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_Dir) override + virtual bool OnItemUse( + cWorld * a_World, cPlayer * a_Player, cBlockPluginInterface & a_PluginInterface, const cItem & a_Item, + int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace + ) override { - if ((a_Dir != BLOCK_FACE_YM) && (a_Dir != BLOCK_FACE_NONE)) + if ((a_BlockFace != BLOCK_FACE_YM) && (a_BlockFace != BLOCK_FACE_NONE)) { return false; } diff --git a/src/Items/ItemBow.h b/src/Items/ItemBow.h index cf2fe9ad2..5164ddf59 100644 --- a/src/Items/ItemBow.h +++ b/src/Items/ItemBow.h @@ -26,8 +26,12 @@ public: { } + - virtual bool OnItemUse(cWorld * a_World, cPlayer * a_Player, const cItem & a_Item, int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_Dir) override + virtual bool OnItemUse( + cWorld * a_World, cPlayer * a_Player, cBlockPluginInterface & a_PluginInterface, const cItem & a_Item, + int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace + ) override { ASSERT(a_Player != nullptr); @@ -40,6 +44,7 @@ public: a_Player->StartChargingBow(); return true; } + virtual void OnItemShoot(cPlayer * a_Player, int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace) override diff --git a/src/Items/ItemBucket.h b/src/Items/ItemBucket.h index 871db821c..015720415 100644 --- a/src/Items/ItemBucket.h +++ b/src/Items/ItemBucket.h @@ -23,13 +23,18 @@ public: } - virtual bool OnItemUse(cWorld * a_World, cPlayer * a_Player, const cItem & a_Item, int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_Dir) override + + + virtual bool OnItemUse( + cWorld * a_World, cPlayer * a_Player, cBlockPluginInterface & a_PluginInterface, const cItem & a_Item, + int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace + ) override { switch (m_ItemType) { - case E_ITEM_BUCKET: return ScoopUpFluid(a_World, a_Player, a_Item, a_BlockX, a_BlockY, a_BlockZ, a_Dir); - case E_ITEM_LAVA_BUCKET: return PlaceFluid (a_World, a_Player, a_Item, a_BlockX, a_BlockY, a_BlockZ, a_Dir, E_BLOCK_LAVA); - case E_ITEM_WATER_BUCKET: return PlaceFluid (a_World, a_Player, a_Item, a_BlockX, a_BlockY, a_BlockZ, a_Dir, E_BLOCK_WATER); + case E_ITEM_BUCKET: return ScoopUpFluid(a_World, a_Player, a_Item, a_BlockX, a_BlockY, a_BlockZ, a_BlockFace); + case E_ITEM_LAVA_BUCKET: return PlaceFluid (a_World, a_Player, a_PluginInterface, a_Item, a_BlockX, a_BlockY, a_BlockZ, a_BlockFace, E_BLOCK_LAVA); + case E_ITEM_WATER_BUCKET: return PlaceFluid (a_World, a_Player, a_PluginInterface, a_Item, a_BlockX, a_BlockY, a_BlockZ, a_BlockFace, E_BLOCK_WATER); default: { ASSERT(!"Unhandled ItemType"); @@ -40,7 +45,7 @@ public: - bool ScoopUpFluid(cWorld * a_World, cPlayer * a_Player, const cItem & a_Item, int a_BlockX, int a_BlockY, int a_BlockZ, char a_BlockFace) + bool ScoopUpFluid(cWorld * a_World, cPlayer * a_Player, const cItem & a_Item, int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace) { if (a_BlockFace != BLOCK_FACE_NONE) { @@ -75,6 +80,12 @@ public: return false; } + // Remove water / lava block (unless plugins disagree) + if (!a_Player->PlaceBlock(BlockPos.x, BlockPos.y, BlockPos.z, E_BLOCK_AIR, 0)) + { + return false; + } + // Give new bucket, filled with fluid when the gamemode is not creative: if (!a_Player->IsGameModeCreative()) { @@ -85,25 +96,33 @@ public: ASSERT(!"Inventory bucket mismatch"); return true; } - a_Player->GetInventory().AddItem(cItem(NewItem), true, true); + if (a_Player->GetInventory().AddItem(cItem(NewItem), true, true) != 1) + { + // The bucket didn't fit, toss it as a pickup: + a_Player->TossPickup(cItem(NewItem)); + } } - // Remove water / lava block - a_Player->GetWorld()->SetBlock(BlockPos.x, BlockPos.y, BlockPos.z, E_BLOCK_AIR, 0); return true; } - bool PlaceFluid(cWorld * a_World, cPlayer * a_Player, const cItem & a_Item, int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace, BLOCKTYPE a_FluidBlock) + + bool PlaceFluid( + cWorld * a_World, cPlayer * a_Player, cBlockPluginInterface & a_PluginInterface, const cItem & a_Item, + int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace, BLOCKTYPE a_FluidBlock + ) { if (a_BlockFace != BLOCK_FACE_NONE) { return false; } - BLOCKTYPE CurrentBlock; + BLOCKTYPE CurrentBlockType; + NIBBLETYPE CurrentBlockMeta; + eBlockFace EntryFace; Vector3i BlockPos; - if (!GetPlacementCoordsFromTrace(a_World, a_Player, BlockPos, CurrentBlock)) + if (!GetPlacementCoordsFromTrace(a_World, a_Player, BlockPos, CurrentBlockType, CurrentBlockMeta, EntryFace)) { return false; } @@ -125,23 +144,29 @@ public: } // Wash away anything that was there prior to placing: - if (cFluidSimulator::CanWashAway(CurrentBlock)) + if (cFluidSimulator::CanWashAway(CurrentBlockType)) { - cBlockHandler * Handler = BlockHandler(CurrentBlock); + if (a_PluginInterface.CallHookPlayerBreakingBlock(*a_Player, BlockPos.x, BlockPos.y, BlockPos.z, EntryFace, CurrentBlockType, CurrentBlockMeta)) + { + // Plugin disagrees with the washing-away + return false; + } + + cBlockHandler * Handler = BlockHandler(CurrentBlockType); if (Handler->DoesDropOnUnsuitable()) { cChunkInterface ChunkInterface(a_World->GetChunkMap()); - cBlockInServerPluginInterface PluginInterface(*a_World); - Handler->DropBlock(ChunkInterface, *a_World, PluginInterface, a_Player, a_BlockX, a_BlockY, a_BlockZ); + Handler->DropBlock(ChunkInterface, *a_World, a_PluginInterface, a_Player, BlockPos.x, BlockPos.y, BlockPos.z); } + a_PluginInterface.CallHookPlayerBrokenBlock(*a_Player, BlockPos.x, BlockPos.y, BlockPos.z, EntryFace, CurrentBlockType, CurrentBlockMeta); } - a_World->SetBlock(BlockPos.x, BlockPos.y, BlockPos.z, a_FluidBlock, 0); - - return true; + // Place the actual fluid block: + return a_Player->PlaceBlock(BlockPos.x, BlockPos.y, BlockPos.z, a_FluidBlock, 0); } + bool GetBlockFromTrace(cWorld * a_World, cPlayer * a_Player, Vector3i & a_BlockPos) { class cCallbacks : @@ -190,20 +215,25 @@ public: } - bool GetPlacementCoordsFromTrace(cWorld * a_World, cPlayer * a_Player, Vector3i & a_BlockPos, BLOCKTYPE & a_BlockType) + + bool GetPlacementCoordsFromTrace(cWorld * a_World, cPlayer * a_Player, Vector3i & a_BlockPos, BLOCKTYPE & a_BlockType, NIBBLETYPE & a_BlockMeta, eBlockFace & a_BlockFace) { class cCallbacks : public cBlockTracer::cCallbacks { public: Vector3i m_Pos; - BLOCKTYPE m_ReplacedBlock; + BLOCKTYPE m_ReplacedBlockType; + NIBBLETYPE m_ReplacedBlockMeta; + eBlockFace m_EntryFace; virtual bool OnNextBlock(int a_CBBlockX, int a_CBBlockY, int a_CBBlockZ, BLOCKTYPE a_CBBlockType, NIBBLETYPE a_CBBlockMeta, char a_CBEntryFace) override { if (a_CBBlockType != E_BLOCK_AIR) { - m_ReplacedBlock = a_CBBlockType; + m_ReplacedBlockType = a_CBBlockType; + m_ReplacedBlockMeta = a_CBBlockMeta; + m_EntryFace = static_cast<eBlockFace>(a_CBEntryFace); if (!cFluidSimulator::CanWashAway(a_CBBlockType) && !IsBlockLiquid(a_CBBlockType)) { AddFaceDirection(a_CBBlockX, a_CBBlockY, a_CBBlockZ, (eBlockFace)a_CBEntryFace); // Was an unwashawayable block, can't overwrite it! @@ -219,12 +249,14 @@ public: Vector3d Start(a_Player->GetEyePosition()); Vector3d End(a_Player->GetEyePosition() + a_Player->GetLookVector() * 5); - // cTracer::Trace returns true when whole line was traversed. By returning true when we hit something, we ensure that this never happens if liquid could be placed + // cTracer::Trace returns true when whole line was traversed. By returning true from the callback when we hit something, we ensure that this never happens if liquid could be placed // Use this to judge whether the position is valid if (!Tracer.Trace(Start.x, Start.y, Start.z, End.x, End.y, End.z)) { a_BlockPos = Callbacks.m_Pos; - a_BlockType = Callbacks.m_ReplacedBlock; + a_BlockType = Callbacks.m_ReplacedBlockType; + a_BlockMeta = Callbacks.m_ReplacedBlockMeta; + a_BlockFace = Callbacks.m_EntryFace; return true; } diff --git a/src/Items/ItemDye.h b/src/Items/ItemDye.h index bfcd0bac4..273af826a 100644 --- a/src/Items/ItemDye.h +++ b/src/Items/ItemDye.h @@ -19,7 +19,12 @@ public: { } - virtual bool OnItemUse(cWorld * a_World, cPlayer * a_Player, const cItem & a_Item, int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace) override + + + virtual bool OnItemUse( + cWorld * a_World, cPlayer * a_Player, cBlockPluginInterface & a_PluginInterface, const cItem & a_Item, + int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace + ) override { // Handle growing the plants: if (a_Item.m_ItemDamage == E_META_DYE_WHITE) diff --git a/src/Items/ItemEmptyMap.h b/src/Items/ItemEmptyMap.h index 9238d771b..6e944b4da 100644 --- a/src/Items/ItemEmptyMap.h +++ b/src/Items/ItemEmptyMap.h @@ -27,12 +27,17 @@ public: { } - virtual bool OnItemUse(cWorld * a_World, cPlayer * a_Player, const cItem & a_Item, int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_Dir) override + + + virtual bool OnItemUse( + cWorld * a_World, cPlayer * a_Player, cBlockPluginInterface & a_PluginInterface, const cItem & a_Item, + int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace + ) override { UNUSED(a_Item); UNUSED(a_BlockX); UNUSED(a_BlockZ); - UNUSED(a_Dir); + UNUSED(a_BlockFace); // The map center is fixed at the central point of the 8x8 block of chunks you are standing in when you right-click it. @@ -60,3 +65,7 @@ public: return true; } } ; + + + + diff --git a/src/Items/ItemFishingRod.h b/src/Items/ItemFishingRod.h index 6350a38ba..5bf4b29b7 100644 --- a/src/Items/ItemFishingRod.h +++ b/src/Items/ItemFishingRod.h @@ -93,9 +93,14 @@ public: { } - virtual bool OnItemUse(cWorld * a_World, cPlayer * a_Player, const cItem & a_Item, int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_Dir) override + + + virtual bool OnItemUse( + cWorld * a_World, cPlayer * a_Player, cBlockPluginInterface & a_PluginInterface, const cItem & a_Item, + int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace + ) override { - if (a_Dir != BLOCK_FACE_NONE) + if (a_BlockFace != BLOCK_FACE_NONE) { return false; } diff --git a/src/Items/ItemHandler.cpp b/src/Items/ItemHandler.cpp index 621cf9501..dddd67cdd 100644 --- a/src/Items/ItemHandler.cpp +++ b/src/Items/ItemHandler.cpp @@ -72,7 +72,7 @@ cItemHandler * cItemHandler::m_ItemHandler[2268]; cItemHandler * cItemHandler::GetItemHandler(int a_ItemType) { - if ((a_ItemType < 0) || ((size_t)a_ItemType >= ARRAYCOUNT(m_ItemHandler))) + if ((a_ItemType < 0) || (static_cast<size_t>(a_ItemType) >= ARRAYCOUNT(m_ItemHandler))) { // Either nothing (-1), or bad value, both cases should return the air handler if (a_ItemType < -1) @@ -287,7 +287,7 @@ cItemHandler * cItemHandler::CreateItemHandler(int a_ItemType) void cItemHandler::Deinit() { - for (int i = 0; i < 2267; i++) + for (size_t i = 0; i < ARRAYCOUNT(m_ItemHandler); i++) { delete m_ItemHandler[i]; m_ItemHandler[i] = nullptr; @@ -336,7 +336,7 @@ bool cItemHandler::OnPlayerPlace( if ( BlockHandler(ClickedBlock)->DoesIgnoreBuildCollision() || BlockHandler(ClickedBlock)->DoesIgnoreBuildCollision(&a_Player, ClickedBlockMeta) - ) + ) { cChunkInterface ChunkInterface(a_World.GetChunkMap()); BlockHandler(ClickedBlock)->OnDestroyedByPlayer(ChunkInterface, a_World, &a_Player, a_BlockX, a_BlockY, a_BlockZ); @@ -411,15 +411,19 @@ bool cItemHandler::OnPlayerPlace( -bool cItemHandler::OnItemUse(cWorld * a_World, cPlayer * a_Player, const cItem & a_Item, int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_Dir) +bool cItemHandler::OnItemUse( + cWorld * a_World, cPlayer * a_Player, cBlockPluginInterface & a_PluginInterface, const cItem & a_Item, + int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace +) { UNUSED(a_World); UNUSED(a_Player); + UNUSED(a_PluginInterface); UNUSED(a_Item); UNUSED(a_BlockX); UNUSED(a_BlockY); UNUSED(a_BlockZ); - UNUSED(a_Dir); + UNUSED(a_BlockFace); return false; } @@ -753,7 +757,7 @@ bool cItemHandler::GetPlacementBlockTypeMeta( return false; } - cBlockHandler * BlockH = BlockHandler((BLOCKTYPE)m_ItemType); + cBlockHandler * BlockH = BlockHandler(static_cast<BLOCKTYPE>(m_ItemType)); cChunkInterface ChunkInterface(a_World->GetChunkMap()); return BlockH->GetPlacementBlockTypeMeta( ChunkInterface, a_Player, diff --git a/src/Items/ItemHandler.h b/src/Items/ItemHandler.h index 3ac664798..ec88aeb99 100644 --- a/src/Items/ItemHandler.h +++ b/src/Items/ItemHandler.h @@ -4,6 +4,7 @@ #include "../Defines.h" #include "../Item.h" #include "../Entities/EntityEffect.h" +#include "../Blocks/BlockPluginInterface.h" @@ -56,8 +57,12 @@ public: ); - /** Called when the player tries to use the item (right mouse button). Return false to make the item unusable. DEFAULT: False */ - virtual bool OnItemUse(cWorld * a_World, cPlayer * a_Player, const cItem & a_Item, int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_Dir); + /** Called when the player tries to use the item (right mouse button). + Return false to abort the usage. DEFAULT: False */ + virtual bool OnItemUse( + cWorld * a_World, cPlayer * a_Player, cBlockPluginInterface & a_PluginInterface, const cItem & a_Item, + int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace + ); /** Called when the client sends the SHOOT status in the lclk packet */ diff --git a/src/Items/ItemHoe.h b/src/Items/ItemHoe.h index ae3723323..ce6355ee7 100644 --- a/src/Items/ItemHoe.h +++ b/src/Items/ItemHoe.h @@ -18,9 +18,14 @@ public: { } - virtual bool OnItemUse(cWorld *a_World, cPlayer *a_Player, const cItem & a_Item, int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_Dir) override + + + virtual bool OnItemUse( + cWorld * a_World, cPlayer * a_Player, cBlockPluginInterface & a_PluginInterface, const cItem & a_Item, + int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace + ) override { - if ((a_Dir == BLOCK_FACE_NONE) || (a_BlockY >= cChunkDef::Height)) + if ((a_BlockFace == BLOCK_FACE_NONE) || (a_BlockY >= cChunkDef::Height)) { return false; } diff --git a/src/Items/ItemItemFrame.h b/src/Items/ItemItemFrame.h index 5d22c1cb8..77a5bf47c 100644 --- a/src/Items/ItemItemFrame.h +++ b/src/Items/ItemItemFrame.h @@ -19,21 +19,26 @@ public: } - virtual bool OnItemUse(cWorld *a_World, cPlayer *a_Player, const cItem & a_Item, int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_Dir) override + + + virtual bool OnItemUse( + cWorld * a_World, cPlayer * a_Player, cBlockPluginInterface & a_PluginInterface, const cItem & a_Item, + int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace + ) override { - if ((a_Dir == BLOCK_FACE_NONE) || (a_Dir == BLOCK_FACE_YP) || (a_Dir == BLOCK_FACE_YM)) + if ((a_BlockFace == BLOCK_FACE_NONE) || (a_BlockFace == BLOCK_FACE_YP) || (a_BlockFace == BLOCK_FACE_YM)) { // Client sends this if clicked on top or bottom face return false; } - AddFaceDirection(a_BlockX, a_BlockY, a_BlockZ, a_Dir); // Make sure block that will be occupied is free + AddFaceDirection(a_BlockX, a_BlockY, a_BlockZ, a_BlockFace); // Make sure block that will be occupied is free BLOCKTYPE Block = a_World->GetBlock(a_BlockX, a_BlockY, a_BlockZ); - AddFaceDirection(a_BlockX, a_BlockY, a_BlockZ, a_Dir, true); // We want the clicked block, so go back again + AddFaceDirection(a_BlockX, a_BlockY, a_BlockZ, a_BlockFace, true); // We want the clicked block, so go back again if (Block == E_BLOCK_AIR) { - cItemFrame * ItemFrame = new cItemFrame(a_Dir, a_BlockX, a_BlockY, a_BlockZ); + cItemFrame * ItemFrame = new cItemFrame(a_BlockFace, a_BlockX, a_BlockY, a_BlockZ); if (!ItemFrame->Initialize(*a_World)) { delete ItemFrame; diff --git a/src/Items/ItemLighter.h b/src/Items/ItemLighter.h index 9f98bf85f..4959d52cc 100644 --- a/src/Items/ItemLighter.h +++ b/src/Items/ItemLighter.h @@ -19,7 +19,12 @@ public: { } - virtual bool OnItemUse(cWorld * a_World, cPlayer * a_Player, const cItem & a_Item, int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace) override + + + virtual bool OnItemUse( + cWorld * a_World, cPlayer * a_Player, cBlockPluginInterface & a_PluginInterface, const cItem & a_Item, + int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace + ) override { if (a_BlockFace < 0) { diff --git a/src/Items/ItemLilypad.h b/src/Items/ItemLilypad.h index b9d837384..3440a7516 100644 --- a/src/Items/ItemLilypad.h +++ b/src/Items/ItemLilypad.h @@ -29,7 +29,11 @@ public: } - virtual bool OnItemUse(cWorld * a_World, cPlayer * a_Player, const cItem & a_Item, int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace) override + + virtual bool OnItemUse( + cWorld * a_World, cPlayer * a_Player, cBlockPluginInterface & a_PluginInterface, const cItem & a_Item, + int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace + ) override { if (a_BlockFace > BLOCK_FACE_NONE) { diff --git a/src/Items/ItemMinecart.h b/src/Items/ItemMinecart.h index ed0a4711c..e7d2cf8cd 100644 --- a/src/Items/ItemMinecart.h +++ b/src/Items/ItemMinecart.h @@ -27,9 +27,12 @@ public: - virtual bool OnItemUse(cWorld * a_World, cPlayer * a_Player, const cItem & a_Item, int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_Dir) override + virtual bool OnItemUse( + cWorld * a_World, cPlayer * a_Player, cBlockPluginInterface & a_PluginInterface, const cItem & a_Item, + int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace + ) override { - if (a_Dir < 0) + if (a_BlockFace < 0) { return false; } diff --git a/src/Items/ItemPainting.h b/src/Items/ItemPainting.h index d6f2e24b4..dd35931dd 100644 --- a/src/Items/ItemPainting.h +++ b/src/Items/ItemPainting.h @@ -19,15 +19,20 @@ public: { } - virtual bool OnItemUse(cWorld * a_World, cPlayer * a_Player, const cItem & a_Item, int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_Dir) override + + + virtual bool OnItemUse( + cWorld * a_World, cPlayer * a_Player, cBlockPluginInterface & a_PluginInterface, const cItem & a_Item, + int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace + ) override { - if ((a_Dir == BLOCK_FACE_NONE) || (a_Dir == BLOCK_FACE_YM) || (a_Dir == BLOCK_FACE_YP)) + if ((a_BlockFace == BLOCK_FACE_NONE) || (a_BlockFace == BLOCK_FACE_YM) || (a_BlockFace == BLOCK_FACE_YP)) { // Paintings can't be flatly placed return false; } - AddFaceDirection(a_BlockX, a_BlockY, a_BlockZ, a_Dir); // Make sure block that will be occupied is free + AddFaceDirection(a_BlockX, a_BlockY, a_BlockZ, a_BlockFace); // Make sure block that will be occupied is free BLOCKTYPE Block = a_World->GetBlock(a_BlockX, a_BlockY, a_BlockZ); if (Block == E_BLOCK_AIR) @@ -65,7 +70,7 @@ public: { "BurningSkull" } }; - cPainting * Painting = new cPainting(gPaintingTitlesList[a_World->GetTickRandomNumber(ARRAYCOUNT(gPaintingTitlesList) - 1)].Title, a_Dir, a_BlockX, a_BlockY, a_BlockZ); + cPainting * Painting = new cPainting(gPaintingTitlesList[a_World->GetTickRandomNumber(ARRAYCOUNT(gPaintingTitlesList) - 1)].Title, a_BlockFace, a_BlockX, a_BlockY, a_BlockZ); Painting->Initialize(*a_World); if (!a_Player->IsGameModeCreative()) diff --git a/src/Items/ItemPotion.h b/src/Items/ItemPotion.h index 798573846..a176c591e 100644 --- a/src/Items/ItemPotion.h +++ b/src/Items/ItemPotion.h @@ -26,7 +26,10 @@ public: } - virtual bool OnItemUse(cWorld * a_World, cPlayer * a_Player, const cItem & a_Item, int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_Dir) override + virtual bool OnItemUse( + cWorld * a_World, cPlayer * a_Player, cBlockPluginInterface & a_PluginInterface, const cItem & a_Item, + int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace + ) override { short PotionDamage = a_Item.m_ItemDamage; diff --git a/src/Items/ItemSlab.h b/src/Items/ItemSlab.h index b0b5ce005..3a78cc016 100644 --- a/src/Items/ItemSlab.h +++ b/src/Items/ItemSlab.h @@ -39,10 +39,15 @@ public: int a_CursorX, int a_CursorY, int a_CursorZ ) override { + // Prepare sound effect + AString PlaceSound = cBlockInfo::GetPlaceSound(m_ItemType); + float Volume = 1.0f, Pitch = 0.8f; + // Special slab handling - placing a slab onto another slab produces a dblslab instead: BLOCKTYPE ClickedBlockType; NIBBLETYPE ClickedBlockMeta; a_World.GetBlockTypeMeta(a_BlockX, a_BlockY, a_BlockZ, ClickedBlockType, ClickedBlockMeta); + // If clicked on a half slab directly if ( (ClickedBlockType == m_ItemType) && // Placing the same slab material ((ClickedBlockMeta & 0x07) == a_EquippedItem.m_ItemDamage) // Placing the same slab sub-kind (and existing slab is single) @@ -54,6 +59,7 @@ public: ((ClickedBlockMeta & 0x08) == 0) ) { + a_World.BroadcastSoundEffect(PlaceSound, a_BlockX + 0.5, a_BlockY + 0.5, a_BlockZ + 0.5, Volume, Pitch); return a_Player.PlaceBlock(a_BlockX, a_BlockY, a_BlockZ, m_DoubleSlabBlockType, ClickedBlockMeta & 0x07); } @@ -63,11 +69,28 @@ public: ((ClickedBlockMeta & 0x08) != 0) ) { + a_World.BroadcastSoundEffect(PlaceSound, a_BlockX + 0.5, a_BlockY + 0.5, a_BlockZ + 0.5, Volume, Pitch); return a_Player.PlaceBlock(a_BlockX, a_BlockY, a_BlockZ, m_DoubleSlabBlockType, ClickedBlockMeta & 0x07); } } + // Checking the type of block that should be placed + AddFaceDirection(a_BlockX, a_BlockY, a_BlockZ, a_BlockFace); + BLOCKTYPE PlaceBlockType; + NIBBLETYPE PlaceBlockMeta; + a_World.GetBlockTypeMeta(a_BlockX, a_BlockY, a_BlockZ, PlaceBlockType, PlaceBlockMeta); + // If it's a slab combine into a doubleslab (means that clicked on side, top or bottom of a block adjacent to a half slab) + if ( + (PlaceBlockType == m_ItemType) && // Placing the same slab material + ((PlaceBlockMeta & 0x07) == a_EquippedItem.m_ItemDamage) // Placing the same slab sub-kind (and existing slab is single) + ) + { + a_World.BroadcastSoundEffect(PlaceSound, a_BlockX + 0.5, a_BlockY + 0.5, a_BlockZ + 0.5, Volume, Pitch); + return a_Player.PlaceBlock(a_BlockX, a_BlockY, a_BlockZ, m_DoubleSlabBlockType, PlaceBlockMeta & 0x07); + } + // The slabs didn't combine, use the default handler to place the slab: + AddFaceDirection(a_BlockX, a_BlockY, a_BlockZ, a_BlockFace, true); bool res = super::OnPlayerPlace(a_World, a_Player, a_EquippedItem, a_BlockX, a_BlockY, a_BlockZ, a_BlockFace, a_CursorX, a_CursorY, a_CursorZ); /* diff --git a/src/Items/ItemSpawnEgg.h b/src/Items/ItemSpawnEgg.h index a07e4ef49..b67fe074d 100644 --- a/src/Items/ItemSpawnEgg.h +++ b/src/Items/ItemSpawnEgg.h @@ -19,7 +19,11 @@ public: } - virtual bool OnItemUse(cWorld * a_World, cPlayer * a_Player, const cItem & a_Item, int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace) override + + virtual bool OnItemUse( + cWorld * a_World, cPlayer * a_Player, cBlockPluginInterface & a_PluginInterface, const cItem & a_Item, + int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace + ) override { if (a_BlockFace < 0) { diff --git a/src/Items/ItemThrowable.h b/src/Items/ItemThrowable.h index cdcbdab3b..0e06623fc 100644 --- a/src/Items/ItemThrowable.h +++ b/src/Items/ItemThrowable.h @@ -26,7 +26,11 @@ public: } - virtual bool OnItemUse(cWorld * a_World, cPlayer * a_Player, const cItem & a_Item, int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_Dir) override + + virtual bool OnItemUse( + cWorld * a_World, cPlayer * a_Player, cBlockPluginInterface & a_PluginInterface, const cItem & a_Item, + int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace + ) override { Vector3d Pos = a_Player->GetThrowStartPos(); Vector3d Speed = a_Player->GetLookVector() * m_SpeedCoeff; @@ -128,7 +132,12 @@ public: { } - virtual bool OnItemUse(cWorld * a_World, cPlayer * a_Player, const cItem & a_Item, int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_Dir) override + + + virtual bool OnItemUse( + cWorld * a_World, cPlayer * a_Player, cBlockPluginInterface & a_PluginInterface, const cItem & a_Item, + int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace + ) override { if (a_World->GetBlock(a_BlockX, a_BlockY, a_BlockZ) == E_BLOCK_AIR) { @@ -149,3 +158,7 @@ public: } }; + + + + diff --git a/src/Mobs/Bat.cpp b/src/Mobs/Bat.cpp index c072d4f48..e187e928a 100644 --- a/src/Mobs/Bat.cpp +++ b/src/Mobs/Bat.cpp @@ -9,6 +9,8 @@ cBat::cBat(void) : super("Bat", mtBat, "mob.bat.hurt", "mob.bat.death", 0.5, 0.9) { + SetGravity(-2.0f); + SetAirDrag(0.05f); } diff --git a/src/Mobs/Blaze.cpp b/src/Mobs/Blaze.cpp index 89eeb3709..d4ad24166 100644 --- a/src/Mobs/Blaze.cpp +++ b/src/Mobs/Blaze.cpp @@ -11,6 +11,8 @@ cBlaze::cBlaze(void) : super("Blaze", mtBlaze, "mob.blaze.hit", "mob.blaze.death", 0.6, 1.8) { + SetGravity(-8.0f); + SetAirDrag(0.05f); } diff --git a/src/Mobs/IronGolem.cpp b/src/Mobs/IronGolem.cpp index dae4615e4..b0e76daca 100644 --- a/src/Mobs/IronGolem.cpp +++ b/src/Mobs/IronGolem.cpp @@ -8,7 +8,7 @@ cIronGolem::cIronGolem(void) : - super("IronGolem", mtIronGolem, "mob.IronGolem.hit", "mob.IronGolem.death", 1.4, 2.9) + super("IronGolem", mtIronGolem, "mob.irongolem.hit", "mob.irongolem.death", 1.4, 2.9) { } diff --git a/src/Mobs/Monster.cpp b/src/Mobs/Monster.cpp index a86497753..55d83302a 100644 --- a/src/Mobs/Monster.cpp +++ b/src/Mobs/Monster.cpp @@ -38,6 +38,7 @@ static const struct {mtEnderman, "enderman", "Enderman"}, {mtEnderDragon, "enderdragon", "EnderDragon"}, {mtGhast, "ghast", "Ghast"}, + {mtGiant, "giant", "Giant"}, {mtGuardian, "guardian", "Guardian"}, {mtHorse, "horse", "EntityHorse"}, {mtIronGolem, "irongolem", "VillagerGolem"}, diff --git a/src/OSSupport/File.cpp b/src/OSSupport/File.cpp index 7ad1b3f81..43105b230 100644 --- a/src/OSSupport/File.cpp +++ b/src/OSSupport/File.cpp @@ -456,17 +456,50 @@ AString cFile::ReadWholeFile(const AString & a_FileName) AString cFile::ChangeFileExt(const AString & a_FileName, const AString & a_NewExt) { auto res = a_FileName; + + // If the path separator is the last character of the string, return the string unmodified (refers to a folder): + #if defined(_MSC_VER) + // Find either path separator - MSVC CRT accepts slashes as separators, too + auto LastPathSep = res.find_last_of("/\\"); + #elif defined(_WIN32) + // Windows with different CRTs support only the backslash separator + auto LastPathSep = res.rfind('\\'); + #else + // Linux supports only the slash separator + auto LastPathSep = res.rfind('/'); + #endif + if ((LastPathSep != AString::npos) && (LastPathSep + 1 == res.size())) + { + return res; + } + + // Append or replace the extension: auto DotPos = res.rfind('.'); - if (DotPos == AString::npos) + if ( + (DotPos == AString::npos) || // No dot found + ((LastPathSep != AString::npos) && (LastPathSep > DotPos)) // Last dot is before the last path separator (-> in folder name) + ) { - // No extension, just append it: - res.push_back('.'); + // No extension, just append the new one: + if (!a_NewExt.empty() && (a_NewExt[0] != '.')) + { + // a_NewExt doesn't start with a dot, insert one: + res.push_back('.'); + } res.append(a_NewExt); } else { // Replace existing extension: - res.erase(DotPos + 1, AString::npos); + if (!a_NewExt.empty() && (a_NewExt[0] != '.')) + { + // a_NewExt doesn't start with a dot, keep the current one: + res.erase(DotPos + 1, AString::npos); + } + else + { + res.erase(DotPos, AString::npos); + } res.append(a_NewExt); } return res; @@ -476,6 +509,52 @@ AString cFile::ChangeFileExt(const AString & a_FileName, const AString & a_NewEx +unsigned cFile::GetLastModificationTime(const AString & a_FileName) +{ + struct stat st; + if (stat(a_FileName.c_str(), &st) < 0) + { + return 0; + } + #ifdef _WIN32 + // Windows returns times in local time already + return static_cast<unsigned>(st.st_mtime); + #else + // Linux returns UTC time, convert to local timezone: + return static_cast<unsigned>(mktime(localtime(&st.st_mtime))); + #endif +} + + + + + +AString cFile::GetPathSeparator(void) +{ + #ifdef _WIN32 + return "\\"; + #else + return "/"; + #endif +} + + + + + +AString cFile::GetExecutableExt(void) +{ + #ifdef _WIN32 + return ".exe"; + #else + return ""; + #endif +} + + + + + int cFile::Printf(const char * a_Fmt, ...) { AString buf; diff --git a/src/OSSupport/File.h b/src/OSSupport/File.h index 1cf5c71d7..6ee080480 100644 --- a/src/OSSupport/File.h +++ b/src/OSSupport/File.h @@ -131,6 +131,18 @@ public: a_FileName may contain path specification. */ static AString ChangeFileExt(const AString & a_FileName, const AString & a_NewExt); + /** Returns the last modification time (in current timezone) of the specified file. + The value returned is in the same units as the value returned by time() function. + If the file is not found / accessible, zero is returned. */ + static unsigned GetLastModificationTime(const AString & a_FileName); + + /** Returns the path separator used by the current platform. + Note that the platform / CRT may support additional path separators (such as slashes on Windows), these don't get reported. */ + static AString GetPathSeparator(void); + + /** Returns the customary executable extension used by the current platform. */ + static AString GetExecutableExt(void); + // tolua_end /** Returns the list of all items in the specified folder (files, folders, nix pipes, whatever's there). */ diff --git a/src/OSSupport/TCPLinkImpl.cpp b/src/OSSupport/TCPLinkImpl.cpp index c6f1978ad..ae6ba04f1 100644 --- a/src/OSSupport/TCPLinkImpl.cpp +++ b/src/OSSupport/TCPLinkImpl.cpp @@ -23,7 +23,6 @@ cTCPLinkImpl::cTCPLinkImpl(cTCPLink::cCallbacksPtr a_LinkCallbacks): m_RemotePort(0), m_ShouldShutdown(false) { - LOGD("Created new cTCPLinkImpl at %p with BufferEvent at %p", this, m_BufferEvent); } @@ -38,8 +37,6 @@ cTCPLinkImpl::cTCPLinkImpl(evutil_socket_t a_Socket, cTCPLink::cCallbacksPtr a_L m_RemotePort(0), m_ShouldShutdown(false) { - LOGD("Created new cTCPLinkImpl at %p with BufferEvent at %p", this, m_BufferEvent); - // Update the endpoint addresses: UpdateLocalAddress(); UpdateAddress(a_Address, a_AddrLen, m_RemoteIP, m_RemotePort); @@ -51,7 +48,6 @@ cTCPLinkImpl::cTCPLinkImpl(evutil_socket_t a_Socket, cTCPLink::cCallbacksPtr a_L cTCPLinkImpl::~cTCPLinkImpl() { - LOGD("Deleting cTCPLinkImpl at %p with BufferEvent at %p", this, m_BufferEvent); bufferevent_free(m_BufferEvent); } @@ -216,8 +212,6 @@ void cTCPLinkImpl::WriteCallback(bufferevent * a_BufferEvent, void * a_Self) void cTCPLinkImpl::EventCallback(bufferevent * a_BufferEvent, short a_What, void * a_Self) { - LOGD("cTCPLink event callback for link %p, BEV %p; what = 0x%02x", a_Self, a_BufferEvent, a_What); - ASSERT(a_Self != nullptr); cTCPLinkImplPtr Self = static_cast<cTCPLinkImpl *>(a_Self)->m_Self; diff --git a/src/OSSupport/UDPEndpointImpl.cpp b/src/OSSupport/UDPEndpointImpl.cpp index 31ca107ce..68117e5a0 100644 --- a/src/OSSupport/UDPEndpointImpl.cpp +++ b/src/OSSupport/UDPEndpointImpl.cpp @@ -384,7 +384,7 @@ void cUDPEndpointImpl::Open(UInt16 a_Port) // Failed to create IPv6 socket, create an IPv4 one instead: m_IsMainSockIPv6 = false; err = EVUTIL_SOCKET_ERROR(); - LOGD("Failed to create IPv6 MainSock: %d (%s)", err, evutil_socket_error_to_string(err)); + LOGD("UDP: Failed to create IPv6 MainSock: %d (%s)", err, evutil_socket_error_to_string(err)); m_MainSock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); if (!IsValidSocket(m_MainSock)) { diff --git a/src/Protocol/Protocol18x.cpp b/src/Protocol/Protocol18x.cpp index a1ca25200..0baae00de 100644 --- a/src/Protocol/Protocol18x.cpp +++ b/src/Protocol/Protocol18x.cpp @@ -2106,7 +2106,7 @@ void cProtocol180::HandlePacketLoginStart(cByteBuffer & a_ByteBuffer) void cProtocol180::HandlePacketAnimation(cByteBuffer & a_ByteBuffer) { - m_Client->HandleAnimation(1); // Packet exists solely for arm-swing notification + m_Client->HandleAnimation(0); // Packet exists solely for arm-swing notification } @@ -2675,7 +2675,7 @@ void cProtocol180::ParseItemMetadata(cItem & a_Item, const AString & a_Metadata) if (!NBT.IsValid()) { AString HexDump; - CreateHexDump(HexDump, a_Metadata.data(), a_Metadata.size(), 16); + CreateHexDump(HexDump, a_Metadata.data(), std::max<size_t>(a_Metadata.size(), 1024), 16); LOGWARNING("Cannot parse NBT item metadata: (" SIZE_T_FMT " bytes)\n%s", a_Metadata.size(), HexDump.c_str()); return; } 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/UI/Window.cpp b/src/UI/Window.cpp index bb2e2a807..d1c08acec 100644 --- a/src/UI/Window.cpp +++ b/src/UI/Window.cpp @@ -21,19 +21,23 @@ -char cWindow::m_WindowIDCounter = 1; +Byte cWindow::m_WindowIDCounter = 0; cWindow::cWindow(WindowType a_WindowType, const AString & a_WindowTitle) : - m_WindowID((++m_WindowIDCounter) % 127), + m_WindowID(static_cast<char>((++m_WindowIDCounter) % 127)), m_WindowType(a_WindowType), m_WindowTitle(a_WindowTitle), m_IsDestroyed(false), m_Owner(nullptr) { + // The window ID is signed in protocol 1.7, unsigned in protocol 1.8. Keep out of trouble by using only 7 bits: + // Ref.: http://forum.mc-server.org/showthread.php?tid=1876 + ASSERT((m_WindowID >= 0) && (m_WindowID < 127)); + if (a_WindowType == wtInventory) { m_WindowID = 0; diff --git a/src/UI/Window.h b/src/UI/Window.h index 9821aade1..156028465 100644 --- a/src/UI/Window.h +++ b/src/UI/Window.h @@ -185,7 +185,7 @@ protected: cWindowOwner * m_Owner; - static char m_WindowIDCounter; + static Byte m_WindowIDCounter; /// Sets the internal flag as "destroyed"; notifies the owner that the window is destroying virtual void Destroy(void); diff --git a/src/WebAdmin.cpp b/src/WebAdmin.cpp index 13cf3cc41..484626de3 100644 --- a/src/WebAdmin.cpp +++ b/src/WebAdmin.cpp @@ -234,7 +234,7 @@ void cWebAdmin::HandleWebadminRequest(cHTTPConnection & a_Connection, cHTTPReque bool ShouldWrapInTemplate = ((BareURL.length() > 1) && (BareURL[1] != '~')); // Retrieve the request data: - cWebadminRequestData * Data = (cWebadminRequestData *)(a_Request.GetUserData()); + cWebadminRequestData * Data = reinterpret_cast<cWebadminRequestData *>(a_Request.GetUserData()); if (Data == nullptr) { a_Connection.SendStatusAndReason(500, "Bad UserData"); @@ -244,6 +244,7 @@ void cWebAdmin::HandleWebadminRequest(cHTTPConnection & a_Connection, cHTTPReque // Wrap it all up for the Lua call: AString Template; HTTPTemplateRequest TemplateRequest; + TemplateRequest.Request.URL = a_Request.GetURL(); TemplateRequest.Request.Username = a_Request.GetAuthUsername(); TemplateRequest.Request.Method = a_Request.GetMethod(); TemplateRequest.Request.Path = BareURL.substr(1); @@ -464,10 +465,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 +490,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) diff --git a/src/WebAdmin.h b/src/WebAdmin.h index 1e1a9bfa9..4dbcc57a6 100644 --- a/src/WebAdmin.h +++ b/src/WebAdmin.h @@ -50,9 +50,18 @@ struct HTTPRequest typedef std::map< std::string, std::string > StringStringMap; typedef std::map< std::string, HTTPFormData > FormDataMap; + /** The entire URL presented to the HTTP server. */ + AString URL; + + /** HTTP method used for the request ("GET", "POST" etc.) */ AString Method; + + /** The Path part of the request's URL (excluding GET params). */ AString Path; + + /** Name of the logged-in user. Empty if not logged in. */ AString Username; + // tolua_end /** Parameters given in the URL, after the questionmark */ diff --git a/src/World.cpp b/src/World.cpp index 4480013c3..a088f6eb1 100644 --- a/src/World.cpp +++ b/src/World.cpp @@ -812,7 +812,7 @@ void cWorld::InitialiseGeneratorDefaults(cIniFile & a_IniFile) a_IniFile.GetValueSet("Generator", "HeightGen", "Flat"); a_IniFile.GetValueSet("Generator", "FlatHeight", "128"); a_IniFile.GetValueSet("Generator", "CompositionGen", "Nether"); - a_IniFile.GetValueSet("Generator", "Finishers", "SoulsandRims, WormNestCaves, BottomLava, LavaSprings, NetherClumpFoliage, NetherOreNests, NetherForts, PreSimulator"); + a_IniFile.GetValueSet("Generator", "Finishers", "SoulsandRims, WormNestCaves, BottomLava, LavaSprings, NetherClumpFoliage, NetherOreNests, NetherForts, GlowStone, PreSimulator"); a_IniFile.GetValueSet("Generator", "BottomLavaHeight", "30"); break; } |