Writing Extensions

From AlliedModders Wiki
Revision as of 03:45, 19 January 2007 by BAILOPAN (talk | contribs) (Creating Events/Forwards)
Jump to: navigation, search

SourceMod's Extension System is a powerful way to greatly enhance the flexibility of your SourceMod plugins. You can take full advantage of SourceMod's Core, using the interfaces it provides, to create C++ plugins, plugin events, plugin native functions, and shared interfaces. Extensions can also hook into Metamod:Source.

This article goes into the details of creating a SourceMod Extension. Knowledge of C++ is required.

SDK Structure

First, download the SourceMod SDK from the SourceMod website. The directory structure looks like this:

  • extensions
    • geoip/ - Source code to the GeoIP extension
    • mysql/ - Source code to the MySQL extension
    • threader/ - Source code to the Threader extension
  • plugins/ - Source code to all of SourceMod's plugins
    • include/ - The include files which document plugin API
  • public/ - Interface files for SourceMod's Core Interfaces
    • extensions/ - Interfaces that are provided by extensions
    • sample_ext/ - The Sample Extension SDK
    • sourcepawn - The include/interface files for SourcePawn
  • sourcepawn/
    • compiler/ - The SourcePawn Compiler


Starting an Extension

For simplicity, this article will assume you are using the SDK base files. These are:

  • smsdk_config.h - Configuration settings (we will be editing this)
  • smsdk_ext.h - Header for SDK wrappers (usually never needs to be edited)
    • Note: Sometimes, this may be updated by the SourceMod Dev Team. Using a newest version is recommended.'
  • smsdk_ext.cpp - Source for SDK wrappers (usually never needs to be edited)
    • Note: Sometimes, this may be updated by the SourceMod Dev Team. Using a newest version is recommended.'
  • extension.h - User file for main extension header
  • extension.cpp - User file for main extension code

The extension.* files are not technically part of the SDK. However, they are provided to give users a starting point and some documentation.


Note: For help setting up Visual Studio, please see Setting up Visual Studio.


The first step of creating your extension is to configure it. Open the smsdk_config.h file and edit each of the following defines. Use the information below to guide you.

  • SMEXT_CONF_NAME - The general name for your Extension (should be short).
  • SMEXT_CONF_DESCRIPTION - A short description of what your extension does.
  • SMEXT_CONF_VERSION - A version number string. By convention, SourceMod uses the four set build number notation. I.e. for 1.2.3.4:
    • 1 is the "Major" release version.
    • 2 is the "Minor" release version.
    • 3 is the "Maintenance" or "Revision" version.
    • 4 is the "Build," usually an internal source control number.
  • SMEXT_CONF_AUTHOR - Your name (or whoever/whatever authored the plugin).
  • SMEXT_CONF_URL - The URL/homepage of this extension.
  • SMEXT_CONF_LOGTAG - The logtag your extension will use for SourceMod logging. By convention, try to keep this under ten characters or so, and to make it all caps.
  • SMEXT_CONF_LICENSE - Right now, the SourceMod Dev Team mandates that 3rd party extensions must be under an Open Source license. Putting a license abbreviation or very short message here is appropriate. For more information, see SourceMod License.
  • SMEXT_CONF_DATESTRING - You do not need to modify this.
  • SMEXT_CONF_METAMOD - If your plugin requires using SourceHook or Metamod:Source, uncomment this line.


Next, you may want to change the name of the default class that extension.h declares. To do this...

  • Open extension.h and change "Sample" in this line:
    class Sample : public SDKExtension
  • Open extension.cpp and change the two lines to correspond to your new class name. You can also change the global singleton that's declared, although you cannot remove the SMEXT_LINK line (this lets SourceMod load your extension).


Visual Studio Users

Make sure you change the name of your .dll file. The naming convention for SourceMod extensions is name.ext.dll, where name is a short name for your extension. You are free to change this, but it is highly recommended that you keep the .ext.dll in tact.

You can do this by going to Project -> Properties -> Configuration Properties -> Linker -> General. Set the Output File option appropriately.

Linux Users

Simply edit the Makefile and change the BINARY line near the top. Make sure you do not add any trailing spaces at the end of the name, or the build won't come out right.

You should now be able to compile a blank extension!


Creating Native Functions

Most of the time, an extension exists to add "native functions" to the plugin system. Native functions are simply the functions that plugins can use in SourceMod. They are coded in C++ and have a short prototype declaration in an include file.

Prototyping

The first step to creating a native is to spec it, or prototype it. This is a simple but often time consuming process, but if you get in the habit of it, your documentation will be much better. Let's first create a very simply native. Here is a prototype for it, which we will place in sample.inc:

/**
 * Returns the square of a number.
 *
 * @param num	Number to square.
 * @return	The square of num.
 */
native SquareNumber(num);

The native keyword tells the compiler that this function exists in an outside source. The comment style is similar to doxygen or Javadoc, and is preferred by the SourceMod Development Team.

Implementing

Now that our function is documented, it's time to create it! Each native function is bound to a C++ function. The prototype for this function looks lile:

cell_t Function(IPluginContext *pContext, const cell_t *params);

A quick explanation of these types:

  • cell_t - This is a 32bit signed integer, the generic data type for Pawn.
  • IPluginContext - This interface provides Virtual Machine functions for retrieving or modifying memory in the plugin.
  • params - This is the "stack" of parameters that the plugin passed. It is an array from [0..N] where 0 contains N.
    • For example, if one parameter was passed, and the parameter is 25:
      • params[0] == 1
      • params[1] == 25
    • Even though it tells you how many parameters are passed, you do not need to verify this number. The compiler will always pass the correct number of parameters in. The only time you need to verify it is for backwards compatibility or variable parameter lists, which will be discussed later.

Now, let's write our native function:

cell_t SquareNumber(IPluginContext *pContext, const cell_t *params)
{
	cell_t number = params[1];
	return number * number;
}

Binding

Now, the final step is to bind our native. Natives are bound in native lists. Each list must be terminated by a NULL bind. Example:

const sp_nativeinfo_t MyNatives[] = 
{
	{"SquareNumber",	SquareNumber},
	{NULL,			NULL},
};

The first column/member is a string containing the name you've assigned to the native. The second column/member is the C++ function you're binding to. Here, both contain the same name, but it is certainly possible to bind a function to any valid Pawn function name, and likewise, you can bind one function to more than one name.

Lastly, you must tell Core about your native list. There are two places that are good to do this. Whichever you choose, you must uncomment the function in extension.h and implement it in extension.cpp.

  • SDK_OnLoad - This is called when your extension is first loaded. If you do not require any outside interfaces, it is safe to add natives here.
  • SDK_OnAllLoaded - This is called when all extensions have been loaded. This is generally a better place to add natives.

Let's say we choose SDK_OnAllLoaded, we'd then have code like this:

void Sample::SDK_OnAllLoaded()
{
	g_pShareSys->AddNatives(myself, MyNatives);
}

Note: myself is a global variable declared in the SDK. It is a pointer that identifies your extension.

For more information on writing natives, see Natives (SourceMod Development).


Creating Events/Forwards

Events are an integral part to SourceMod. An Event is formally called a Forward. Forwards are functions inside a plugin which are not called by the plugin itself, but are triggered from an external source. For example, OnClientConnect is a forward which is triggered when a player connects to a server.

There are two major types of forwards:

  • Managed: This forward is global and has a name. To use this forward, the function must be declared as public inside the user's script. Any plugin having this public function will receive the event.
  • Unmanaged: This forward is private in nature. For example, the HookUserMessage native lets users specify a function to be called when a specific user message is sent. This is done with an unmanaged forward, and adding functions to this forward is not automatic. In general, function calls for unmanaged forwards are not public, although they can be.

Managed Forwards

Let's say we want to create a forward that will be called when a player uses say chat. For simplicity, let's assume you already have a function that tells you when a player says say chat. We will just be creating the forward for it.

As we did before, first let's prototype our global forward. In our include file, we add this:

/**
 * @brief Called whenever a client says somethign in chat.
 *
 * @param client	Index of the client.
 * @param text		String containing the say text.
 * @return 		Pl_Handled to block text from printing, Pl_Continue otherwise.
 */
forward ResultType:OnPlayerSayChat(client, const String:text[]);

The forward keyword tells the compiler that any function having this name must be declared exactly the same. In a plugin, this would

The next step is to declare your forward somewhere at the top of your extension.cpp file. This will look like:

IForward *g_pSayChat = NULL

Next, re-using our code from above, let's create this forward in SDK_OnAllLoaded:

void Sample::SDK_OnAllLoaded()
{
	g_pShareSys->AddNatives(myself, MyNatives);
	g_pSayChat = g_pForwards->CreateForward(ET_Event, 2, NULL, Param_Cell, Param_String);
}

Explanation of parameters:

  • ET_Event - Since forwards can execute multiple functions in multiple scripts, this is one of the ways of specifying what to do with the return value of each function. ET_Event means "return the highest value, do not allow stops." A stop refers to Pl_Stop, which lets a plugin block the rest of the event chain.
  • 2 - This is the number of parameters our forward will have.
  • NULL - Not used in our example. This lets you pass parameters by array rather than variable arguments.
  • Param_Cell - The first parameter is a cell (integer).
  • Param_String - The second parameter is a string.

Now, we write a quick function in our module to call this forward:

/** Fires the say chat event in plugins.  Returns true to allow the text, false to block. */
bool FireChatEvent(int client, const char *text)
{
	if (!g_pSayChat)
	{
		return true;
	}
 
	cell_t result = 0;
	g_pSayChat->PushCell(client);
	g_pSayChat->PushString(text);
	g_pSayChat->Execute(&result);
 
	if (result == Pl_Handled)
	{
		return false;
	}
 
	return true;
}

As you can see, this was pretty simple. Each parameter is "pushed" one at a time, in ascending order. First we push the client index as a cell, then the text as a string. Once done, we execute, and the value will be returned by reference.

Lastly, there is a caveat. Unlike natives, SourceMod does not automatically clean up forwards for us when are done. We have to make sure we destroy them. Uncomment SDK_OnUnload from extension.h and implement it like so:

void Sample::SDK_OnUnload()
{
	g_pForwards->ReleaseForward(g_pSayChat);
}

Unmanaged Forwards

Now things get a bit more complicated. However, unmanaged forwards are essentially the same -- they just give you more flexibility. Let's consider the managed example with a new twisty. We want to let users add and remove hooks from their plugins, rather than having one global forward. The standard way to do this is with function IDs.

Example prototype:

funcenum SayChatHook
{
	forward(client, const String:text[]);
}
 
native AddSayChatHook(SayChatHook:hook);
native RemoveSayChatHook(SayChatHook:hook);

The funcenum syntax lets you define all the valid prototypes for your hook. In this example, the only valid method is a function declared like this (only MyHook can be changed):

MyHook(client, const String:text[])

How do we make such a beast? The first step is setting up our forward.

IChangeableForward *g_pSayChat = NULL;
/* ... */
void Sample::SDK_OnAllLoaded()
{
	g_pSayChat = g_pForwards->CreateForwardEx(NULL, ET_Hook, 2, NULL, Param_Cell, Param_String); 
}

Notice two big difference. We now use IChangeableForward instead of IForward, and we use CreateForwardEx instead. The initial first parameter specifies a name for our forward - we're going to ignore this.

Now, we need to create our natives. These will be fairly simple:

cell_t AddSayChatHook(IPluginContext *pContext, const cell_t *params)
{
	g_pSayChat->AddFunction(pContext, static_cast<funcid_t>(params[1]));
	return 1;
}
 
cell_t RemoveSayChatHook(IPluginContext *pContext, const cell_t *params)
{
	IPluginFunction *pFunction = pContext->GetFunctionById(static_cast<funcid_t>(params[1]));
	g_pSayChat->RemoveFunction(pFunction);
	return 1;
}

You do not need to worry about a plugin unloading - Core will automatically remove the functions for you. Since an IChangeableForward is also an IForward, the FireChatEvent function from earlier does not need to change. We're done!