Scripting FAQ (SourceMod)
Contents
- 1 How do I learn SourcePawn?
- 2 Where can I find all the SourcePawn functions and other information?
- 3 How do I find the classname of an entity? (e.g., "weapon_knife" or "prop_physics")
- 4 How do I block regular commands, such as kill and say?
- 5 How do I hook +commands, such as +zoom and +attack?
- 6 How do I get rid of loose indentation warnings?
- 7 How do I get rid of tag mismatch warnings?
- 8 How do I add color to my messages?
- 9 Why do I get an "unknown symbol" error when using an SDKTools native?
- 10 Why is Source telling me my command is an "Unknown command"?
How do I learn SourcePawn?
A good start is on the Introduction to SourcePawn page, which will teach you the SourcePawn language. Then go through the Introduction to SourceMod Plugins, which will teach you how to write your first plug-in.
Where can I find all the SourcePawn functions and other information?
All SourceMod plug-ins have this line:
#include <sourcemod>
This line instructs the compiler to retrieve the file sourcemod.inc from the sourcemod/scripting/include folder. Every function, variable, and definition can be found in the .inc files inside the sourcemod/scripting/include folder.
But searching through all those files can be quite tedious. Thankfully, the honourable Nican created an API reference viewable on the web, complete with searching and commenting: http://docs.sourcemod.net/api/
Also, if you are in the SourceMod IRC channel (#sourcemod on GameSurge), you can use the !api command to search Nican's API reference:
<theY4Kman> !api Netclass <yakbot> theY4Kman: GetEntityNetClass(edict, String:clsname[], maxlength): Retrieves an entity's networkable serverclass name. This is not the same as the classname and is used for networkable state changes. (http://docs.sourcemod.net/api/index.php?fastload=show&id=66)
How do I find the classname of an entity? (e.g., "weapon_knife" or "prop_physics")
The classname of an entity (not to be confused with a netclass such as CCSPlayer) is a unique identifier. It's the most well known of entity names. To find it, use the function GetEdictClassname():
char classname[128]; GetEdictClassname(myentity, classname, sizeof(classname)); PrintToServer("myentity classname: %s", classname); // myentity classname: weapon_knife
How do I block regular commands, such as kill and say?
As of version 1.3, the recommended way to hook and block commands is with AddCommandListener(). Previously, using RegConsoleCmd() with the command name was the only way to hook commands, but this creates a whole new command for the command dispatch to check every time any command is executed. AddCommandListener() creates only a lightweight hook, processed only when the specific command is executed.
Here's how to use it to block the say command:
public OnPluginStart() { AddCommandListener(SayCallback, "say"); } public Action:SayCallback(client, const String:command[], argc) { Return Plugin_Handled; }
How do I hook +commands, such as +zoom and +attack?
Unlike regular commands, +commands are handled on the client's computer, then sent to the server in a more compressed fashion. This means AddCommandListener() cannot be used to hook +commands. As of version 1.3, the recommended solution is to use the global forward OnPlayerRunCmd(). This forward is fired every time a player uses a movement button. To detect or block a +command, you'll first have to find out its proper IN_ constant (see entity_prop_stocks.inc).
Here's how to use it to block crouching when attacking:
public Action:OnPlayerRunCmd(client, &buttons, &impulse, Float:vel[3], Float:angles[3], &weapon) { // Check if the player is attacking (+attack) if ((buttons & IN_ATTACK) == IN_ATTACK) { // If so, block their crouching (+duck) buttons &= ~IN_DUCK; } // We must return Plugin_Continue to let the changes be processed. // Otherwise, we can return Plugin_Handled to block the commands return Plugin_Continue; }
How do I get rid of loose indentation warnings?
myplugin.sp(#) : warning 217: loose indentation
Loose indentation warnings arise when indentation in your code is inconsistent. This usually means using both tabs and spaces as indentation. However, it can also mean a different amount of spaces or tabs are being used. Therefore, to correct it, use just one type of indentation. Because different editors have different settings for the size of tab stops, it is recommended you use 4 spaces to indent.
Good:
public OnPluginStart() { new myvar = 5; if (myvar == (2 + 3)) PrintToServer("myvar is %d", myvar); }
Bad:
public OnPluginStart() { new myvar = 5; if (myvar == (2 + 3)) PrintToServer("myvar is %d", myvar); }
How do I get rid of tag mismatch warnings?
Though every variable in SourcePawn is one cell (4 bytes), with the exception of strings, there are many different ways to interpret what's inside a cell. To signify a cell's contents, tags are used. The most common are _ (the default tag: a vanilla cell. This tag is implied when no other tag is specified.), Float, bool, and String. See Introduction to SourcePawn#Variables_2 for more information.
Functions wear these tags on their parameters so you can tell what needs to be passed to the function:
native SetEntPropFloat(entity, PropType:type, const String:prop[], Float:value);
This function calls for entity, a cell with the implied tag _; type, with the developer-defined tag PropType; prop, with the built-in tag String; and value, with the built-in tag Float. To call this function, then, you must pass values with the specified tags. For example:
SetEntPropFloat(1234, Prop_Send, "m_fNumber", 1.0);
This calls the function correctly: 1234 is a regular cell (_), Prop_Send is a variable defined in the enum PropType in entity.inc, "m_fNumber" is a String, and 1.0 is a Float. For a nonexample:
SetEntPropFloat(1234.0, 1, "m_fNumber", 1337);
This is incorrect! 1234.0 is a Float that should be a regular cell (_); 1 is a regular cell (_) that should be a PropType; and 1337 is a regular cell (_) that should be a Float. This call will generate a tag mismatch warning. To correct it, simply use a value with the correct tag. Most of the time, tags that are not built-in (such as PropType) can be found in the same file where a function uses them (you can find PropType in entity.inc).
How do I add color to my messages?
Though the actual colors will vary depending on the mod, you can add color to any chat message using the characters 0x01 to 0x08. For example:
PrintToChatAll ("\x01 1 .. \x02 2 .. \x03 3 .. \x04 4 .. \x05 5 .. \x06 6 .. \x07 7 .. \x08 8");
Example output from Left 4 Dead:
With a little experimenting, you can learn the colors for a mod. However, the meaning behind the colors is generally as follows:
- 0x01 = Normal color
- 0x02 = Use team color to the end of a player's name. When used, it can be the only color used, and it must be at the start of the message.
- 0x03 = Team color
- 0x04 = Location color
This data comes from Counter-Strike: Source.
For CS:GO, here are some colors:
PrintToChat("\x011\x022\x033\x044\x055\x066\x077\x088\x099\x0AA\x0BB\x0CC\x0DD\x0EE\x0FF");
Unfortunately, to use players' team colors, you must use UserMessages, because there is no way for the current SourceMod functions to know which team color to use. Here's an example:
new Handle:hBf; hBf = StartMessageOne("SayText2", player_to); // To send the message to all players, use StartMessageAll("SayText2"); if (hBf != INVALID_HANDLE) { BfWriteByte(hBf, player_from); BfWriteByte(hBf, 0); BfWriteString(hBf, "<\x03player_from team color\x01> My message"); EndMessage(); }
player_to is the client index to send the message to (or use StartMessageAll() instead of StartMessageOne() to send the message to all players). player_from is the client index of the player whose team color will be utilized in the message. The message will now look like this (assuming player_from is on the RED team):
<player_from team color> My message
There's another catch, however: this is only known to work in CS:S and TF2. For other mods, your mileage may vary.
Color Libraries
There are a few libraries that can handle colors for you, so you don't have to muck with UserMessages:
exvel's Colors
This is a simple include file, allowing easy control over chat coloring. You can grab it on the forums. Instead of hex codes, colors are denoted with tags, such as {default}, {green}, or {blue}.
Here's some example usage. Note that this code assumes client 1 is in game and on the BLU team:
#include <colors> public OnPluginStart() { CPrintToChatAll("{green}Hello {red}World! {default}Test 1 concluded."); new client_on_blu_team = 1; CPrintToChatAllEx(client_on_blu_team, "{teamcolor}BLU team {default}says {green}hi!"); }
Hello World! Test 1 concluded. BLU team says hi!
Note that to use a team color, there needs to be at least one player on that team. Otherwise, it will default to {green}.
SMLib
SMLib is a huge collection of stock functions to keep plug-in developers from reinventing the wheel. It includes a colors API very similar to exvel's. The benefits of SMLib are it supports shorthand color names, such as {N} and {G}, and all colors are supported in its Client_PrintToChat() function.
Here's how to print the same thing as the above example with SMLib. Note that this code also assumes client 1 is in game and on the BLU team:
#include <smlib> public OnPluginStart() { Client_PrintToChatAll("{G}Hello {R}World! {N}Test 1 concluded."); new client_on_blu_team = 1; Client_PrintToChatAll(client_on_blu_team, "{T}BLU team {N}says {G}hi!"); }
Hello World! Test 1 concluded. BLU team says hi!
Why do I get an "unknown symbol" error when using an SDKTools native?
None of SourceMod's or SDKTools's functions are built into SourcePawn. Therefore, every time you use one of their functions, SourcePawn needs to know how to call the function. Normally, this is done using includes. Just like you would #include <sourcemod> to use SourceMod's functions, you need to:
#include <sdktools>
to use an SDKTools native.
Why is Source telling me my command is an "Unknown command"?
This is because you're not returning Plugin_Handled in your callback. If you don't, SourceMod believes you didn't want the Source Engine to know the command was registered, and it handles it so.
public Action:MyCommand(client, args) { // Do something... return Plugin_Handled; }