From 8c6337ff202d81b65cb45b81c60b5d780853e6e7 Mon Sep 17 00:00:00 2001 From: Alexander Harkness Date: Tue, 24 Dec 2013 13:25:03 +0000 Subject: Removed documentation for cChatColour:MakeColor() --- MCServer/Plugins/APIDump/APIDesc.lua | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) (limited to 'MCServer/Plugins/APIDump') diff --git a/MCServer/Plugins/APIDump/APIDesc.lua b/MCServer/Plugins/APIDump/APIDesc.lua index 5bc4a5f39..d69dd14cd 100644 --- a/MCServer/Plugins/APIDump/APIDesc.lua +++ b/MCServer/Plugins/APIDump/APIDesc.lua @@ -1,4 +1,3 @@ - -- APIDesc.lua -- Contains the API objects' descriptions @@ -308,18 +307,15 @@ g_APIDesc = A wrapper class for constants representing colors or effects. ]], - Functions = - { - MakeColor = { Params = "ColorCodeConstant", Return = "string", Notes = "Creates the complete color-code-sequence from the color or effect constant" }, - }, + Functions = {}, Constants = { Black = { Notes = "" }, Blue = { Notes = "" }, Bold = { Notes = "" }, - Color = { Notes = "The first character of the color-code-sequence, §" }, + Color = { Notes = "The first character of the color-code-sequence, �" }, DarkPurple = { Notes = "" }, - Delimiter = { Notes = "The first character of the color-code-sequence, §" }, + Delimiter = { Notes = "The first character of the color-code-sequence, �" }, Gold = { Notes = "" }, Gray = { Notes = "" }, Green = { Notes = "" }, -- cgit v1.2.3 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) --- MCServer/Plugins/APIDump/WebWorldThreads.html | 100 ++++---- .../Plugins/APIDump/Writing-a-MCServer-plugin.html | 253 +++++++++++++++++++++ MCServer/Plugins/APIDump/main.css | 6 + 3 files changed, 310 insertions(+), 49 deletions(-) create mode 100644 MCServer/Plugins/APIDump/Writing-a-MCServer-plugin.html (limited to 'MCServer/Plugins/APIDump') diff --git a/MCServer/Plugins/APIDump/WebWorldThreads.html b/MCServer/Plugins/APIDump/WebWorldThreads.html index 2f117ab7c..6caf40e6d 100644 --- a/MCServer/Plugins/APIDump/WebWorldThreads.html +++ b/MCServer/Plugins/APIDump/WebWorldThreads.html @@ -8,62 +8,64 @@ -

Webserver vs World threads

-

- This article will explain the threading issues that arise between the webserver and world threads are of concern to plugin authors.

-

- Generally, plugins that provide webadmin pages should be quite careful about their interactions. Most operations on MCServer objects requires synchronization, that MCServer provides automatically and transparently to plugins - when a block is written, the chunkmap is locked, or when an entity is being manipulated, the entity list is locked. Each plugin also has a mutex lock, so that only one thread at a time may be executing plugin code.

-

- This locking can be a source of deadlocks for plugins that are not written carefully.

+
+

Webserver vs World threads

+

+ This article will explain the threading issues that arise between the webserver and world threads are of concern to plugin authors.

+

+ Generally, plugins that provide webadmin pages should be quite careful about their interactions. Most operations on MCServer objects requires synchronization, that MCServer provides automatically and transparently to plugins - when a block is written, the chunkmap is locked, or when an entity is being manipulated, the entity list is locked. Each plugin also has a mutex lock, so that only one thread at a time may be executing plugin code.

+

+ This locking can be a source of deadlocks for plugins that are not written carefully.

-

Example scenario

-

Consider the following example. A plugin provides a webadmin page that allows the admin to kick players off the server. When the admin presses the "Kick" button, the plugin calls cWorld:DoWithPlayer() with a callback to kick the player. Everything seems to be working fine now.

-

- A new feature is developed in the plugin, now the plugin adds a new in-game command so that the admins can kick players while they're playing the game. The plugin registers a command callback with cPluginManager.AddCommand(). Now there are problems bound to happen.

-

- Suppose that two admins are in, one is using the webadmin and the other is in-game. Both try to kick a player at the same time. The webadmin locks the plugin, so that it can execute the plugin code, but right at this moment the OS switches threads. The world thread locks the world so that it can access the list of in-game commands, receives the in-game command, it tries to lock the plugin. The plugin is already locked, so the world thread is put on hold. After a while, the webadmin thread is woken up again and continues processing. It tries to lock the world so that it can traverse the playerlist, but the lock is already held by the world thread. Now both threads are holding one lock each and trying to grab the other lock, and are therefore deadlocked.

+

Example scenario

+

Consider the following example. A plugin provides a webadmin page that allows the admin to kick players off the server. When the admin presses the "Kick" button, the plugin calls cWorld:DoWithPlayer() with a callback to kick the player. Everything seems to be working fine now.

+

+ A new feature is developed in the plugin, now the plugin adds a new in-game command so that the admins can kick players while they're playing the game. The plugin registers a command callback with cPluginManager.AddCommand(). Now there are problems bound to happen.

+

+ Suppose that two admins are in, one is using the webadmin and the other is in-game. Both try to kick a player at the same time. The webadmin locks the plugin, so that it can execute the plugin code, but right at this moment the OS switches threads. The world thread locks the world so that it can access the list of in-game commands, receives the in-game command, it tries to lock the plugin. The plugin is already locked, so the world thread is put on hold. After a while, the webadmin thread is woken up again and continues processing. It tries to lock the world so that it can traverse the playerlist, but the lock is already held by the world thread. Now both threads are holding one lock each and trying to grab the other lock, and are therefore deadlocked.

-

How to avoid the deadlock

-

- There are two main ways to avoid such a deadlock. The first approach is using tasks: Everytime you need to execute a task inside a world, instead of executing it, queue it, using cWorld:QueueTask(). This handy utility can will call the given function inside the world's TickThread, thus eliminating the deadlock, because now there's only one thread. However, this approach will not let you get data back. You cannot query the player list, or the entities, or anything - because when the task runs, the webadmin page has already been served to the browser.

-

- To accommodate this, you'll need to use the second approach - preparing and caching data in the tick thread, possibly using callbacks. This means that the plugin will have global variables that will store the data, and update those variables when the data changes; then the webserver thread will only read those variables, instead of calling the world functions. For example, if a webpage was to display the list of currently connected players, the plugin should maintain a global variable, g_WorldPlayers, which would be a table of worlds, each item being a list of currently connected players. The webadmin handler would read this variable and create the page from it; the plugin would use HOOK_PLAYER_JOINED and HOOK_DISCONNECT to update the variable.

+

How to avoid the deadlock

+

+ There are two main ways to avoid such a deadlock. The first approach is using tasks: Everytime you need to execute a task inside a world, instead of executing it, queue it, using cWorld:QueueTask(). This handy utility can will call the given function inside the world's TickThread, thus eliminating the deadlock, because now there's only one thread. However, this approach will not let you get data back. You cannot query the player list, or the entities, or anything - because when the task runs, the webadmin page has already been served to the browser.

+

+ To accommodate this, you'll need to use the second approach - preparing and caching data in the tick thread, possibly using callbacks. This means that the plugin will have global variables that will store the data, and update those variables when the data changes; then the webserver thread will only read those variables, instead of calling the world functions. For example, if a webpage was to display the list of currently connected players, the plugin should maintain a global variable, g_WorldPlayers, which would be a table of worlds, each item being a list of currently connected players. The webadmin handler would read this variable and create the page from it; the plugin would use HOOK_PLAYER_JOINED and HOOK_DISCONNECT to update the variable.

-

What to avoid

-

- Now that we know what the danger is and how to avoid it, how do we know if our code is susceptible?

-

- The general rule of thumb is to avoid calling any functions that read or write lists of things in the webserver thread. This means most ForEach() and DoWith() functions. Only cRoot:ForEachWorld() is safe - because the list of worlds is not expected to change, so it is not guarded by a mutex. Getting and setting world's blocks is, naturally, unsafe, as is calling other plugins, or creating entities.

+

What to avoid

+

+ Now that we know what the danger is and how to avoid it, how do we know if our code is susceptible?

+

+ The general rule of thumb is to avoid calling any functions that read or write lists of things in the webserver thread. This means most ForEach() and DoWith() functions. Only cRoot:ForEachWorld() is safe - because the list of worlds is not expected to change, so it is not guarded by a mutex. Getting and setting world's blocks is, naturally, unsafe, as is calling other plugins, or creating entities.

-

Example

- The Core has the facility to kick players using the web interface. It used the following code for the kicking (inside the webadmin handler): -
-		local KickPlayerName = Request.Params["players-kick"]
-		local FoundPlayerCallback = function(Player)
-		  if (Player:GetName() == KickPlayerName) then
-			Player:GetClientHandle():Kick("You were kicked from the game!")
-		  end
-		end
-		cRoot:Get():FindAndDoWithPlayer(KickPlayerName, FoundPlayerCallback)
-		
- The cRoot:FindAndDoWithPlayer() is unsafe and could have caused a deadlock. The new solution is queue a task; but since we don't know in which world the player is, we need to queue the task to all worlds: -
-		cRoot:Get():ForEachWorld(    -- For each world...
-		  function(World)
-			World:QueueTask(         -- ... queue a task...
-			  function(a_World)
-				a_World:DoWithPlayer(KickPlayerName,  -- ... to walk the playerlist...
-				  function (a_Player)
-					a_Player:GetClientHandle():Kick("You were kicked from the game!")  -- ... and kick the player
+			

Example

+ The Core has the facility to kick players using the web interface. It used the following code for the kicking (inside the webadmin handler): +
+			local KickPlayerName = Request.Params["players-kick"]
+			local FoundPlayerCallback = function(Player)
+			  if (Player:GetName() == KickPlayerName) then
+				Player:GetClientHandle():Kick("You were kicked from the game!")
+			  end
+			end
+			cRoot:Get():FindAndDoWithPlayer(KickPlayerName, FoundPlayerCallback)
+			
+ The cRoot:FindAndDoWithPlayer() is unsafe and could have caused a deadlock. The new solution is queue a task; but since we don't know in which world the player is, we need to queue the task to all worlds: +
+			cRoot:Get():ForEachWorld(    -- For each world...
+			  function(World)
+				World:QueueTask(         -- ... queue a task...
+				  function(a_World)
+					a_World:DoWithPlayer(KickPlayerName,  -- ... to walk the playerlist...
+					  function (a_Player)
+						a_Player:GetClientHandle():Kick("You were kicked from the game!")  -- ... and kick the player
+					  end
+					)
 				  end
 				)
 			  end
 			)
-		  end
-		)
-		
- +
+ +
\ No newline at end of file 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. +

    +
  • function Initialize is called on plugin startup. It is the place where the plugin is set up.
  • +
  • Plugin:SetName sets the name of the plugin.
  • +
  • Plugin:SetVersion sets the revision number of the plugin. This must be an integer.
  • +
  • LOG logs to console a message, in this case, it prints that the plugin was initialised.
  • +
  • The PLUGIN variable just stores this plugin's object, so GetName() can be called in OnDisable (as no Plugin parameter is passed there, contrary to Initialize).
  • +
  • function OnDisable is called when the plugin is disabled, commonly when the server is shutting down. Perform cleanup and logging here.
  • +
+ 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? +

    +
  • cPluginManager.AddHook registers the hook. The hook name is the second parameter. See the previous API documentation link for a list of all hooks.
  • +
+ 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? +

    +
  • PluginManager:BindCommand binds a command. It takes the command name (with a slash), the permission a player needs to execute the command, the function + to call when the command is executed, and a description of the command.
  • +
+ 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 ;) +

+ + +
+
+
This tutorial was brought you by Aperture Science, in conjunction with McDonalds Enterprises.
+ + diff --git a/MCServer/Plugins/APIDump/main.css b/MCServer/Plugins/APIDump/main.css index 5cc603a3f..797079873 100644 --- a/MCServer/Plugins/APIDump/main.css +++ b/MCServer/Plugins/APIDump/main.css @@ -49,6 +49,12 @@ header font-family: Segoe UI Light, Helvetica; } +footer +{ + text-align: center; + font-family: Segoe UI Light, Helvetica; +} + #content { padding: 0px 25px 25px 25px; -- cgit v1.2.3 From 1a3a291bde12ad438bd95ea5df3c7b647df550d9 Mon Sep 17 00:00:00 2001 From: madmaxoft Date: Fri, 27 Dec 2013 14:32:40 +0100 Subject: APIDump: Fixed bad for-loop. How did I not see this before? --- MCServer/Plugins/APIDump/main_APIDump.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'MCServer/Plugins/APIDump') diff --git a/MCServer/Plugins/APIDump/main_APIDump.lua b/MCServer/Plugins/APIDump/main_APIDump.lua index e5c8b9c30..06dd451d7 100644 --- a/MCServer/Plugins/APIDump/main_APIDump.lua +++ b/MCServer/Plugins/APIDump/main_APIDump.lua @@ -232,11 +232,11 @@ end function DumpAPIHtml() LOG("Dumping all available functions and constants to API subfolder..."); - LOG("Moving static files.."); + LOG("Copying static files.."); cFile:CreateFolder("API/Static"); local localFolder = g_Plugin:GetLocalFolder(); - for k, v in cFile:GetFolderContents(localFolder .. "/Static") do - cFile:Copy(localFolder .. "/Static/" .. v, "API/Static/" .. v); + for idx, fnam in ipairs(cFile:GetFolderContents(localFolder .. "/Static")) do + cFile:Copy(localFolder .. "/Static/" .. fnam, "API/Static/" .. fnam); end LOG("Creating API tables..."); -- cgit v1.2.3 From f0ca18d72ab37521b2352d06232a4e1a0f5c38f4 Mon Sep 17 00:00:00 2001 From: madmaxoft Date: Fri, 27 Dec 2013 14:33:15 +0100 Subject: APIDump: Added the WritingPlugin article to the exported pages. --- MCServer/Plugins/APIDump/APIDesc.lua | 1 + 1 file changed, 1 insertion(+) (limited to 'MCServer/Plugins/APIDump') diff --git a/MCServer/Plugins/APIDump/APIDesc.lua b/MCServer/Plugins/APIDump/APIDesc.lua index d69dd14cd..f08f83698 100644 --- a/MCServer/Plugins/APIDump/APIDesc.lua +++ b/MCServer/Plugins/APIDump/APIDesc.lua @@ -2760,6 +2760,7 @@ end ExtraPages = { -- No sorting is provided for these, they will be output in the same order as defined here + { FileName = "Writing-a-MCServer-plugin.html", Title = "Writing a MCServer plugin" }, { FileName = "WebWorldThreads.html", Title = "Webserver vs World threads" }, } } ; -- 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') 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 daeb37b27b9732bd34e02566f708f961bb6003a5 Mon Sep 17 00:00:00 2001 From: madmaxoft Date: Fri, 27 Dec 2013 15:01:49 +0100 Subject: APIDump: Extra pages renamed to articles and moved to front. --- MCServer/Plugins/APIDump/main_APIDump.lua | 63 +++++++++++++++++++------------ 1 file changed, 39 insertions(+), 24 deletions(-) (limited to 'MCServer/Plugins/APIDump') diff --git a/MCServer/Plugins/APIDump/main_APIDump.lua b/MCServer/Plugins/APIDump/main_APIDump.lua index 06dd451d7..c36285099 100644 --- a/MCServer/Plugins/APIDump/main_APIDump.lua +++ b/MCServer/Plugins/APIDump/main_APIDump.lua @@ -229,6 +229,32 @@ end +local function WriteArticles(f) + f:write([[ +

Articles

+

The following articles provide various extra information on plugin development

+
    + ]]); + for i, extra in ipairs(g_APIDesc.ExtraPages) do + local SrcFileName = g_PluginFolder .. "/" .. extra.FileName; + if (cFile:Exists(SrcFileName)) then + local DstFileName = "API/" .. extra.FileName; + if (cFile:Exists(DstFileName)) then + cFile:Delete(DstFileName); + end + cFile:Copy(SrcFileName, DstFileName); + f:write("
  • " .. extra.Title .. "
  • \n"); + else + f:write("
  • " .. extra.Title .. " (file is missing)
  • \n"); + end + end + f:write("

"); +end + + + + + function DumpAPIHtml() LOG("Dumping all available functions and constants to API subfolder..."); @@ -308,12 +334,17 @@ function DumpAPIHtml()

The API reference is divided into the following sections:


+ ]]); + + WriteArticles(f); + + f:write([[

Class index

The following classes are available in the MCServer Lua scripting language:

    @@ -325,6 +356,9 @@ function DumpAPIHtml() f:write([[


+ ]]); + + f:write([[

Hooks

A plugin can register to be called whenever an "interesting event" occurs. It does so by calling @@ -352,30 +386,11 @@ function DumpAPIHtml() WriteHtmlHook(hook); end end - f:write([[ - + f:write([[ +


-

Extra pages

- -

The following pages provide various extra information

- -
    -]]); - for i, extra in ipairs(g_APIDesc.ExtraPages) do - local SrcFileName = g_PluginFolder .. "/" .. extra.FileName; - if (cFile:Exists(SrcFileName)) then - local DstFileName = "API/" .. extra.FileName; - if (cFile:Exists(DstFileName)) then - cFile:Delete(DstFileName); - end - cFile:Copy(SrcFileName, DstFileName); - f:write("
  • " .. extra.Title .. "
  • \n"); - else - f:write("
  • " .. extra.Title .. " (file is missing)
  • \n"); - end - end - f:write("
"); - + ]]); + -- Copy the static files to the output folder (overwrite any existing): cFile:Copy(g_Plugin:GetLocalFolder() .. "/main.css", "API/main.css"); cFile:Copy(g_Plugin:GetLocalFolder() .. "/prettify.js", "API/prettify.js"); -- cgit v1.2.3 From 0c071c593787c1faa86349497101db42ffc92490 Mon Sep 17 00:00:00 2001 From: madmaxoft Date: Fri, 27 Dec 2013 15:08:27 +0100 Subject: APIDump: Split the huge DumpAPIHtml function into smaller ones. --- MCServer/Plugins/APIDump/main_APIDump.lua | 108 +++++++++++++++++------------- 1 file changed, 61 insertions(+), 47 deletions(-) (limited to 'MCServer/Plugins/APIDump') diff --git a/MCServer/Plugins/APIDump/main_APIDump.lua b/MCServer/Plugins/APIDump/main_APIDump.lua index c36285099..67d6d1036 100644 --- a/MCServer/Plugins/APIDump/main_APIDump.lua +++ b/MCServer/Plugins/APIDump/main_APIDump.lua @@ -255,6 +255,65 @@ end +local function WriteClasses(f, a_API) + f:write([[ +

Class index

+

The following classes are available in the MCServer Lua scripting language: +

    + ]]); + for i, cls in ipairs(a_API) do + f:write("
  • ", cls.Name, "
  • \n"); + WriteHtmlClass(cls, a_API); + end + f:write([[ +

+
+ ]]); +end + + + + + +local function WriteHooks(f, a_Hooks, a_UndocumentedHooks) + f:write([[ +

Hooks

+

+ A plugin can register to be called whenever an "interesting event" occurs. It does so by calling + cPluginManager's AddHook() function and implementing a callback + function to handle the event.

+

+ A plugin can decide whether it will let the event pass through to the rest of the plugins, or hide it + from them. This is determined by the return value from the hook callback function. If the function + returns false or no value, the event is propagated further. If the function returns true, the processing + is stopped, no other plugin receives the notification (and possibly MCServer disables the default + behavior for the event). See each hook's details to see the exact behavior.

+ + + + + + ]]); + for i, hook in ipairs(a_Hooks) do + if (hook.DefaultFnName == nil) then + -- The hook is not documented yet + f:write(" \n \n \n \n"); + table.insert(a_UndocumentedHooks, hook.Name); + else + f:write(" \n \n \n \n"); + WriteHtmlHook(hook); + end + end + f:write([[ +
Hook nameCalled when
" .. hook.Name .. "(No documentation yet)
" .. hook.Name .. "" .. LinkifyString(hook.CalledWhen, hook.Name) .. "
+
+ ]]); +end + + + + + function DumpAPIHtml() LOG("Dumping all available functions and constants to API subfolder..."); @@ -343,53 +402,8 @@ function DumpAPIHtml() ]]); WriteArticles(f); - - f:write([[ -

Class index

-

The following classes are available in the MCServer Lua scripting language: -

    - ]]); - for i, cls in ipairs(API) do - f:write("
  • ", cls.Name, "
  • \n"); - WriteHtmlClass(cls, API); - end - f:write([[ -

-
- ]]); - - f:write([[ -

Hooks

-

- A plugin can register to be called whenever an "interesting event" occurs. It does so by calling - cPluginManager's AddHook() function and implementing a callback - function to handle the event.

-

- A plugin can decide whether it will let the event pass through to the rest of the plugins, or hide it - from them. This is determined by the return value from the hook callback function. If the function - returns false or no value, the event is propagated further. If the function returns true, the processing - is stopped, no other plugin receives the notification (and possibly MCServer disables the default - behavior for the event). See each hook's details to see the exact behavior.

- - - - - - ]]); - for i, hook in ipairs(Hooks) do - if (hook.DefaultFnName == nil) then - -- The hook is not documented yet - f:write(" \n \n \n \n"); - table.insert(UndocumentedHooks, hook.Name); - else - f:write(" \n \n \n \n"); - WriteHtmlHook(hook); - end - end - f:write([[ -
Hook nameCalled when
" .. hook.Name .. "(No documentation yet)
" .. hook.Name .. "" .. LinkifyString(hook.CalledWhen, hook.Name) .. "
-
- ]]); + WriteClasses(f, API); + WriteHooks(f, Hooks, UndocumentedHooks); -- Copy the static files to the output folder (overwrite any existing): cFile:Copy(g_Plugin:GetLocalFolder() .. "/main.css", "API/main.css"); -- cgit v1.2.3 From b99255820b00547ef49045117d7346a30999870c Mon Sep 17 00:00:00 2001 From: madmaxoft Date: Fri, 27 Dec 2013 15:26:41 +0100 Subject: APIDump: Added a quick-nav menu to class descriptions. First part of #403. --- MCServer/Plugins/APIDump/main_APIDump.lua | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) (limited to 'MCServer/Plugins/APIDump') diff --git a/MCServer/Plugins/APIDump/main_APIDump.lua b/MCServer/Plugins/APIDump/main_APIDump.lua index 67d6d1036..42cd5bad4 100644 --- a/MCServer/Plugins/APIDump/main_APIDump.lua +++ b/MCServer/Plugins/APIDump/main_APIDump.lua @@ -255,7 +255,7 @@ end -local function WriteClasses(f, a_API) +local function WriteClasses(f, a_API, a_ClassMenu) f:write([[

Class index

The following classes are available in the MCServer Lua scripting language: @@ -263,7 +263,7 @@ local function WriteClasses(f, a_API) ]]); for i, cls in ipairs(a_API) do f:write("

  • ", cls.Name, "
  • \n"); - WriteHtmlClass(cls, a_API); + WriteHtmlClass(cls, a_API, a_ClassMenu); end f:write([[

    @@ -378,7 +378,19 @@ function DumpAPIHtml() LOGINFO("Cannot output HTML API: " .. err); return; end + + -- Create a class menu that will be inserted into each class file for faster navigation (#403) + local ClassMenuTab = {}; + for idx, cls in ipairs(API) do + table.insert(ClassMenuTab, ""); + table.insert(ClassMenuTab, cls.Name); + table.insert(ClassMenuTab, "
    "); + end + local ClassMenu = table.concat(ClassMenuTab, ""); + -- Write the HTML file: f:write([[ @@ -402,7 +414,7 @@ function DumpAPIHtml() ]]); WriteArticles(f); - WriteClasses(f, API); + WriteClasses(f, API, ClassMenu); WriteHooks(f, Hooks, UndocumentedHooks); -- Copy the static files to the output folder (overwrite any existing): @@ -860,7 +872,7 @@ end -function WriteHtmlClass(a_ClassAPI, a_AllAPI) +function WriteHtmlClass(a_ClassAPI, a_AllAPI, a_ClassMenu) local cf, err = io.open("API/" .. a_ClassAPI.Name .. ".html", "w"); if (cf == nil) then return; @@ -975,7 +987,11 @@ function WriteHtmlClass(a_ClassAPI, a_AllAPI)

    ]], a_ClassAPI.Name, [[


    -

    Contents

    +
    Quick navigation: + ]]); + cf:write(a_ClassMenu); + cf:write([[ +

    Contents

      ]]); @@ -1073,7 +1089,7 @@ function WriteHtmlClass(a_ClassAPI, a_AllAPI) end end - cf:write([[]]); + cf:write([[
    ]]); cf:close(); end -- cgit v1.2.3 From 9a9888a8cc0be28af5217de38f1154db97fa649a Mon Sep 17 00:00:00 2001 From: madmaxoft Date: Fri, 27 Dec 2013 15:36:28 +0100 Subject: APIDump: Added a quick-nav menu to hook descriptions. Second part of #403. --- MCServer/Plugins/APIDump/main_APIDump.lua | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) (limited to 'MCServer/Plugins/APIDump') diff --git a/MCServer/Plugins/APIDump/main_APIDump.lua b/MCServer/Plugins/APIDump/main_APIDump.lua index 42cd5bad4..fef827f3e 100644 --- a/MCServer/Plugins/APIDump/main_APIDump.lua +++ b/MCServer/Plugins/APIDump/main_APIDump.lua @@ -275,7 +275,7 @@ end -local function WriteHooks(f, a_Hooks, a_UndocumentedHooks) +local function WriteHooks(f, a_Hooks, a_UndocumentedHooks, a_HookNav) f:write([[

    Hooks

    @@ -301,7 +301,7 @@ local function WriteHooks(f, a_Hooks, a_UndocumentedHooks) table.insert(a_UndocumentedHooks, hook.Name); else f:write(" \n " .. hook.Name .. "\n " .. LinkifyString(hook.CalledWhen, hook.Name) .. "\n \n"); - WriteHtmlHook(hook); + WriteHtmlHook(hook, a_HookNav); end end f:write([[ @@ -379,7 +379,7 @@ function DumpAPIHtml() return; end - -- Create a class menu that will be inserted into each class file for faster navigation (#403) + -- Create a class navigation menu that will be inserted into each class file for faster navigation (#403) local ClassMenuTab = {}; for idx, cls in ipairs(API) do table.insert(ClassMenuTab, ""); + table.insert(HookNavTab, (hook.Name:gsub("^HOOK_", ""))); -- remove the "HOOK_" part of the name + table.insert(HookNavTab, "
    "); + end + local HookNav = table.concat(HookNavTab, ""); + -- Write the HTML file: f:write([[ @@ -415,7 +426,7 @@ function DumpAPIHtml() WriteArticles(f); WriteClasses(f, API, ClassMenu); - WriteHooks(f, Hooks, UndocumentedHooks); + WriteHooks(f, Hooks, UndocumentedHooks, HookNav); -- Copy the static files to the output folder (overwrite any existing): cFile:Copy(g_Plugin:GetLocalFolder() .. "/main.css", "API/main.css"); @@ -1097,7 +1108,7 @@ end -function WriteHtmlHook(a_Hook) +function WriteHtmlHook(a_Hook, a_HookNav) local fnam = "API/" .. a_Hook.DefaultFnName .. ".html"; local f, error = io.open(fnam, "w"); if (f == nil) then @@ -1120,7 +1131,11 @@ function WriteHtmlHook(a_Hook)

    ]], a_Hook.Name, [[


    -

    +
    Quick navigation: + ]]); + f:write(a_HookNav); + f:write([[ +

    ]]); f:write(LinkifyString(a_Hook.Desc, HookName)); f:write("

    \n

    Callback function

    \n

    The default name for the callback function is "); @@ -1150,7 +1165,7 @@ function WriteHtmlHook(a_Hook) f:write("

    ", (example.Desc or "missing Desc"), "

    \n"); f:write("
    ", (example.Code or "missing Code"), "\n
    \n\n"); end - f:write([[]]); + f:write([[
    ]]); f:close(); end -- cgit v1.2.3 From e4c6e853eaa231f0c95235b02612ad35edf73dce Mon Sep 17 00:00:00 2001 From: madmaxoft Date: Fri, 27 Dec 2013 15:48:01 +0100 Subject: APIDump: Added quick links to index to each class and hook file. Third part of #403. --- MCServer/Plugins/APIDump/main_APIDump.lua | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) (limited to 'MCServer/Plugins/APIDump') diff --git a/MCServer/Plugins/APIDump/main_APIDump.lua b/MCServer/Plugins/APIDump/main_APIDump.lua index fef827f3e..ff837ec4e 100644 --- a/MCServer/Plugins/APIDump/main_APIDump.lua +++ b/MCServer/Plugins/APIDump/main_APIDump.lua @@ -998,7 +998,13 @@ function WriteHtmlClass(a_ClassAPI, a_AllAPI, a_ClassMenu)

    ]], a_ClassAPI.Name, [[


    -
    Quick navigation: +
    + Index:
    + Articles
    + Classes
    + Hooks
    +
    + Quick navigation:
    ]]); cf:write(a_ClassMenu); cf:write([[ @@ -1131,7 +1137,13 @@ function WriteHtmlHook(a_Hook, a_HookNav)

    ]], a_Hook.Name, [[


    -
    Quick navigation: +
    + Index:
    + Articles
    + Classes
    + Hooks
    +
    + Quick navigation:
    ]]); f:write(a_HookNav); f:write([[ -- 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/WebWorldThreads.html | 1 + MCServer/Plugins/APIDump/Writing-a-MCServer-plugin.html | 1 + 2 files changed, 2 insertions(+) (limited to 'MCServer/Plugins/APIDump') diff --git a/MCServer/Plugins/APIDump/WebWorldThreads.html b/MCServer/Plugins/APIDump/WebWorldThreads.html index 6caf40e6d..fc80a6178 100644 --- a/MCServer/Plugins/APIDump/WebWorldThreads.html +++ b/MCServer/Plugins/APIDump/WebWorldThreads.html @@ -6,6 +6,7 @@ +
    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 From 537d1c4e421d6d79cd5c56fd4d97220ae2a3d0e5 Mon Sep 17 00:00:00 2001 From: madmaxoft Date: Fri, 27 Dec 2013 20:56:01 +0100 Subject: APIDump: Added the SettingUpDecoda article. This implements #383. --- MCServer/Plugins/APIDump/APIDesc.lua | 1 + MCServer/Plugins/APIDump/SettingUpDecoda.html | 49 +++++++++++++++++++++ .../APIDump/Static/decoda_debug_settings.png | Bin 0 -> 6178 bytes MCServer/Plugins/APIDump/Static/decoda_logo.png | Bin 0 -> 2413 bytes .../Plugins/APIDump/Static/decoda_workspace.png | Bin 0 -> 70644 bytes 5 files changed, 50 insertions(+) create mode 100644 MCServer/Plugins/APIDump/SettingUpDecoda.html create mode 100644 MCServer/Plugins/APIDump/Static/decoda_debug_settings.png create mode 100644 MCServer/Plugins/APIDump/Static/decoda_logo.png create mode 100644 MCServer/Plugins/APIDump/Static/decoda_workspace.png (limited to 'MCServer/Plugins/APIDump') diff --git a/MCServer/Plugins/APIDump/APIDesc.lua b/MCServer/Plugins/APIDump/APIDesc.lua index f08f83698..d388d15dd 100644 --- a/MCServer/Plugins/APIDump/APIDesc.lua +++ b/MCServer/Plugins/APIDump/APIDesc.lua @@ -2761,6 +2761,7 @@ end { -- No sorting is provided for these, they will be output in the same order as defined here { FileName = "Writing-a-MCServer-plugin.html", Title = "Writing a MCServer plugin" }, + { FileName = "SettingUpDecoda.html", Title = "Setting up the Decoda Lua IDE" }, { FileName = "WebWorldThreads.html", Title = "Webserver vs World threads" }, } } ; diff --git a/MCServer/Plugins/APIDump/SettingUpDecoda.html b/MCServer/Plugins/APIDump/SettingUpDecoda.html new file mode 100644 index 000000000..4ad827efe --- /dev/null +++ b/MCServer/Plugins/APIDump/SettingUpDecoda.html @@ -0,0 +1,49 @@ + + + + + MCServer - Setting up Decoda + + + + + + + +
    +

    Setting up the Decoda IDE

    +

    + This article will explain how to set up Decoda, an IDE for writing Lua code, so that you can develop MCServer plugins with the comfort of an IDE.

    + +

    About Decoda

    + +

    To quickly introduce Decoda, it is an IDE for writing Lua code. It has the basic features expected of an IDE - you can group files into project, you can edit multiple files in a tabbed editor, the code is syntax-highlighted. Code completion, symbol browsing, and more. It also features a Lua debugger that allows you to debug your Lua code within any application that embeds the Lua runtime or uses Lua as a dynamic-link library (DLL). Although it is written using the multiplatform WxWidgets toolkit, it hasn't yet been ported to any platform other than 32-bit Windows. This unfortunately means that Linux users will not be able to use it. It can be used on 64-bit Windows, but the debugger only works for 32-bit programs.

    +

    Here's a screenshot of a default Decoda window with the debugger stepping through the code (scaled down):
    +

    +

    As you can see, you can set breakpoints in the code, inspect variables' values, view both the Lua and native (C++) call-stacks. Decoda also breaks program execution when a faulty Lua script is executed, providing a detailed error message and pointing you directly to the faulting code. It is even possible to attach a C++ debugger to a process that is being debugged by Decoda, this way you can trap both C++ and Lua errors.

    +

    Decoda is open-source, the sources are on GitHub: https://github.com/unknownworlds/decoda. You can download a compiled binary from the creators' site, http://unknownworlds.com/decoda/. + +

    Project management

    +

    To begin using Decoda, you need to create a project, or load an existing one. Decoda projects have a .deproj extension, and are simply a list of Lua files that are to be opened. You can create a project through menu Project -> New Project. Save your project first, so that Decoda knows what relative paths to use for the files. Then either add existing Lua files or create new one, through menu Project -> Add Add New File / Add Existing File.

    +

    Next you need to set up the executable that Decoda will run when debugging your files. Select menu Project -> Settings. A new dialog will open:
    +

    +

    In the debugging section, fill in the full path to MCServer.exe, or click the triple-dot button to browse for the file. Note that the Working directory will be automatically filled in for you with the folder where the executable is (until the last backslash). This is how it's supposed to work, don't change it; if it for some reason doesn't update, copy and paste the folder name from the Command edit box. All done, you can close this dialog now.

    + +

    Debugging

    +

    You are now ready to debug your code. Before doing that, though, don't forget to save your project file. If you haven't done so already, enable your plugin in the settings.ini file. If you want the program to break at a certain line, it is best to set the breakpoint before starting the program. Set the cursor on the line and hit F9 (or use menu Debug -> Toggle Breakpoint) to toggle a breakpoint on that line. Finally, hit F5, or select menu Debug -> Start to launch MCServer under the debugger. The MCServer window comes up and loads your plugin. If Decoda displays the Project Settings dialog instead, you haven't set up the executable to run, see the Project management section for instructions.

    +

    At this point you will see that Decoda starts adding new items to your project. All the files for all plugins are added temporarily. Don't worry, they are only temporary, they will be removed again once the debugging session finishes. You can tell the temporary files from the regular files by their icon, it's faded out. Decoda handles all the files that MCServer loads, so you can actually debug any of those faded files, too.

    +

    If there's an error in the code, the Decoda window will flash and a dialog box will come up, describing the error and taking you to the line where it occured. Note that the execution is paused in the thread executing the plugin, so until you select Debug -> Continue from the menu (F5), MCServer won't be fully running. You can fix the error and issue a "reload" command in MCServer console to reload the plugin code anew (MCServer doesn't detect changes in plugin code automatically).

    +

    If the execution hits a breakpoint, the Decoda window will flash and a yellow arrow is displayed next to the line. You can step through the code using F10 and F11, just like in MSVS. You can also use the Watch window to inspect variable values, or simply hover your mouse over a variable to display its value in the tooltip.

    + +

    Limitations

    +

    So far everything seems wonderful. Unfortunately, it doesn't always go as easy. There are several limits to what Decoda can do:

    +
      +
    • When the program encounters a logical bug (using a nil value, calling a non-existent function etc.), Decoda will break, but usually the Watch window and the tooltips are showing nonsense values. You shouldn't trust them in such a case, if you kep running into such a problem regularly, put console logging functions (LOG) in the code to print out any info you need prior to the failure.
    • +
    • Sometimes breakpoints just don't work. This is especially true if there are multiple plugins that have files of the same name. Breakpoints will never work after changing the code and reloading the plugin in MCServer; you need to stop the server and start again to reenable breakpoints.
    • +
    • Most weirdly, sometimes Decoda reports an error, but instead of opening the current version of the file, opens up another window with old contents of the file. Watch for this, because you could overwrite your new code if you saved this file over your newer file. Fortunately enough, Decoda will always ask you for a filename before saving such a file.
    • +
    • Decoda stores the project in two files. The .deproj file has the file list, and should be put into version control systems. The .deuser file has the settings (debugged application etc.) and is per-user specific. This file shouldn't go to version control systems, because each user may have different paths for the debuggee.
    • +
    • Unfortunately for us Windows users, the Decoda project file uses Unix-lineends (CR only). This makes it problematic when checking the file into a version control system, since those usually expect windows (CRLF) lineends; I personally convert the lineends each time I edit the project file using TED Notepad.
    • +
    +
    + + diff --git a/MCServer/Plugins/APIDump/Static/decoda_debug_settings.png b/MCServer/Plugins/APIDump/Static/decoda_debug_settings.png new file mode 100644 index 000000000..462d517b4 Binary files /dev/null and b/MCServer/Plugins/APIDump/Static/decoda_debug_settings.png differ diff --git a/MCServer/Plugins/APIDump/Static/decoda_logo.png b/MCServer/Plugins/APIDump/Static/decoda_logo.png new file mode 100644 index 000000000..fb446b62c Binary files /dev/null and b/MCServer/Plugins/APIDump/Static/decoda_logo.png differ diff --git a/MCServer/Plugins/APIDump/Static/decoda_workspace.png b/MCServer/Plugins/APIDump/Static/decoda_workspace.png new file mode 100644 index 000000000..7e626dae9 Binary files /dev/null and b/MCServer/Plugins/APIDump/Static/decoda_workspace.png differ -- cgit v1.2.3 From b84cd0b3c573d584d2dd73b9e3866cd990b9fd49 Mon Sep 17 00:00:00 2001 From: madmaxoft Date: Sun, 29 Dec 2013 13:15:46 +0100 Subject: APIDump: Documented OnPluginsLoaded. --- MCServer/Plugins/APIDump/Hooks/OnPluginsLoaded.lua | 79 ++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 MCServer/Plugins/APIDump/Hooks/OnPluginsLoaded.lua (limited to 'MCServer/Plugins/APIDump') diff --git a/MCServer/Plugins/APIDump/Hooks/OnPluginsLoaded.lua b/MCServer/Plugins/APIDump/Hooks/OnPluginsLoaded.lua new file mode 100644 index 000000000..d36cdf5c5 --- /dev/null +++ b/MCServer/Plugins/APIDump/Hooks/OnPluginsLoaded.lua @@ -0,0 +1,79 @@ +return +{ + HOOK_PLUGINS_LOADED = + { + CalledWhen = "All the enabled plugins have been loaded", + DefaultFnName = "OnPluginsLoaded", -- also used as pagename + Desc = [[ + This callback gets called when the server finishes loading and initializing plugins. This is the + perfect occasion for a plugin to query other plugins and possibly start communicating with them using + the {{cPluginManager}}:Call() function. + ]], + Params = {}, + Returns = [[ + The return value is ignored, all registered callbacks are called. + ]], + CodeExamples = + { + { + Title = "CoreMessaging", + Desc = [[ + This example shows how to implement the CoreMessaging functionality - messages to players will be + sent through the Core plugin, formatted by that plugin. As a fallback for when the Core plugin is + not present, the messages are sent directly by this code, unformatted. + ]], + Code = [[ +-- These are the fallback functions used when the Core is not present: +local function SendMessageFallback(a_Player, a_Message) + a_Player:SendMessage(a_Message); +end + +local function SendMessageSuccessFallback(a_Player, a_Message) + a_Player:SendMessage(a_Message); +end + +local function SendMessageFailureFallback(a_Player, a_Message) + a_Player:SendMessage(a_Message); +end + +-- These three "variables" will hold the actual functions to call. +-- By default they are initialized to the Fallback variants, but will be redirected to Core when all plugins load +SendMessage = SendMessageFallback; +SendMessageSuccess = SendMessageSuccessFallback; +SendMessageFailure = SendMessageFailureFallback; + +-- The callback tries to connect to the Core, if successful, overwrites the three functions with Core ones +local function OnPluginsLoaded() + local CorePlugin = cPluginManager:Get():GetPlugin("Core"); + if (CorePlugin == nil) then + -- The Core is not loaded, keep the Fallback functions + return; + end + + -- Overwrite the three functions with Core functionality: + SendMessage = function(a_Player, a_Message) + CorePlugin:Call("SendMessage", a_Player, a_Message); + end + SendMessageSuccess = function(a_Player, a_Message) + CorePlugin:Call("SendMessageSuccess", a_Player, a_Message); + end + SendMessageFailure = function(a_Player, a_Message) + CorePlugin:Call("SendMessageFailure", a_Player, a_Message); + end +end + +-- Global scope, register the callback: +cPluginManager.AddHook(cPluginManager.HOOK_PLUGINS_LOADED, CoreMessagingPluginsLoaded); + + +-- Usage, anywhere else in the plugin: +SendMessageFailure(a_Player, "Cannot teleport to player, the destination player " .. PlayerName .. " was not found"); + ]], + }, + } , -- CodeExamples + }, -- HOOK_PLUGINS_LOADED +} + + + + -- cgit v1.2.3 From 8406a37ac262bdf135b35f8a8f6f9170553c645e Mon Sep 17 00:00:00 2001 From: madmaxoft Date: Sun, 29 Dec 2013 13:23:30 +0100 Subject: APIDump: The PRE html tag has tab width set to 2 spaces. --- MCServer/Plugins/APIDump/main.css | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'MCServer/Plugins/APIDump') diff --git a/MCServer/Plugins/APIDump/main.css b/MCServer/Plugins/APIDump/main.css index 797079873..aa26bd186 100644 --- a/MCServer/Plugins/APIDump/main.css +++ b/MCServer/Plugins/APIDump/main.css @@ -30,6 +30,11 @@ pre { border: 1px solid #ccc; background-color: #eee; + -moz-tab-size: 2; + -o-tab-size: 2; + -webkit-tab-size: 2; + -ms-tab-size: 2; + tab-size: 2; } body -- cgit v1.2.3 From 61af77a5c5c99ec82decc24ff8fca899f959e48a Mon Sep 17 00:00:00 2001 From: madmaxoft Date: Sun, 29 Dec 2013 13:24:38 +0100 Subject: APIDump: Static files overwrite their destination. --- MCServer/Plugins/APIDump/main_APIDump.lua | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) (limited to 'MCServer/Plugins/APIDump') diff --git a/MCServer/Plugins/APIDump/main_APIDump.lua b/MCServer/Plugins/APIDump/main_APIDump.lua index ff837ec4e..b3a95eb22 100644 --- a/MCServer/Plugins/APIDump/main_APIDump.lua +++ b/MCServer/Plugins/APIDump/main_APIDump.lua @@ -321,6 +321,7 @@ function DumpAPIHtml() cFile:CreateFolder("API/Static"); local localFolder = g_Plugin:GetLocalFolder(); for idx, fnam in ipairs(cFile:GetFolderContents(localFolder .. "/Static")) do + cFile:Delete("API/Static/" .. fnam); cFile:Copy(localFolder .. "/Static/" .. fnam, "API/Static/" .. fnam); end @@ -428,11 +429,18 @@ function DumpAPIHtml() WriteClasses(f, API, ClassMenu); WriteHooks(f, Hooks, UndocumentedHooks, HookNav); - -- Copy the static files to the output folder (overwrite any existing): - cFile:Copy(g_Plugin:GetLocalFolder() .. "/main.css", "API/main.css"); - cFile:Copy(g_Plugin:GetLocalFolder() .. "/prettify.js", "API/prettify.js"); - cFile:Copy(g_Plugin:GetLocalFolder() .. "/prettify.css", "API/prettify.css"); - cFile:Copy(g_Plugin:GetLocalFolder() .. "/lang-lua.js", "API/lang-lua.js"); + -- Copy the static files to the output folder: + local StaticFiles = + { + "main.css", + "prettify.js", + "prettify.css", + "lang-lua.js", + }; + for idx, fnam in ipairs(StaticFiles) do + cFile:Delete("API/" .. fnam); + cFile:Copy(g_Plugin:GetLocalFolder() .. "/" .. fnam, "API/" .. fnam); + end -- List the documentation problems: LOG("Listing leftovers..."); -- cgit v1.2.3 From 61254d035643de23013b8995a7385ac79a673cbd Mon Sep 17 00:00:00 2001 From: madmaxoft Date: Sun, 29 Dec 2013 14:48:58 +0100 Subject: APIDump: Fixed a factual error in OnPluginsLoaded description. --- MCServer/Plugins/APIDump/Hooks/OnPluginsLoaded.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'MCServer/Plugins/APIDump') diff --git a/MCServer/Plugins/APIDump/Hooks/OnPluginsLoaded.lua b/MCServer/Plugins/APIDump/Hooks/OnPluginsLoaded.lua index d36cdf5c5..0d5b7271e 100644 --- a/MCServer/Plugins/APIDump/Hooks/OnPluginsLoaded.lua +++ b/MCServer/Plugins/APIDump/Hooks/OnPluginsLoaded.lua @@ -6,8 +6,8 @@ return DefaultFnName = "OnPluginsLoaded", -- also used as pagename Desc = [[ This callback gets called when the server finishes loading and initializing plugins. This is the - perfect occasion for a plugin to query other plugins and possibly start communicating with them using - the {{cPluginManager}}:Call() function. + perfect occasion for a plugin to query other plugins through {{cPluginManager}}:GetPlugin() and + possibly start communicating with them using the {{cPlugin}}:Call() function. ]], Params = {}, Returns = [[ -- cgit v1.2.3