Difference between revisions of "CSGO Quirks"

From AlliedModders Wiki
Jump to: navigation, search
m (Reverted edits by Donrevan (talk) to last revision by Psychonic)
m (Denoting Text-Colors: Update highlighting)
 
(23 intermediate revisions by 6 users not shown)
Line 3: Line 3:
 
This article explains some of the quirks when coding for CS:GO and offers some workarounds when available and other information.   
 
This article explains some of the quirks when coding for CS:GO and offers some workarounds when available and other information.   
  
=Playing Sounds=
+
=Playing Custom Sounds=
 
Since Left 4 Dead, all normally played sounds must exist in the sound cache on the client. This is an issue for custom sounds as adding to the sound cache requires the client to run snd_rebuildaudiocache (not executable by the server), which also takes a sizable amount of time to run while otherwise locking up the game.
 
Since Left 4 Dead, all normally played sounds must exist in the sound cache on the client. This is an issue for custom sounds as adding to the sound cache requires the client to run snd_rebuildaudiocache (not executable by the server), which also takes a sizable amount of time to run while otherwise locking up the game.
  
Line 16: Line 16:
 
Example:
 
Example:
 
for csgo/sound/custom/ur.mp3
 
for csgo/sound/custom/ur.mp3
<pawn>
+
<sourcepawn>
ClientCommand( client, "play *custom/ur.mp3" );
+
ClientCommand( client, "play */custom/ur.mp3" );
</pawn>
+
</sourcepawn>
  
 
===Fake precaching and EmitSound===
 
===Fake precaching and EmitSound===
Line 26: Line 26:
  
 
Full plugin example:
 
Full plugin example:
<pawn>
+
<sourcepawn>
 
#include <sourcemod>
 
#include <sourcemod>
 
#include <sdktools>
 
#include <sdktools>
  
new const String:FULL_SOUND_PATH[] = "sound/custom/ur.mp3";
+
char FULL_SOUND_PATH[] = "sound/custom/ur.mp3";
new const String:RELATIVE_SOUND_PATH[] = "*custom/ur.mp3";
+
char RELATIVE_SOUND_PATH[] = "*/custom/ur.mp3";
  
public OnPluginStart()
+
public void OnPluginStart()
 
{
 
{
RegConsoleCmd( "sm_testsound", sm_testsound );
+
RegConsoleCmd("sm_testsound", sm_testsound);
 
}
 
}
  
public OnMapStart()
+
public void OnMapStart()
 
{
 
{
AddFileToDownloadsTable( FULL_SOUND_PATH );
+
AddFileToDownloadsTable( FULL_SOUND_PATH);
FakePrecacheSound( RELATIVE_SOUND_PATH );
+
FakePrecacheSound(RELATIVE_SOUND_PATH);
 
}
 
}
  
public Action:sm_testsound( client, argc )
+
public Action sm_testsound(int client, int argc)
 
{
 
{
EmitSoundToClient( client, RELATIVE_SOUND_PATH );
+
EmitSoundToClient(client, RELATIVE_SOUND_PATH);
 
 
 
return Plugin_Handled;
 
return Plugin_Handled;
 
}
 
}
  
stock FakePrecacheSound( const String:szPath[] )
+
void FakePrecacheSound(const char[] szPath)
 
{
 
{
AddToStringTable( FindStringTable( "soundprecache" ), szPath );
+
AddToStringTable(FindStringTable( "soundprecache" ), szPath);
 
}
 
}
</pawn>
+
</sourcepawn>
 +
 
 +
Alternately, if you are going to have sounds in a plugin that supports multiple games, you should use the [https://forums.alliedmods.net/showthread.php?t=237045 EmitSoundAny snippets].
 +
 
 +
''Recent reports indicate that this only works for '''mp3'''s and not '''wav'''s. In the latter case nothing will happen apart from you experiencing puzzled facial expressions and headaches on yourself.''
  
 
===Using the music directory===
 
===Using the music directory===
Line 73: Line 77:
 
This is the number known as gpGlobals->maxClients in SM extensions or MM:S plugins and MaxClients in SourcePawn.
 
This is the number known as gpGlobals->maxClients in SM extensions or MM:S plugins and MaxClients in SourcePawn.
  
It is able to be changed in other games, up to the maximum, by setting the -maxplayers command line parameter. As this doesn't exist in CS:GO, it acts like other games when not set and uses a hardcoded default, '''32''' for CS:GO.
+
It is able to be changed in other games, up to the maximum, by setting the -maxplayers command line parameter. As this doesn't exist in CS:GO, it acts like other games when not set and uses a hardcoded default, '''64''' for CS:GO.
  
 
(This is the default value as returned by IServerGameClients::GetPlayerLimits).
 
(This is the default value as returned by IServerGameClients::GetPlayerLimits).
  
You can also restore the -maxplayers command line functionality with the [https://forums.alliedmods.net/showthread.php?t=198360 CS:GO Max plugin], allowing you to override the default value, up to the absolute maximum.
+
A gamemode's maxplayers can be overridden with the maxplayers parameter in the appropriate section of gamemodes_server.txt
 +
 
 +
To override maxplayers for all gamemodes, use the -maxplayers_override command line parameter. However, be aware that the game itself appears to enforce a 44 player maximum regardless of what you set this to.
  
 
==The GameTypes maxplayers==
 
==The GameTypes maxplayers==
Line 96: Line 102:
  
 
=Menus=
 
=Menus=
This section refers to CHudMenu menus on the client triggered by the ShowMenu usermessage on the server. (Radio-style menus in SourceMod)
+
With SourceMod's "Radio-style" menus (ShowMenu / CHudMenu), the 0 key will cannot be detected due to the client never sending "menuselect 0". SourceMod works around this by limiting menus to 9.
  
 
The older "Valve-style" menus created by IServerPluginHelpers::CreateMessage with the DIALOG_MENU type aren't supported at all due at least to missing res file info on the client.
 
The older "Valve-style" menus created by IServerPluginHelpers::CreateMessage with the DIALOG_MENU type aren't supported at all due at least to missing res file info on the client.
 
==Sizing==
 
At some larger resolutions, the client display of menus is grossly oversized.
 
 
This cannot be fixed from the server.
 
 
==Input while observing==
 
While observing, either dead or as spectator, the slot key inputs are captured by the specgui spectator panel and are not forwarded to the menu system. Therefore, the server never receives the menuselect commands resulting in menu choices being ignored.
 
 
The specgui vgui panel in CS:GO is one of the new [http://gameware.autodesk.com/scaleform Scaleform] panels and thus does not respond to the normal hide panel requests from the server (with the VGUIMenu usermessage, show value of 0/false), else it could be hidden and blocked while menus are up and restored afterward.
 
 
The only known workaround is to block the specgui usermessage completely, causing it to never be shown in the first place. This would result in observers not being able to change their spectator target as, aside from that panel (which also catches spec_next, spec_prev, etc.), "buttons" (IN_ATTACK, IN_ATTACK2) are ignored while observing.
 
  
 
=GameTypes / GameModes=
 
=GameTypes / GameModes=
Line 137: Line 131:
 
<cpp>
 
<cpp>
 
IGameTypes *g_pGameTypes;
 
IGameTypes *g_pGameTypes;
 +
 +
#define GET_V_IFACE_CURRENT_CUSTOM(v_factory, v_var, v_type, v_name) \
 +
        v_var = (v_type *)g_SMAPI->VInterfaceMatch(v_factory, v_name); \
 +
        if (!v_var) \
 +
        { \
 +
                if (error && maxlen) \
 +
                { \
 +
                        g_SMAPI->Format(error, maxlen, "Could not find interface: %s", v_name); \
 +
                } \
 +
                return false; \
 +
        }
  
 
{
 
{
Line 157: Line 162:
 
}
 
}
 
 
GET_V_IFACE_ANY_CUSTOM(matchmakingDSFactory, g_pGameTypes, IGameTypes, VENGINE_GAMETYPES_VERSION);
+
GET_V_IFACE_CURRENT_CUSTOM(matchmakingDSFactory, g_pGameTypes, IGameTypes, VENGINE_GAMETYPES_VERSION);
  
 
if (!g_pGameTypes)
 
if (!g_pGameTypes)
Line 166: Line 171:
 
}
 
}
 
</cpp>
 
</cpp>
 +
 +
=Coding=
 +
There are exceptionalities for some of the SourcePawn coding conventions in CS:GO.
 +
 +
==Slaying players during player_hurt event==
 +
CS:GO will crash if you attempt to call ForcePlayerSuicide() during a player_hurt callback. A timer can be used as a workaround.
 +
 +
Example for slaying a team attacker:
 +
 +
<sourcepawn>
 +
public void OnPluginStart()
 +
{
 +
HookEvent("player_hurt", OnPlayerHurt);
 +
}
 +
 +
public void OnPlayerHurt(Event event, const char[] name, bool dontBroadcast)
 +
{
 +
int victim  = GetClientOfUserId(event.GetInt("userid"));
 +
int attacker = GetClientOfUserId(event.GetInt("attacker"));
 +
if (attacker > 0 && attacker <= MaxClients && IsPlayerAlive(attacker) && GetClientTeam(attacker) == GetClientTeam(victim))
 +
CreateTimer(0.0, SlayTimer, attacker, TIMER_FLAG_NO_MAPCHANGE);
 +
}
 +
 +
public Action SlayTimer(Handle timer, int client)
 +
{
 +
ForcePlayerSuicide(client);
 +
}
 +
</sourcepawn>
 +
 +
==Assists / Score==
 +
Assists and score are not named entity properties in CS:GO. To view or alter assists or displayed score, you can use the following natives new in SM 1.5.0-hg3706:
 +
 +
CS_GetClientAssists
 +
CS_SetClientAssists
 +
CS_GetContributionScore
 +
CS_SetContributionScore
 +
 +
==Denoting Text-Colors==
 +
CSGO has a few odd requirements for properly coloring text in chat.
 +
 +
<sourcepawn>
 +
// \x01 is white.
 +
// \x04 is green.
 +
 +
//These examples set the first half of the string green, and the second half white.
 +
 +
//Normal example:
 +
char normalColoring[] = "\x04This half is green,\x01 This half is white.";
 +
 +
//CSGO example:
 +
char csgoColoring[] = " \x01\x0B\x04This half is green,\x01 This half is white.";
 +
</sourcepawn>
 +
Specific Quirks:
 +
* The string must start with a space.
 +
* Following the space there must be a white color-indicator (\x01).
 +
* Following the \x01, there must be a printable character. \x0B works great because it does not appear in the message.
 +
* After all of these steps you can treat the rest of the string like normal.
 +
  
 
[[Category:SourceMod Development]]
 
[[Category:SourceMod Development]]
 
[[Category:SourceMod Scripting]]
 
[[Category:SourceMod Scripting]]

Latest revision as of 17:17, 29 March 2020

Others are encouraged to expand on this article with their findings or to clarify any information

This article explains some of the quirks when coding for CS:GO and offers some workarounds when available and other information.

Playing Custom Sounds

Since Left 4 Dead, all normally played sounds must exist in the sound cache on the client. This is an issue for custom sounds as adding to the sound cache requires the client to run snd_rebuildaudiocache (not executable by the server), which also takes a sizable amount of time to run while otherwise locking up the game.

Workarounds

Note that with any of these methods, it is still required that you still add the sound to the download table if you need the client to download it from the server or "fastdl".

All of the listed workarounds involve explicitly telling the client to stream the sound directly from the disk rather than starting it from the cache. This is less than ideal, but seems to work well enough. These have only been made to work with mp3 files. A way to support wav files has not yet been found.

The "play" client command

If you don't need all of the flexibility that the EmitSound* natives expose and just need one or more clients to hear a sound, you can use the ClientCommand native with the 'play' command. The only difference from normal operation is the prefixing of an asterisk (*) to the path which denotes the sound to be streamed. For this method, you do not need to use PrecacheSound on the server.

Example: for csgo/sound/custom/ur.mp3

ClientCommand( client, "play */custom/ur.mp3" );

Fake precaching and EmitSound

For this method, you also need to make use of the asterisk trick. Unfortunately, the EmitSound natives will fail if the file is not listed in the soundprecache table, and PrecacheSound will fail if the file does not exist, (which is won't, as it treats the asterisk as part of the path when doing the lookup).

Since the sound will be streamed, not needing to actually be precached other than to satisfy the check in EmitSound, we can work around this by manually adding the path, with the asterisk prefixed, directly to the soundprecache table. Then you use the EmitSound native of choice as usual with the exception of the asterisk.

Full plugin example:

#include <sourcemod>
#include <sdktools>
 
char FULL_SOUND_PATH[] = "sound/custom/ur.mp3";
char RELATIVE_SOUND_PATH[] = "*/custom/ur.mp3";
 
public void OnPluginStart()
{
	RegConsoleCmd("sm_testsound", sm_testsound);
}
 
public void OnMapStart()
{
	AddFileToDownloadsTable( FULL_SOUND_PATH);
	FakePrecacheSound(RELATIVE_SOUND_PATH);
}
 
public Action sm_testsound(int client, int argc)
{
	EmitSoundToClient(client, RELATIVE_SOUND_PATH);
 
	return Plugin_Handled;
}
 
void FakePrecacheSound(const char[] szPath)
{
	AddToStringTable(FindStringTable( "soundprecache" ), szPath);
}

Alternately, if you are going to have sounds in a plugin that supports multiple games, you should use the EmitSoundAny snippets.

Recent reports indicate that this only works for mp3s and not wavs. In the latter case nothing will happen apart from you experiencing puzzled facial expressions and headaches on yourself.

Using the music directory

By adding your custom sounds under sound/music/, they will automatically be streamed from disk. They can also be used just like sounds in any other game with regard to PrecacheSound (albeit not necessary to be beyond adding to soundprecache table) and EmitSound.

This will have the side effect of your sounds volume being tied to the game's music volume, which many players turn down or off. For this reason, it's not recommended.

Max Players, Clients

CS:GO has four different values that all affect the maximum number of players that can join a game. The lowest one of the four determines the value used.

The absolute maximum

The current absolute maximum number of players for CS:GO, including GOTV is 64. This is a compile-time maximum in the engine on both client and server and cannot be changed.

(This is the maximum value as returned by IServerGameClients::GetPlayerLimits).

The engine's Maxclients

This is the number known as gpGlobals->maxClients in SM extensions or MM:S plugins and MaxClients in SourcePawn.

It is able to be changed in other games, up to the maximum, by setting the -maxplayers command line parameter. As this doesn't exist in CS:GO, it acts like other games when not set and uses a hardcoded default, 64 for CS:GO.

(This is the default value as returned by IServerGameClients::GetPlayerLimits).

A gamemode's maxplayers can be overridden with the maxplayers parameter in the appropriate section of gamemodes_server.txt

To override maxplayers for all gamemodes, use the -maxplayers_override command line parameter. However, be aware that the game itself appears to enforce a 44 player maximum regardless of what you set this to.

The GameTypes maxplayers

CS:GO has a new "GameTypes" system with it's own set of game mode and type -specifec params. There is more on this below, but it also includes a maxplayers value, also referred to as numSlots or MaxHumanPlayers in some areas.

This is the value set in gamemodes.txt or overridden in gamemodes_server.txt or overridden by the new -maxplayer_override command line parameter.

It is used for showing the maxplayers listed in the output of the status command as well as the max count used for the server browser (unless overridden by sv_visiblemaxplayers).

This does not include the count set by the "extraspectators" value in the gamemodes.txt, but both maxplayers and extraspectators must be equal or less than the engine's Maxclients.

The GameTypes max can be retrieved in SourcePawn with the new GetMaxHumanPlayers native or in C++ with IServerGameClients::GetMaxHumanPlayers.

Spawnpoint count

Regardless of the above values, you're limited by the running map's number of spawnpoints, evenly split per team (30 for stock maps).

You can use a mod like Stripper:Source or Spawn Tools 7 to add more spawnpoint entities.

Menus

With SourceMod's "Radio-style" menus (ShowMenu / CHudMenu), the 0 key will cannot be detected due to the client never sending "menuselect 0". SourceMod works around this by limiting menus to 9.

The older "Valve-style" menus created by IServerPluginHelpers::CreateMessage with the DIALOG_MENU type aren't supported at all due at least to missing res file info on the client.

GameTypes / GameModes

CS:GO uses a new "GameTypes" system for coordinating server mode and type info between the server, client, and matchmaking, as well as for handling some server rules.

Some things handled by the GameTypes system:

  • Game types
  • Game modes
  • Config to execute for each mode
  • Weapon progression for applicable modes
  • Maps and map order for each mode
  • Maxplayers for each type and mode
  • Player and view models for each map
  • Bot difficulty
  • ELO ranking data

gamemodes.txt is the main data file (in KeyValues format) holding the backing info, with an optional gamemodes_server.txt being merged into it.

The gamemodes data files should not be accessed directly, but rather through the IGameTypes interface. None of it is currently exposed in SourceMod but there are plans to add a new extension giving access to much of the data.

In C++, you can easily get a pointer to the gametypes interface with CreateInterfaceFn from the matchmaking_ds binary, looking up VENGINE_GAMETYPES_VERSION.

See: http://hg.alliedmods.net/hl2sdks/hl2sdk-csgo/file/tip/public/matchmaking/igametypes.h

Example for getting the gametypes ptr in an SM extension, courtesy of Drifter's work-in-progress gametypes extension:

IGameTypes *g_pGameTypes;
 
#define GET_V_IFACE_CURRENT_CUSTOM(v_factory, v_var, v_type, v_name) \
        v_var = (v_type *)g_SMAPI->VInterfaceMatch(v_factory, v_name); \
        if (!v_var) \
        { \
                if (error && maxlen) \
                { \
                        g_SMAPI->Format(error, maxlen, "Could not find interface: %s", v_name); \
                } \
                return false; \
        }
 
{
	CreateInterfaceFn matchmakingDSFactory = NULL;
	ILibrary *mmlib;
	char path[PLATFORM_MAX_PATH];
 
	libsys->PathFormat(path, sizeof(path), "%s/bin/matchmaking_ds%s.%s", g_SMAPI->GetBaseDir(), MATCHMAKINGDS_SUFFIX, MATCHMAKINGDS_EXT);
 
	if ((mmlib = libsys->OpenLibrary(path, NULL, 0)))
	{
		matchmakingDSFactory = (CreateInterfaceFn)mmlib->GetSymbolAddress("CreateInterface");
		mmlib->CloseLibrary();
	}
 
	if (!matchmakingDSFactory)
	{
		g_pSM->Format(error, maxlen, "Failed to find matchmakingDS factory");
		return false;
	}
 
	GET_V_IFACE_CURRENT_CUSTOM(matchmakingDSFactory, g_pGameTypes, IGameTypes, VENGINE_GAMETYPES_VERSION);
 
	if (!g_pGameTypes)
	{
		g_pSM->Format(error, maxlen, "Failed to find IGameTypes ptr");
		return false;
	}
}

Coding

There are exceptionalities for some of the SourcePawn coding conventions in CS:GO.

Slaying players during player_hurt event

CS:GO will crash if you attempt to call ForcePlayerSuicide() during a player_hurt callback. A timer can be used as a workaround.

Example for slaying a team attacker:

public void OnPluginStart()
{
	HookEvent("player_hurt", OnPlayerHurt);
}
 
public void OnPlayerHurt(Event event, const char[] name, bool dontBroadcast)
{
	int victim   = GetClientOfUserId(event.GetInt("userid"));
	int attacker = GetClientOfUserId(event.GetInt("attacker"));
	if (attacker > 0 && attacker <= MaxClients && IsPlayerAlive(attacker) && GetClientTeam(attacker) == GetClientTeam(victim))
		CreateTimer(0.0, SlayTimer, attacker, TIMER_FLAG_NO_MAPCHANGE);
}
 
public Action SlayTimer(Handle timer, int client)
{
	ForcePlayerSuicide(client);
}

Assists / Score

Assists and score are not named entity properties in CS:GO. To view or alter assists or displayed score, you can use the following natives new in SM 1.5.0-hg3706:

CS_GetClientAssists CS_SetClientAssists CS_GetContributionScore CS_SetContributionScore

Denoting Text-Colors

CSGO has a few odd requirements for properly coloring text in chat.

// \x01 is white.
// \x04 is green.
 
//These examples set the first half of the string green, and the second half white.
 
//Normal example:
char normalColoring[] = "\x04This half is green,\x01 This half is white.";
 
//CSGO example:
char csgoColoring[] = " \x01\x0B\x04This half is green,\x01 This half is white.";

Specific Quirks:

  • The string must start with a space.
  • Following the space there must be a white color-indicator (\x01).
  • Following the \x01, there must be a printable character. \x0B works great because it does not appear in the message.
  • After all of these steps you can treat the rest of the string like normal.