Server plugins: Difference between revisions

From Valve Developer Community
Jump to navigation Jump to search
 
(67 intermediate revisions by 34 users not shown)
Line 1: Line 1:
[[Category:Programming]]
{{LanguageBar}}
The Source engine comes with a built in 3rd party plugin API similar to the API provided by MetaMod under the HL1 engine. This document provides a brief description of the interfaces provided and how to best use them.


=Compiling the Sample=
{{toc-right}}
'''Server plugins''' are C++ code libraries that modify the behaviour of [[dedicated server]]s. They are used to provide everything from maintenance tools to additional gametypes.


The SDK contains a sample plugin that exercises the various capabilities available in a plugin. You can find the sample in <code>src/utils/serverplugin_sample</code>. Under Windows load the <code>serverplugin_empty.vcproj</code> project, under linux you can type:
== Installing ==
<pre>
Source automatically loads plugins defined in files matching <code><game>/addons/*.vdf</code>.  
make plugin
</pre>


from the <code>linux_sdk</code> directory.
== .vdf format ==
These files should be formatted like this:


=Running a Plugin=
Plugin
{
file "<path to plugin>"
}


The engine will load plugins based upon specially formatted key value files put in the <code><mod dir>/addons/</code> folder (you may have to create this directory, a default server install doesn't come with one). To load your plugin place a file in this folder with the extension vdf and with the following format:
The path is relative to the folder that <code>gameinfo.txt</code> resides (i.e., the mod base folder).
<pre>
 
"Plugin"
== Managing ==
{
 
        "file"  "serverplugin_empty"
The following console commands are provided:
}
 
</pre>
* <code>plugin_print</code> (provides IDs for use with other commands)
The <code>"file"</code> parameter should point to the file to load. In this example the binary created after you compiled the code should be copied to the <code>bin/</code> folder of the server install. The file extension (.dll for Windows) is added automatically to the filename when it is loaded. The file name should be relative to the base bin/ folder on the server (i.e you would use <code>../cstrike/addons/serverplugin_empty</code> to specify a file in the cstrike addons folder).
* <code>plugin_load</code>
* <code>plugin_unload</code>
* <code>plugin_pause</code> (the engine stops sending callbacks to the plugin)
* <code>plugin_unpause</code>
* <code>plugin_pause_all</code>
* <code>plugin_unpause_all</code>
 
== Coding ==
 
:''You can find a [https://github.com/ValveSoftware/source-sdk-2013/tree/master/src/utils/serverplugin_sample sample plugin project] at <code>src\utils\serverplugin_sample\</code>. The Orange Box version is good for both [[Source 2007]] and [[Source 2009|2009]].''
 
Plugins work by exposing an object inheriting from <code>[[IServerPluginCallbacks]]</code> and <code>[[IGameEventListener2]]</code> to the engine. Source will send function calls into the plugin at various times, hard-coded and or listened for, and the plugin can react to each of those events by running its own code. In some cases (e.g. player connection) changing the outcome of the original function is also possible.
 
'''The most important caveat is that you cannot access server classes'''. Each plugin is a discrete library that can only access the parts of the server that are exposed through Source's various [[:Category:Interfaces|interfaces]]. The likes of <code>[[CBaseEntity]]</code> are not available; entities are instead represented with <code>[[edict_t]]</code>. {{confirm|Entities without edicts cannot be accessed at all.}}
 
{{warning|If you have source code for a class' declaration it is possible to include it and gain access, but doing so is dangerous. Should the class ever change in the server your plugin will find itself with invalid pointers.}}
 
{{tip|A plugin will fail to initialize (rather than crash with a call stack) if any exceptions occur during load. This can easily happen if you try to use an interface before it has been initialized by <code>IServerPluginCallbacks::Load()</code> (e.g. caching a pointer with <code>cvar->FindVar()</code>). You can declare things, but don't assign to them until <code>Load()</code>.}}


=The IServerPlugin interface=
=== Compiling ===


Plugins work by exposing an <code>IServerPluginCallbacks</code> interface to the engine. The engine calls back into this interface when various events happen. The definition of this interface can be found in <code>public/engine/iserverplugin.h</code>. The <code>PLUGIN_RESULT</code> return type of some of the functions allows the plugin to control whether this call is passed onto the underlying mod or not, see the header file for details.
; Windows
: Build with the Visual Studio project provided; see [[Compiling under VS2008]] or [[Compiling under VS2010]] for help with upgrading
; Linux
: Navigate to <code>src/linux_sdk/</code>, edit <code>Makefile.plugin</code> to include your files, and then execute <code>make plugin</code>.
:If you are compiling for [[Source 2009]], you must change the names of some Valve libraries in <code>Makefile</code> and <code>Makefile.vpcm</code>: {{tip|It is probably best to make a copy of the linux_sdk folder before doing this, so that you don't break Source 2007 compiles.}}
:* tier0_i486 > libtier0
:* vstdlib_i486 > libvstdlib
:* steam_api_i486 > libsteam_api
: Use <code>ldd -d <plugin.so></code> to check for dependencies. It won't be able to find the libs; just worry about what they're called.
; Mac
: Not possible yet


=IServerPluginCallbacks explained=
To debug your plugin, you must launch the server with <code>-allowdebug</code>.
{|
| <code>Load</code> || This function is called when the plugin is loaded by the engine. This can happen either on initialization or when being re-loaded after being unloaded. The two parameters provide the interface factories that the plugin needs to operate.
|-
| <code>UnLoad</code> || Called when a plugin is unloaded, use this to disable any asynchronous tasks and remove any callbacks you have registered with the engine (for example a game events listener).
|-
| <code>Pause</code> || Called when the operation of the plugin is paused (i.e it will stop receiving callbacks but should not be unloaded).
|-
| <code>UnPause</code> || Called when a plugin is brought out of the paused state. You should re-enable any asynchronous events your plugin uses in this call
|-
| <code>GetPluginDescription</code> || This function should return a friendly string describing your plugin. Typically this would be its name and the author.
|-
| <code>LevelInit</code> || Called on level (map) startup, it is the first function called as a server enters a new level.
|-
| <code>ServerActivate</code> || This is called when the server successfully enters a new level, this will happen after the LevelInit call. This call provides pointers to the list of edicts created on the server and the maxplayer count of the server.
|-
| <code>GameFrame</code> || Called once per server frame (typically 60 times a second). Server performance is very sensitive to the execution time of this function so keep anything you do in this function to a minimum.
|-
| <code>LevelShutdown</code> || Called when a server is changing to a new level or is being shutdown. Remove any map specific allocations in this call. This can be called multiple times during a map change.
|-
| <code>ClientConnect</code> || Called when a client initially connects to a server. Set bAllowConnect to false to stop this user from connecting.
|-
| <code>ClientPutInServer</code> || Called when a client spawns into a server. This is called before the server spawn function.
|-
| <code>ClientActive</code> || Called after a client is fully spawned and configured by the Mod. Use this call to change any player specific settings.
|-
| <code>ClientDisconnect</code> || Called when a client disconnects from the server.
|-
| <code>SetCommandClient</code> || Called by the ConVar code to let you keep track of which client is entering a ConCommand. Use this index in ConCommands if you want to see who is running the command. As ConVar commands don't have an edict associated with them when they run you can use this index to look up the entity that is running the command.
|-
| <code>ClientSettingsChanged</code> ||
Called when player specific cvars about a player change (for example the users name). Use this function to control what operations a user is allowed to perform to their settings (for example limiting usernames).
|-
| <code>ClientCommand</code> || Called when a remote client enters a command into the console. This should be used to provide commands to clients (and ConCommand used to implement server side only commands).
|-
| <code>NetworkIDValidated</code> || Called when the server retrieves a clients network ID (i.e Steam ID). Note that a clients network ID is not set on connect, you must wait for this callback before a users network ID is valid. The client name is passed into this function, you need to search the currently connected players to find the associated edict pointer. Note that the clients name can change between connect and id validation, you should use the entity index to track specific players.  Note also that this function is NOT called for the local user when running a listen/non-dedicated server.
|}


=Listening to Events=
===Listening to Events===


The <code>IGameEventManager</code> interface allows a plugin to listen to game events. Game events are fired by a Mod when things of interest happen, like a player dying or the bomb being planted. <code>IGameEventManager::AddListener</code> should be called to subscribe to game events. FireGameEvent is then called in your plugin when an event happens. The data contained in each event is described by event configuration files, in particular:
The <code>IGameEventManager2</code> (<code>IGameEventManager</code> was used previously) interface allows a plugin to listen to game events. Game events are fired by a Mod when things of interest happen, like a player dying or the bomb being planted. <code>IGameEventManager2::AddListener</code> should be called to subscribe to particular game events. It must be called for each event that your listener wishes to be aware of. FireGameEvent is then called in your plugin when an one of the listened for events happen. The data contained in each event is described by event configuration files, in particular:


* <code>hl2/resource/serverevents.res</code>
* <code>hl2/resource/serverevents.res</code>
Line 72: Line 65:
* <code><mod dir>/resource/ModEvents.res</code>
* <code><mod dir>/resource/ModEvents.res</code>


=Creating ConVars and Commands=
===Creating [[ConVar]]s and [[Command]]s===
 
[[ConVar]]s let you specify variables that users can use to configure the behavior of your plugin. [[ConCommand]]s allow the creation of commands that your plugin provides. Creating both is easy and self contained, the code snippet below creates a new command <code>empty_version</code> and a new variable <code>plugin_empty</code>. These commands can be run from the server or client, you should use the index provided by <code>SetCommandClient</code> to determine the source of the command.


ConVars let you specify variables that users can use to configure the behavior of your plugin. ConCommands allow the creation of commands that your plugin provides. Creating both is easy and self contained, the code snippet below creates a new command <code>empty_version</code> and a new variable <code>plugin_empty</code>. These commands can be run from the server or client, you should use the index provided by <code>SetCommandClient</code> to determine the source of the command.
<source lang=cpp>
<pre>
CON_COMMAND( empty_version, "prints the version of the empty plugin" )
CON_COMMAND( empty_version, "prints the version of the empty plugin" )
{
{
Line 82: Line 76:


static ConVar empty_cvar("plugin_empty", "0", 0, "Example plugin cvar");
static ConVar empty_cvar("plugin_empty", "0", 0, "Example plugin cvar");
</pre>
</source>
 
=== Other tricks ===
 
'''Get player entities:'''
 
<source lang=cpp>
static CGlobalVars *gpGlobals;
static IVEngineServer *engine;
 
CON_COMMAND( list_players, "Prints the name of each connected player" )
{
for (int i=1;i<gpGlobals->maxClients;i++) // EntIndex 0 is worldspawn, after which come the players
{
// n.b. this only works if the player is active; players still connecting won't show up
IPlayerInfo *playerinfo = playerinfomanager->GetPlayerInfo(engine->PEntityOfEntIndex(i));
if (playerinfo)
{
Msg(playerinfo->GetName());
Msg("\n");
}
}
}
</source>
 
'''Add and remove a [[sv tags|server tag]]:'''
 
<source lang=cpp>
#include <string>
 
void AddTag(char* MyTag)
{
static ConVar* sv_tags = cvar->FindVar("sv_tags");
std::string tag_string;
 
tag_string.assign(sv_tags->GetString());
 
if (tag_string.find(MyTag) == -1)
{
if (tag_string.length() && tag_string.at(tag_string.length()) != ',')
tag_string.append(",");
 
tag_string.append(MyTag);
 
sv_tags->SetValue(tag_string.c_str());
}
}
 
void RemoveTag(char* MyTag)
{
static ConVar* sv_tags = cvar->FindVar("sv_tags");
std::string tag_string;
 
tag_string.assign(sv_tags->GetString());
 
size_t start = tag_string.find(MyTag);
if (start != -1)
{
tag_string.erase( start, tag_string.find(start,',') );
sv_tags->SetValue(tag_string.c_str());
}
}
</source>


=Interacting with Clients=
== See also ==


The engine provides the <code>IServerPluginHelpers</code> interface to allow plugins to message clients. This interface provides a single function, <code>CreateMessage</code> that is called with 3 different dialog options to message users in different ways.
* [[Client plugins]]
* [[Left 4 Dead Plugins]]
* [[Orange Box server plugins]]
* [[Querying ConVars from Server Plugins]]


# <code>DIALOG_MSG, this prints a simple message to a client.</code>
[[Category:Plugins]]
# <code>DIALOG_MENU, this provides a client with a menu of options.</code>
# <code>DIALOG_TEXT, this shows a user a larger text field.</code>


Examples of how to use this interface are shown in the sample plugin example. Each message will be displayed for 10 seconds in the HUD and for the menu and text option for up to 200 seconds in GameUI. Only 1 message can be displayed at a time, you have to wait for the current one to expire before a new one can be shown. Messages with a lower <code>"level"</code> field can be used to override current messages (the minimum level value is 0). Details of valid message fields can be found in the sample code.
[[Category:Dedicated Server]]
[[Category:Plain text files]]
[[Category:Plain text formats]]

Latest revision as of 13:32, 11 April 2025

English (en)Deutsch (de)Español (es)Русский (ru)Translate (Translate)

Server plugins are C++ code libraries that modify the behaviour of dedicated servers. They are used to provide everything from maintenance tools to additional gametypes.

Installing

Source automatically loads plugins defined in files matching <game>/addons/*.vdf.

.vdf format

These files should be formatted like this:

Plugin
{
	file		"<path to plugin>"
}

The path is relative to the folder that gameinfo.txt resides (i.e., the mod base folder).

Managing

The following console commands are provided:

  • plugin_print (provides IDs for use with other commands)
  • plugin_load
  • plugin_unload
  • plugin_pause (the engine stops sending callbacks to the plugin)
  • plugin_unpause
  • plugin_pause_all
  • plugin_unpause_all

Coding

You can find a sample plugin project at src\utils\serverplugin_sample\. The Orange Box version is good for both Source 2007 and 2009.

Plugins work by exposing an object inheriting from IServerPluginCallbacks and IGameEventListener2 to the engine. Source will send function calls into the plugin at various times, hard-coded and or listened for, and the plugin can react to each of those events by running its own code. In some cases (e.g. player connection) changing the outcome of the original function is also possible.

The most important caveat is that you cannot access server classes. Each plugin is a discrete library that can only access the parts of the server that are exposed through Source's various interfaces. The likes of CBaseEntity are not available; entities are instead represented with edict_t.

Confirm:Entities without edicts cannot be accessed at all.
Warning.pngWarning:If you have source code for a class' declaration it is possible to include it and gain access, but doing so is dangerous. Should the class ever change in the server your plugin will find itself with invalid pointers.
Tip.pngTip:A plugin will fail to initialize (rather than crash with a call stack) if any exceptions occur during load. This can easily happen if you try to use an interface before it has been initialized by IServerPluginCallbacks::Load() (e.g. caching a pointer with cvar->FindVar()). You can declare things, but don't assign to them until Load().

Compiling

Windows
Build with the Visual Studio project provided; see Compiling under VS2008 or Compiling under VS2010 for help with upgrading
Linux
Navigate to src/linux_sdk/, edit Makefile.plugin to include your files, and then execute make plugin.
If you are compiling for Source 2009, you must change the names of some Valve libraries in Makefile and Makefile.vpcm:
Tip.pngTip:It is probably best to make a copy of the linux_sdk folder before doing this, so that you don't break Source 2007 compiles.
  • tier0_i486 > libtier0
  • vstdlib_i486 > libvstdlib
  • steam_api_i486 > libsteam_api
Use ldd -d <plugin.so> to check for dependencies. It won't be able to find the libs; just worry about what they're called.
Mac
Not possible yet

To debug your plugin, you must launch the server with -allowdebug.

Listening to Events

The IGameEventManager2 (IGameEventManager was used previously) interface allows a plugin to listen to game events. Game events are fired by a Mod when things of interest happen, like a player dying or the bomb being planted. IGameEventManager2::AddListener should be called to subscribe to particular game events. It must be called for each event that your listener wishes to be aware of. FireGameEvent is then called in your plugin when an one of the listened for events happen. The data contained in each event is described by event configuration files, in particular:

  • hl2/resource/serverevents.res
  • hl2/resource/GameEvents.res
  • <mod dir>/resource/ModEvents.res

Creating ConVars and Commands

ConVars let you specify variables that users can use to configure the behavior of your plugin. ConCommands allow the creation of commands that your plugin provides. Creating both is easy and self contained, the code snippet below creates a new command empty_version and a new variable plugin_empty. These commands can be run from the server or client, you should use the index provided by SetCommandClient to determine the source of the command.

CON_COMMAND( empty_version, "prints the version of the empty plugin" )
{
        Msg( "Version:1.0.0.0\n" );
}

static ConVar empty_cvar("plugin_empty", "0", 0, "Example plugin cvar");

Other tricks

Get player entities:

static CGlobalVars *gpGlobals;
static IVEngineServer *engine;

CON_COMMAND( list_players, "Prints the name of each connected player" )
{
	for (int i=1;i<gpGlobals->maxClients;i++) // EntIndex 0 is worldspawn, after which come the players
	{
		// n.b. this only works if the player is active; players still connecting won't show up
		IPlayerInfo *playerinfo = playerinfomanager->GetPlayerInfo(engine->PEntityOfEntIndex(i));
		if (playerinfo)
		{
			Msg(playerinfo->GetName());
			Msg("\n");
		}
	}
}

Add and remove a server tag:

#include <string>

void AddTag(char* MyTag)
{
	static ConVar* sv_tags = cvar->FindVar("sv_tags");
	std::string tag_string;

	tag_string.assign(sv_tags->GetString());	

	if (tag_string.find(MyTag) == -1)
	{
		if (tag_string.length() && tag_string.at(tag_string.length()) != ',')
			tag_string.append(",");

		tag_string.append(MyTag);

		sv_tags->SetValue(tag_string.c_str());
	}
}

void RemoveTag(char* MyTag)
{
	static ConVar* sv_tags = cvar->FindVar("sv_tags");
	std::string tag_string;

	tag_string.assign(sv_tags->GetString());	

	size_t start = tag_string.find(MyTag);
	if (start != -1)
	{
		tag_string.erase( start, tag_string.find(start,',') );
		sv_tags->SetValue(tag_string.c_str());
	}
}

See also