Development

SourcePawn plugins

The same API as C++, as a SourcePawn script — .smx files compiled with spcomp, loaded next to native plugins.

Source2Mod runs two kinds of plugin side by side, and both are first-class:

C++SourcePawn
File.smx (a native library).smx (a compiled SourcePawn script)
Built withThe SDK and your compiler (CMake or AMBuild)spcomp, shipped in addons/source2mod/scripting
APIISource2Mod* interfaces, g_pSource2Mod* globalsSource2Mod* methodmaps, #include <source2mod/api>
Hot reloadYesYes

Both go into addons/source2mod/plugins/. The core tells them apart by their content, so the extension is the same.

One API, two languages

The script API mirrors the C++ one name for name, so a guide written for one reads for the other:

  • Every interface is a methodmap of static methods named like the C++ one, without the I and the version: ISource2ModCommands::RegisterConCommand is Source2ModCommands.RegisterConCommand.
  • Every schema class is a methodmap of the same name and base class: CCSPlayerController, CCSPlayerPawn, … Fields are properties with the C++ names (pawn.m_iHealth = 50), and the helper methods carry over (Teleport, GiveNamedItem, Respawn, …).
  • Services are reached through the entity that owns them: pawn.m_pItemServices.GiveNamedItem("weapon_ak47").
  • Every Hook* returns an id that the matching Unhook* takes.
  • An entity is held as an entity handle: once the entity is gone, IsValid() is false and natives refuse it instead of touching another entity.

samples/cs2/cpp_sample and samples/cs2/sp_sample in the Source2Mod repository are the same plugin in both languages, section for section.

A first script

scripting/hello.sp
#include <source2mod/api>

public Plugin myinfo = {
	name = "Hello",
	author = "You",
	description = "Says hello",
	version = "1.0.0",
	url = ""
};

public void OnPluginStart()
{
	Source2ModCommands.RegisterConCommand("hello", Cmd_Hello);
}

public void Cmd_Hello(Source2ModCommandContext context, Source2ModCommandArgs args, bool post)
{
	char name[64] = "Console";

	CPlayerSlot slot = context.GetPlayerSlot();
	if (slot.IsValid())
	{
		CCSPlayerController player = CCSPlayerController.FromSlot(slot);
		if (player != NULL_ENTITY)
			player.GetPlayerName(name, sizeof(name));
	}

	Source2ModCommands.ReplyToCommand(context, "Hello, %s!", name);
}

public void Source2Mod_OnClientPutInServer(CPlayerSlot slot, const char[] name, int type, const char[] xuid)
{
	Source2ModApi.Log("%s joined", name);
}

The engine callbacks a C++ plugin gets through ISource2ModListener arrive as forwards named Source2Mod_On… (listener.inc): connects and disconnects, level init and shutdown, GameFrame, settings changes.

Compiling

spcomp and the include set ship with every Source2Mod build in addons/source2mod/scripting; the include set itself lives in the SDK under public/scripting.

cd addons/source2mod/scripting
./spcomp -i include hello.sp -o ../plugins/hello.smx

For a project of its own, samples/cs2/sp_sample builds a script with CMake or AMBuild — point SOURCE2MOD_SCRIPTING at a Source2Mod build's addons/source2mod/scripting.

Talking to C++ plugins

A C++ plugin can give scripts natives of its own through ISource2ModNatives, and scripts can create natives and forwards for each other. cpp_sample exposes a few and sp_sample uses them — the library is optional, so the script keeps working while cpp_sample is not loaded.

On this page