Server plugins

From Valve Developer Community
Jump to: navigation, search
English (en)español (es)русский (ru)
... Icon-Important.png

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