Skip to content

Creating a Plugin

Sw1ft edited this page Sep 10, 2022 · 1 revision

1. Singleton Interface

The main part of a SvenMod's plugin is IClientPlugin interface that must be inherited and implemented by a single class, this interface is queried by SvenMod at DLL load and it has several important and useful callbacks when your plugin is going to load/unload or pause/unpause, etc (for more, check the header svenmod/public/IClientPlugin.h). Then after implementing your interface, it must be exposed as a singleton that may be queried from the automatically exported function CreateInterface.

2. Describing your plugin's interface

Tip: it's better to implement the interface in a separate source file (.cpp).

Include this header in your file:

#include <IClientPlugin.h>

Now we can inherit and implement our interface. This is how may look your inherited class:

class MyPluginClass : IClientPlugin
{
public:
	// Always add this in implementation: return SVENMOD_API_VER;
	virtual api_version_s		GetAPIVersion( void ) = 0;

	// Initialize the plugin to run
	// Return 'false' if there is an error during startup
	virtual bool				Load( CreateInterfaceFn pfnSvenModFactory, ISvenModAPI *pSvenModAPI, IPluginHelpers *pPluginHelpers ) = 0;

	// Called if the plugin was loaded during SvenMod's initialization after attaching detours (via plugins.txt file, @bGlobalLoad is 'true')
	// Otherwise, called right after 'Load' callback (@bGlobalLoad is 'false')
	virtual void				PostLoad( bool bGlobalLoad ) = 0;

	// Called when the plugin should be shutdown
	virtual void				Unload( void ) = 0;

	// Called when the plugin should stop executing
	// Return 'false' if it should not pause
	virtual bool				Pause( void ) = 0;

	// Called when the plugin should start executing again (sometime after a Pause() call)
	virtual void				Unpause( void ) = 0;

	// Called on each game frame twice (if @bPostRunCmd is 'false', then none of inputs/commands were processed yet)
	virtual void				GameFrame( client_state_t state, double frametime, bool bPostRunCmd ) = 0;
	
	// Called to draw 2D paints after rendering the game view, you can handle the callback to influence on other plugins
	// @PLUGIN_CONTINUE - don't abort the call sequence
	// @PLUGIN_STOP and @PLUGIN_CALL_STOP - don't call the further plugins callbacks
	virtual PLUGIN_RESULT		Draw( void ) = 0;
	
	// Called to redraw client's HUD, the same callback as @Draw method
	virtual PLUGIN_RESULT		DrawHUD( float time, int intermission ) = 0;

	// Name of the plugin
	virtual const char			*GetName( void ) = 0;

	// Author of the plugin
	virtual const char			*GetAuthor( void ) = 0;

	// Version of the plugin
	virtual const char			*GetVersion( void ) = 0;

	// Description of the plugin
	virtual const char			*GetDescription( void ) = 0;

	// URL of the plugin
	virtual const char			*GetURL( void ) = 0;
	
	// Build date of the plugin
	virtual const char			*GetDate( void ) = 0;

	// Tag of the plugin to log
	virtual const char			*GetLogTag( void ) = 0;
};

Implementing your class is easy too:

api_version_s MyPluginClass::GetAPIVersion()
{
	return SVENMOD_API_VER;
}

bool MyPluginClass::Load(CreateInterfaceFn pfnSvenModFactory, ISvenModAPI *pSvenModAPI, IPluginHelpers *pPluginHelpers)
{
	return true;
}

void MyPluginClass::PostLoad(bool bGlobalLoad)
{
	if ( bGlobalLoad )
	{
	}
	else
	{
	}
}

void MyPluginClass::Unload(void)
{
}

bool MyPluginClass::Pause(void)
{
	return true;
}

void CSamplePlugin::Unpause(void)
{
}

void MyPluginClass::GameFrame(client_state_t state, double frametime, bool bPostRunCmd)
{
	if (bPostRunCmd) // called from HUD_Frame
	{

	}
	else // called on start of function Host_Frame
	{

	}
}

PLUGIN_RESULT MyPluginClass::Draw(void)
{
	return PLUGIN_CONTINUE;
}

PLUGIN_RESULT MyPluginClass::DrawHUD(float time, int intermission)
{
	return PLUGIN_CONTINUE;
}

const char *MyPluginClass::GetName(void)
{
	return "My Plugin Name";
}

const char *MyPluginClass::GetAuthor(void)
{
	return "Author";
}

const char *MyPluginClass::GetVersion(void)
{
	return "1.0";
}

const char *MyPluginClass::GetDescription(void)
{
	return "My plugin for SvenMod";
}

const char *MyPluginClass::GetURL(void)
{
	return "https://github.com/sw1ft747";
}

const char *MyPluginClass::GetDate(void)
{
	return SVENMOD_BUILD_TIMESTAMP;
}

const char *MyPluginClass::GetLogTag(void)
{
	return "SAMPLE_PLUGIN";
}

3. Exposing the interface

Now we need only to expose our interface and build the project.

Include this header in your file:

#include <interface.h>

The header above will minimize your time to expose the interface.

You can use one of these macros:

// Use this to expose a singleton interface with a global variable you've created.
#define EXPOSE_SINGLE_INTERFACE_GLOBALVAR(className, interfaceName, versionName, globalVarName)

// Use this to expose a singleton interface. This creates the global variable for you automatically.
#define EXPOSE_SINGLE_INTERFACE(className, interfaceName, versionName)

Example (autocreate global variable):

EXPOSE_SINGLE_INTERFACE(MyPluginClass, IClientPlugin, CLIENT_PLUGIN_INTERFACE_VERSION);

Another example (with your global variable):

MyPluginClass g_Plugin;

EXPOSE_SINGLE_INTERFACE_GLOBALVAR(MyPluginClass, IClientPlugin, CLIENT_PLUGIN_INTERFACE_VERSION, g_Plugin);

4. More examples

You may look at the sample plugin in SvenMod's folder (svenmod/sample_plugin/sample_plugin.*) it either has examples with other interfaces/functions (convars, printing, hooking functions).

5. Done

Now you can build your plugin and load it via SvenMod