Server plugins
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
.
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/
, editMakefile.plugin
to include your files, and then executemake plugin
. - If you are compiling for Source 2009, you must change the names of some Valve libraries in
Makefile
andMakefile.vpcm
: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
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());
}
}