From d41f724a4034724f0c1da72cad15bd0a274ec62d Mon Sep 17 00:00:00 2001 From: Tiger Wang Date: Thu, 26 Dec 2013 15:11:48 +0000 Subject: Writing a plugin APIDump article (#382) --- .../Plugins/APIDump/Writing-a-MCServer-plugin.html | 253 +++++++++++++++++++++ 1 file changed, 253 insertions(+) create mode 100644 MCServer/Plugins/APIDump/Writing-a-MCServer-plugin.html (limited to 'MCServer/Plugins/APIDump/Writing-a-MCServer-plugin.html') diff --git a/MCServer/Plugins/APIDump/Writing-a-MCServer-plugin.html b/MCServer/Plugins/APIDump/Writing-a-MCServer-plugin.html new file mode 100644 index 000000000..3ab997dcd --- /dev/null +++ b/MCServer/Plugins/APIDump/Writing-a-MCServer-plugin.html @@ -0,0 +1,253 @@ + + + + + + MCS Plugin Tutorial + + + + + + +
+

Writing a MCServer plugin

+

+ This article will explain how to write a basic plugin. It details basic requirements + for a plugin, explains how to register a hook and bind a command, and gives plugin + standards details. +

+

+ Let us begin. In order to begin development, we must firstly obtain a compiled copy + of MCServer, and make sure that the Core plugin is within the Plugins folder, and activated. + Core handles much of the MCServer end-user experience and is a necessary component of + plugin development, as necessary plugin components depend on sone of its functions. +

+

+ Next, we must obtain a copy of CoreMessaging.lua. This can be found + here. + This is used to provide messaging support that is compliant with MCServer standards. +

+

Creating the basic template

+

+ Plugins are written in Lua. Therefore, create a new Lua file. You can create as many files as you wish, with + any filename - MCServer bungs them all together at runtime, however, let us create a file called main.lua for now. + Format it like so: +

+
+			local PLUGIN
+			
+			function Initialize( Plugin )
+				Plugin:SetName( "DerpyPlugin" )
+				Plugin:SetVersion( 1 )
+				
+				PLUGIN = Plugin
+
+				-- Hooks
+		
+				local PluginManager = cPluginManager:Get()
+				-- Command bindings
+
+				LOG( "Initialised " .. Plugin:GetName() .. " v." .. Plugin:GetVersion() )
+				return true
+			end
+			
+			function OnDisable()
+				LOG(PLUGIN:GetName() .. " is shutting down...")
+			end
+			
+

+ Now for an explanation of the basics. +

+ Be sure to return true for this function, else MCS thinks you plugin had failed to initialise and prints a stacktrace with an error message. +

+ +

Registering hooks

+

+ Hooks are things that MCServer calls when an internal event occurs. For example, a hook is fired when a player places a block, moves, + logs on, eats, and many other things. For a full list, see the API documentation. +

+

+ A hook can be either informative or overridable. In any case, returning false will not trigger a response, but returning true will cancel + the hook and prevent it from being propagated further to other plugins. An overridable hook simply means that there is visible behaviour + to a hook's cancellation, such as a chest being prevented from being opened. There are some exceptions to this where only changing the value the + hook passes has an effect, and not the actual return value, an example being the HOOK_KILLING hook. See the API docs for details. +

+

+ To register a hook, insert the following code template into the "-- Hooks" area in the previous code example. +

+
+				cPluginManager.AddHook(cPluginManager.HOOK_NAME_HERE, FunctionNameToBeCalled)
+			
+

+ What does this code do? +

+ What about the third parameter, you ask? Well, it is the name of the function that MCServer calls when the hook fires. It is in this + function that you should handle or cancel the hook. +

+

+ So in total, this is a working representation of what we have so far covered. +

+
+			function Initialize( Plugin )
+				Plugin:SetName( "DerpyPlugin" )
+				Plugin:SetVersion( 1 )
+
+				cPluginManager.AddHook(cPluginManager.HOOK_PLAYER_MOVING, OnPlayerMoving)
+		
+				local PluginManager = cPluginManager:Get()
+				-- Command bindings
+
+				LOG( "Initialised " .. Plugin:GetName() .. " v." .. Plugin:GetVersion() )
+				return true
+			end
+			
+			function OnPlayerMoving(Player) -- See API docs for parameters of all hooks
+				return true -- Prohibit player movement, see docs for whether a hook is cancellable
+			end
+			
+

+ So, that code stops the player from moving. Not particularly helpful, but yes :P. Note that ALL documentation is available + on the main API docs page, so if ever in doubt, go there. +

+

Binding a command

+

Format

+

+ So now we know how to hook into MCServer, how do we bind a command, such as /explode, for a player to type? That is more complicated. + We firstly add this template to the "-- Command bindings" section of the initial example: +

+
+				-- ADD THIS IF COMMAND DOES NOT REQUIRE A PARAMETER (/explode)
+				PluginManager:BindCommand("/commandname", "permissionnode", FunctionToCall, " - Description of command")
+				
+				-- ADD THIS IF COMMAND DOES REQUIRE A PARAMETER (/explode Notch)
+				PluginManager:BindCommand("/commandname", "permissionnode", FunctionToCall, " ~ Description of command and parameter(s)")
+			
+

+ What does it do, and why are there two? +

+ The command name is pretty self explanatory. The permission node is basically just a string that the player's group needs to have, so you can have anything in there, + though we recommend a style such as "derpyplugin.explode". The function to call is like the ones with Hooks, but with some fixed parameters which we will come on to later, + and the description is a description of the command which is shown when "/help" is typed. +

+

+ So why are there two? Standards. A plugin that accepts a parameter MUST use a format for the description of " ~ Description of command and parms" + whereas a command that doesn't accept parameters MUST use " - Description of command" instead. Be sure to put a space before the tildes or dashes. + Additionally, try to keep the description brief and on one line on the client. +

+

Parameters

+

+ What parameters are in the function MCServer calls when the command is executed? A 'Split' array and a 'Player' object. +

+

The Split Array

+

+ The Split array is an array of all text submitted to the server, including the actual command. MCServer automatically splits the text into the array, + so plugin authors do not need to worry about that. An example of a Split array passed for the command, "/derp zubby explode" would be:

+    /derp (Split[1])
+    zubby (Split[2])
+    explode (Split[3])
+
+    The total amount of parameters passed were: 3 (#Split) +

+

The Player Object and sending them messages

+

+ The Player object is basically a pointer to the player that has executed the command. You can do things with them, but most common is sending + a message. Again, see the API documentation for fuller details. But, you ask, how do we send a message to the client? +

+

+ Remember that copy of CoreMessaging.lua that we downloaded earlier? Make sure that file is in your plugin folder, along with the main.lua file you are typing + your code in. Since MCS brings all the files together on JIT compile, we don't need to worry about requiring any files or such. Simply follow the below examples: +

+
+				-- Format: §yellow[INFO] §white%text% (yellow [INFO], white text following it)
+				-- Use: Informational message, such as instructions for usage of a command
+				SendMessage(Player, "Usage: /explode [player]")
+				
+				-- Format: §green[INFO] §white%text% (green [INFO] etc.)
+				-- Use: Success message, like when a command executes successfully
+				SendMessageSuccess(Player, "Notch was blown up!")
+				
+				-- Format: §rose[INFO] §white%text% (rose coloured [INFO] etc.)
+				-- Use: Failure message, like when a command was entered correctly but failed to run, such as when the destination player wasn't found in a /tp command
+				SendMessageFailure(Player, "Player Salted was not found")
+			
+

+ Those are the basics. If you want to output text to the player for a reason other than the three listed above, and you want to colour the text, simply concatenate + "cChatColor.*colorhere*" with your desired text, concatenate being "..". See the API docs for more details of all colours, as well as details on logging to console with + LOG("Text"). +

+

Final example and conclusion

+

+ So, a working example that checks the validity of a command, and blows up a player, and also refuses pickup collection to players with >100ms ping. +

+
+			function Initialize( Plugin )
+				Plugin:SetName( "DerpyPluginThatBlowsPeopleUp" )
+				Plugin:SetVersion( 9001 )
+		
+				local PluginManager = cPluginManager:Get()
+				PluginManager:BindCommand("/explode", "derpyplugin.explode", Explode, " ~ Explode a player");
+
+				cPluginManager.AddHook(cPluginManager.HOOK_COLLECTING_PICKUP, OnCollectingPickup)
+
+				LOG( "Initialised " .. Plugin:GetName() .. " v." .. Plugin:GetVersion() )
+				return true
+			end
+			
+			function Explode(Split, Player)
+				if #Split ~= 2
+					SendMessage(Player, "Usage: /explode [playername]") -- There was more or less than one argument (excluding the /explode bit)
+				else
+					local ExplodePlayer = function(Explodee) -- Create a callback ExplodePlayer with parameter Explodee, which MCS calls for every player on the server
+						if (Explodee:GetName() == Split[2] then -- If the player we are currently at is the one we specified as the parameter...
+							Player:GetWorld():DoExplosionAt(Explodee:GetPosX(), Explodee:GetPosY(), Explodee:GetPosZ(), false, esPlugin) -- Explode 'em; see API docs for further details of this function
+							SendMessageSuccess(Player, Split[2] .. " was successfully exploded") -- Success!
+							return true -- Break out
+						end
+					end
+					
+					cRoot:Get():FindAndDoWithPlayer(Split[2], ExplodePlayer) -- Tells MCS to loop through all players and call the callback above with the Player object it has found
+					
+					SendMessageFailure(Player, Split[2] .. " was not found") -- We have not broken out so far, therefore, the player must not exist, send failure
+				end
+				
+				return true -- Concluding return
+			end
+			
+			function OnCollectingPickup(Player, Pickup) -- Again, see the API docs for parameters of all hooks. In this case, it is a Player and Pickup object
+				if (Player:GetClientHandle():GetPing() > 100) then -- Get ping of player, in milliseconds
+					return true -- Discriminate against high latency - you don't get drops :D
+				else
+					return false -- You do get the drops! Yay~
+				end
+			end
+			
+

+ Make sure to read the comments for a description of what everything does. Also be sure to return true for all command handlers, unless you want MCS to print out an "Unknown command" message + when the command gets executed :P. Make sure to follow standards - use CoreMessaging.lua functions for messaging, dashes for no parameter commands and tildes for vice versa, + and finally, the API documentation is your friend! +

+

+ Happy coding ;) +

+ + +
+
+ + + -- cgit v1.2.3