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 From 270d79d47be1396b2ab03c2447544c64997b28db Mon Sep 17 00:00:00 2001 From: madmaxoft Date: Fri, 27 Dec 2013 14:46:07 +0100 Subject: APIDump: Fixed the WritingPlugin article. The code is no longer weirdly indented in the browser, and links are relative to the API docs root. --- .../Plugins/APIDump/Writing-a-MCServer-plugin.html | 205 +++++++++++---------- 1 file changed, 107 insertions(+), 98 deletions(-) (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 index 3ab997dcd..8d74051a6 100644 --- a/MCServer/Plugins/APIDump/Writing-a-MCServer-plugin.html +++ b/MCServer/Plugins/APIDump/Writing-a-MCServer-plugin.html @@ -2,7 +2,6 @@ - MCS Plugin Tutorial @@ -35,26 +34,26 @@ 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
+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. @@ -72,7 +71,7 @@

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. + 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 @@ -84,7 +83,7 @@ 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)
+cPluginManager.AddHook(cPluginManager.HOOK_NAME_HERE, FunctionNameToBeCalled)
 			

What does this code do? @@ -98,22 +97,22 @@ 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
+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 @@ -126,11 +125,11 @@ 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)")
+-- 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? @@ -171,17 +170,17 @@ 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")
+-- 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 @@ -193,51 +192,63 @@ 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
+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) then
+		-- There was more or less than one argument (excluding the "/explode" bit)
+		-- Send the proper usage to the player and exit
+		SendMessage(Player, "Usage: /explode [playername]")
+		return true
+	end
+
+	-- Create a callback ExplodePlayer with parameter Explodee, which MCS calls for every player on the server
+	local HasExploded = false
+	local ExplodePlayer = function(Explodee)
+		-- If the player we are currently at is the one we specified as the parameter
+		if (Explodee:GetName() == Split[2]) then
+			-- Create an explosion at the same position as they are; see API docs for further details of this function
+			Player:GetWorld():DoExplosionAt(Explodee:GetPosX(), Explodee:GetPosY(), Explodee:GetPosZ(), false, esPlugin)
+			SendMessageSuccess(Player, Split[2] .. " was successfully exploded")
+			HasExploded = true;
+			return true -- Signalize to MCS that we do not need to call this callback for any more players
+		end
+	end
+	
+	-- Tell MCS to loop through all players and call the callback above with the Player object it has found
+	cRoot:Get():FindAndDoWithPlayer(Split[2], ExplodePlayer)
+	
+	if not(HasExploded) then
+		-- We have not broken out so far, therefore, the player must not exist, send failure
+		SendMessageFailure(Player, Split[2] .. " was not found")
+	end
+	
+	return true
+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! + and finally, the API documentation is your friend!

Happy coding ;) @@ -247,7 +258,5 @@ prettyPrint(); -


-
This tutorial was brought you by Aperture Science, in conjunction with McDonalds Enterprises.
-- cgit v1.2.3 From 9689896f9c113b4ee2e3d2ca29659cc8b7215686 Mon Sep 17 00:00:00 2001 From: madmaxoft Date: Fri, 27 Dec 2013 20:48:11 +0100 Subject: APIDump: Articles eplicitly declare being UTF-8. This fixes display in firefox. --- MCServer/Plugins/APIDump/Writing-a-MCServer-plugin.html | 1 + 1 file changed, 1 insertion(+) (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 index 8d74051a6..50e39d533 100644 --- a/MCServer/Plugins/APIDump/Writing-a-MCServer-plugin.html +++ b/MCServer/Plugins/APIDump/Writing-a-MCServer-plugin.html @@ -7,6 +7,7 @@ +
-- cgit v1.2.3