Coming from SourceMod
For SourceMod plugin authors — what carries over unchanged, what Source2Mod does differently, and the Source2Mod calls for common SourceMod ones.
Source2Mod scripts are SourcePawn, compiled by SourceMod's own spcomp. The
language, the tooling and much of the core library are what you know. What
is new is everything that touches the game: CS2 is a Source 2 game, and the
API for it is Source2Mod's — the same one the C++ plugins use, not SourceMod's
sdktools.
What stays the same
- The language.
#pragma newdecls required, methodmaps, enum structs, typesets,view_as,any,Function— all of current SourcePawn. spcomp. It ships with Source2Mod inaddons/source2mod/scripting; see Compiling.- The plugin lifecycle.
public Plugin myinfo,AskPluginLoad2,OnPluginStart,OnPluginEnd,OnLibraryAdded/OnLibraryRemoved,OnAutoConfigsBuffered,OnConfigsExecuted. Everything a plugin registered is released when it unloads. - Handles.
Handle,null,delete,Clone(). - ADT.
ArrayList,ArrayStack,StringMap,IntMap, their snapshots, andDataPack, with SourceMod's methods. - ConVars. The
ConVarmethodmap —IntValue,FloatValue,BoolValue,GetString,AddChangeHookwith the sameConVarChangedsignature. - Plugins talking to each other.
GlobalForwardandPrivateForward,ActionwithPlugin_Continue/Plugin_Changed/Plugin_Handled/Plugin_Stop,nativedeclarations,SharedPluginincludes with__pl_<name>_SetNTVOptionalandMarkNativeAsOptional. See Natives and forwards. - Timers.
TIMER_FLAG_REPEATandTIMER_FLAG_NO_MAPCHANGEkeep their meaning.
What is different
One include, no extensions
#include <source2mod/api>There is no sourcemod.inc, and no sdktools, sdkhooks, dhooks or
cstrike. What those do comes from the Source2Mod API:
| SourceMod | Source2Mod |
|---|---|
sdktools — entities, props, players, sounds, traces | The schema classes and their helpers, Source2ModEntities, Source2ModSounds, Source2ModTrace |
sdkhooks | Game hooks (Source2ModGameHooks.HookTakeDamage, HookCanAcquire, HookPostThink, …) and entity listeners |
dhooks | Raw hooks (Source2ModHook) |
SDKCall | Source2ModFunction |
cstrike | Controller and pawn helpers: SwitchTeam, ChangeTeam, Respawn, … |
Names follow the C++ API
SourceMod's free natives are static methods of a methodmap named after the
C++ interface: Source2ModCommands.RegisterConCommand rather than
RegConsoleCmd, Source2ModNatives.CreateNative rather than CreateNative,
Source2ModCall.PushCell rather than Call_PushCell. The methods keep the
C++ names and argument order, so the C++ reference reads
for scripts too.
Players are slots and controllers
There is no client index in the API. A player is a CPlayerSlot (0–63) or
their CCSPlayerController; the body in the world is a separate entity,
the pawn (player.GetPlayerPawn()). A SourceMod client
index is slot + 1. The engine callbacks arrive as forwards named
Source2Mod_On…:
| SourceMod | Source2Mod forward |
|---|---|
OnClientConnect | Source2Mod_OnClientConnect(slot, name, xuid, networkId, unk1, rejectReason, maxlen) |
OnClientConnected | Source2Mod_OnClientConnected |
OnClientPutInServer | Source2Mod_OnClientPutInServer |
OnClientAuthorized | Source2Mod_OnClientAuthorized(slot, steamId) |
OnClientDisconnect, OnClientDisconnect_Post | Source2Mod_OnClientDisconnect, Source2Mod_OnClientDisconnectPost |
OnClientSettingsChanged | Source2Mod_OnClientSettingsChanged |
OnMapStart, OnMapEnd | Source2Mod_OnLevelInit, Source2Mod_OnLevelShutdown |
OnGameFrame | Source2Mod_OnGameFrame(simulating, firstTick, lastTick) |
OnAllPluginsLoaded | Source2Mod_OnAllSource2ModPluginsLoaded |
OnClientCookiesCached | Source2Mod_OnClientCookiesCached |
The full list is in source2mod/listener.inc; see also
Listeners.
Entities are handles
An entity is an entity handle — index and
serial — never an index. Lookups return NULL_ENTITY (-1) for none, live
entities are negative numbers, and a handle kept after its entity is
removed is refused by every native. GetEntProp / SetEntProp become
properties: pawn.m_iHealth = 50, entity.m_iTeamNum.
Commands, hooks and their answers
- A command handler is
void (Source2ModCommandContext context, Source2ModCommandArgs args, bool post). There is noclientparameter (context.GetPlayerSlot()) and nothing to return. - Admin flags are permissions: a command takes a
permission string (
"myplugin.kick") and the core checks it. - Hooks — game events, game hooks, raw hooks, command listeners — answer a
Source2ModAction:Action_Ignore,Action_OverrideorAction_Supersede.Action(Plugin_*) is only for forwards between plugins. - A game event handler gets
(IGameEvent event, bool post, bool &dontBroadcast)and is hooked pre or post;event.GetNamegives the name. Hook*returns anintid;Unhook*takes it.
Timers
Source2ModScheduler.AddTimer(interval, callback, data, flags) returns a
Source2ModTimer, which is not a Handle. The callback is
void (any data): a repeating timer runs until
Source2ModScheduler.KillTimer(timer) — from inside its own callback too —
not until it returns Plugin_Stop. Source2ModScheduler.NextFrame is
RequestFrame.
64-bit values
SteamID64s and other 64-bit integers are decimal text (player.GetSteamID,
IGameEvent.GetUint64) or two cells where an include says so.
Common calls
| SourceMod | Source2Mod |
|---|---|
RegConsoleCmd, RegAdminCmd | Source2ModCommands.RegisterConCommand(name, handler, permission) |
AddCommandListener | Source2ModCommands.RegisterConListener(name, handler, post) |
ReplyToCommand(client, ...) | Source2ModCommands.ReplyToCommand(context, ...) |
CheckCommandAccess | Source2ModPermissions.CheckAccess, PlayerHasPermission |
CreateConVar | CConVar.CreateInt / CreateFloat / CreateBool / CreateString / Create |
FindConVar | CConVarRef.Find |
AutoExecConfig | Source2ModConVars.AutoExecConfig |
HookEvent | Source2ModEvents.HookGameEvent(name, handler, post) |
CreateEvent, Event.Fire | IGameEventManager2.CreateEvent, IGameEventManager2.FireEvent |
CreateTimer, KillTimer | Source2ModScheduler.AddTimer, Source2ModScheduler.KillTimer |
RequestFrame | Source2ModScheduler.NextFrame |
LogMessage, LogError, PrintToServer | Source2ModApi.Log, Source2ModApi.LogError, Source2ModApi.ConPrintf |
Format, VFormat | Source2ModApi.Format, Source2ModApi.FormatArgs |
ServerCommand | Source2ModEngine.ServerCommand |
GetEngineTime, GetGameTime, MaxClients | Source2ModEngine.GetEngineTime, Source2ModApi.GetGlobalVars().curtime, .maxClients |
GetClientName | player.GetPlayerName |
GetClientUserId, GetClientOfUserId | player.GetUserID, CCSPlayerController.FromUserId |
GetClientAuthId | Source2ModClients.GetAuthId, player.GetSteamID |
IsClientInGame, IsFakeClient | Source2ModClients.IsInGame, Source2ModClients.IsFakeClient / player.IsBot |
GetClientCount | Source2ModClients.GetClientCount |
KickClient | Source2ModClients.Kick |
PrintToChat, PrintCenterText | player.PrintToChat, player.PrintToCenter |
ProcessTargetString, FindTarget | Source2ModTargeting.ProcessTargetString, Source2ModTargeting.FindTarget |
CS_SwitchTeam, ChangeClientTeam, CS_RespawnPlayer | player.SwitchTeam, player.ChangeTeam, player.Respawn |
ForcePlayerSuicide | pawn.CommitSuicide(explode, force) |
SetEntityHealth, GetEntProp(…, "m_iHealth") | pawn.m_iHealth |
GetEntProp / SetEntProp (any field) | The property, or Source2ModSchema.GetInt / SetInt(entity, class, field, …) |
GetEntPropVector | The field's method, or Source2ModSchema.GetVector |
GetEntityClassname | entity.GetClassname |
GivePlayerItem | pawn.m_pItemServices.GiveNamedItem |
CreateEntityByName, DispatchSpawn | Source2ModEntities.CreateEntityByName, entity.DispatchSpawn |
AcceptEntityInput, RemoveEntity | entity.AcceptInput, entity.Remove |
FindEntityByClassname | Source2ModEntities.FindEntityByClassname(start, classname) |
TeleportEntity | entity.Teleport(position, angles, velocity) |
GetEntPropVector(…, "m_vecOrigin") | entity.GetAbsOrigin |
HookEntityOutput | Source2ModEntities.AddEntityIOListener |
SDKHook(…, SDKHook_OnTakeDamage, …) | Source2ModGameHooks.HookTakeDamage |
OnEntityCreated | Source2ModEntities.AddEntityListener |
DHookCreateDetour / DynamicDetour | Source2ModHook.FromGameConfig, FromSignature, FromSymbol |
StartPrepSDKCall … EndPrepSDKCall | Source2ModFunction.FromGameConfig + AddParam |
LoadTranslations | Source2ModTranslations.LoadPhraseFile |
GetCurrentMap, ForceChangeLevel | Source2ModMaps.GetCurrentMap, Source2ModMaps.ChangeLevel |
StrEqual, strcopy, StringToInt | Source2ModUtils.Equal, Source2ModUtils.Copy, Source2ModUtils.StringToInt |
SetFailState | Source2ModPlugins.SetFailState |
A plugin, ported
A small SourceMod-style plugin — a ConVar, an admin command, a game event — written for Source2Mod. The SourceMod calls it replaces are in the comments:
#include <source2mod/api>
#pragma semicolon 1
#pragma newdecls required
public Plugin myinfo =
{
name = "Spawn health",
author = "You",
description = "Sets health on spawn, !hp to change it",
version = "1.0.0",
url = ""
};
ConVar g_cvHealth;
public void OnPluginStart()
{
// CreateConVar("spawnhp_health", "100", ...)
g_cvHealth = CConVar.CreateInt("spawnhp_health", FCVAR_NONE, "Health players spawn with", 100, true, 1, true, 1000);
Source2ModConVars.AutoExecConfig();
// RegAdminCmd("sm_hp", Cmd_Hp, ADMFLAG_SLAY) -- "hp" in the console, !hp in chat
Source2ModCommands.RegisterConCommand("hp", Cmd_Hp, "spawnhp.hp");
// HookEvent("player_spawn", Event_PlayerSpawn)
Source2ModEvents.HookGameEvent("player_spawn", Event_PlayerSpawn, true);
}
public Source2ModAction Event_PlayerSpawn(IGameEvent event, bool post, bool &dontBroadcast)
{
// GetClientOfUserId(event.GetInt("userid"))
CCSPlayerController player = event.GetPlayerController("userid");
if (player == NULL_ENTITY)
return Action_Ignore;
// The spawn code may still write the health: one frame later, through the handle.
Source2ModScheduler.NextFrame(ApplyHealth, player);
return Action_Ignore;
}
public void ApplyHealth(any data)
{
CCSPlayerController player = data;
if (!player.IsValid())
return;
CCSPlayerPawn pawn = player.GetPlayerPawn();
if (pawn != NULL_ENTITY)
pawn.m_iHealth = g_cvHealth.IntValue; // SetEntityHealth(client, ...)
}
public void Cmd_Hp(Source2ModCommandContext context, Source2ModCommandArgs args, bool post)
{
if (args.ArgC() < 2)
{
Source2ModCommands.ReplyToCommand(context, "Usage: hp <health>");
return;
}
char value[16];
args.Arg(1, value, sizeof(value));
g_cvHealth.IntValue = Source2ModUtils.StringToInt(value);
Source2ModCommands.ReplyToCommand(context, "Players now spawn with %d HP.", g_cvHealth.IntValue);
}SourcePawn
The script API — how the C++ interfaces, schema classes and hooks look from a .sp, and where scripts work differently.
Entity handles
Entities in scripts are entity handles, not indexes — NULL_ENTITY, IsValid, why an entity is a negative number, converting to and from indexes and slots, and what happens to a handle whose entity is gone.